EF6 Table 拆分和异常 "A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column: 'Id'."

EF6 Table Splitting and exception "A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column: 'Id'."

一段时间以来,我一直在遵循 Entity Framework 的 Object Calisthenics in my domain design. In order to avoid creating a useless "Collection" table though, I use the table splitting 配置中的 "First Class Collections" 规则。

但是如果出于某种原因我有一个 Parent class 除了其 Id 和子集合之外没有其他属性,我会得到异常:

InvalidOperationException: A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column: 'Id'.

奇怪的是数据库创建正确,我可以从中查询,但无法保存。

如果我简单地在Parent中再添加一个属性,问题就消失了,这就更奇怪了。

我将其缩小为一个非常简单的测试用例:

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
        }
    }
}

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; }
}

上下文

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;";
    }
}

配置

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
    }
}

配置顺序很重要,父配置必须在前。

直接从 DbContext class 配置时,保持从父到子的配置逻辑顺序是很自然的,但是当您 separate the config using EntityTypeConfiguration<TEntity> 时,情况可能并非如此。

对于这种特定情况,这似乎是 Entity Framework 中的一个错误,因为它在大多数情况下都有效。

为确保它始终有效,只需先调用层次结构中较高 classes 的配置。

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);
    }
    ...
}