For some time I've been following the "First Class Collections" rule from Object Calisthenics in my domain design. In order to avoid creating a useless "Collection" table though, I use the table splitting configuration from Entity Framework.
But if for whatever reason I have a Parent
class that doesn't have properties besides its Id and the child collection, I get the exception:
InvalidOperationException: A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column: 'Id'.
The weird thing is that the database is created correctly, and I can query from it, but saving isn't possible.
If I simply add another property to the Parent
, the problem disappears, which is even more strange.
I narrowed it down to a very simple test case:
Program.cs
class Program
{
static void Main(string[] args)
{
using (var context = new MyContext(new DropCreateDatabaseAlways<MyContext>()))
{
context.Set<Parent>().Find(1);
}
using (var context = new MyContext(new CreateDatabaseIfNotExists<MyContext>()))
{
context.Set<Parent>().Add(
new Parent()
{
ChildrenCollection = new ChildrenCollection()
{
List = new List<Child>() { new Child() }
}
});
context.SaveChanges(); // Exception thrown here
}
}
}
Domain
public class Parent
{
public int Id { get; set; }
public virtual ChildrenCollection ChildrenCollection { get; set; }
}
public class ChildrenCollection
{
public int Id { get; set; }
public virtual IList<Child> List { get; set; }
}
public class Child
{
public int Id { get; set; }
}
Context
public class MyContext : DbContext
{
public MyContext(IDatabaseInitializer<MyContext> dbInitializer)
: base(nameOrConnectionString: GetConnectionString())
{
Database.SetInitializer(dbInitializer);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new ChildrenCollectionConfiguration());
modelBuilder.Configurations.Add(new ParentConfiguration());
base.OnModelCreating(modelBuilder);
}
private static string GetConnectionString()
{
return @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=TestEntityFramework;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False;MultipleActiveResultSets=true;";
}
}
Configuration
public class ParentConfiguration : EntityTypeConfiguration<Parent>
{
public ParentConfiguration()
{
HasRequired(x => x.ChildrenCollection)
.WithRequiredPrincipal();
}
}
public class ChildrenCollectionConfiguration : EntityTypeConfiguration<ChildrenCollection>
{
public ChildrenCollectionConfiguration()
{
#region Configure Table Splitting
var parentTable = typeof(Parent).Name;
ToTable(parentTable);
HasMany(x => x.List)
.WithRequired()
.Map(x =>
{
x.MapKey(string.Concat(parentTable, "_Id"));
});
#endregion
}
}
Maintaining the logical order of configuration from Parent to Child is natural when configuring directly from the DbContext class, but when you separate the config using EntityTypeConfiguration<TEntity>
, that might not be the case.
This seems to be a bug in Entity Framework for this specific scenario, since it works in most cases.
To make sure it always works, simply call the configuration for classes higher in the hierarchy first.
public class MyContext : DbContext
{
...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new ParentConfiguration()); // must come first
modelBuilder.Configurations.Add(new ChildrenCollectionConfiguration()); // comes after
base.OnModelCreating(modelBuilder);
}
...
}