实体框架:TPC MapInheritedProperties模型超类属性

本文关键字:模型 超类 属性 MapInheritedProperties TPC 框架 实体 | 更新日期: 2023-09-27 18:18:17

Summary:在实体框架中,我使用TPC创建来自同一基类的两个类。在流畅的API我映射继承属性,但如何建模基类的属性?

更详细的描述在实体框架中,我有一个类Child,和两种类型的Child:一个男孩和一个女孩。Boy和Girl都源自Child:

public class Child
{
    public int Id {get; set;}
    public string Name {get; set;}
}
public class Boy : Child
{
    public string SomeBoyishProperty {get; set;}
}
public class Girl : Child
{
    public string SomeGirlyProperty {get; set;}
}

我想要一个有男孩的表和一个有女孩的表,每个表也有孩子属性。

public class MyDbContext : DbContext
{
    public DbSet<Boy> Boys {get; set;}
    public DbSet<Girl> Girls {get; set;
}

从几个来源,例如这个,我了解到这被称为TPC:表每个具体类,我应该MapInheritedProperties在OnModelCreating

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    // model the properties of the base class, for instance set max length
    modelBuilder.Entity<Child>()
        .Property(p => p.Name).IsRequired().HasMaxLength(12);
    // Model Daughter:
    modelBuilder.Entity<Daughter>()
    .Map(m =>
    {
        m.MapInheritedProperties();
        m.ToTable("Daughters");
    })
    .Property(p => p.SomeGirlyProperty).IsOptional().HasMaxLength(13);
    // model Boy
    modelBuilder.Entity<Son>()
    .Map(m =>
    {
        m.MapInheritedProperties();
        m.ToTable("Sons");
    })
    .Property(p => p.SomeBoyishProperty).IsOptional().HasMaxLength(14);
}

在SaveChanges期间,我得到一个InvlidOperationException,表明主键不是唯一的。删除构建Child的部分可以解决此问题。

如何构建子属性,而不必在女孩和男孩属性中再次这样做?

实体框架:TPC MapInheritedProperties模型超类属性

短答:

如果你想让你的代码工作,在你的模型配置中删除任何对Child实体的引用。一旦EF知道Child作为实体,它将强制执行以下规则:不能有2个类型为Child的实体或2个从Child继承的实体具有相同的内存PK。你可以看到这个错误告诉你成功持久化的实体;但是当EF提取新的ID时,它发现两者具有相同的ID。

长回答

删除

modelBuilder.Entity<Child>()
    .Property(p => p.Name).IsRequired().HasMaxLength(12);

相反,这是你的OnModelCreating方法应该看起来像。

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    // Model Daughter:
    var girlEntity = modelBuilder.Entity<Girl>();
    girlEntity.Map(m =>
    {
        m.MapInheritedProperties();
        m.ToTable("Daughters");
    });
    girlEntity.Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
    girlEntity.Property(p => p.Name).IsRequired().HasMaxLength(12);
    girlEntity.Property(p => p.SomeGirlyProperty).IsOptional().HasMaxLength(13);
    // model Boy
    var boyEntity = modelBuilder.Entity<Boy>();
    boyEntity.Map(m =>
    {
        m.MapInheritedProperties();
        m.ToTable("Sons");
    });
    boyEntity.Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
    boyEntity.Property(p => p.Name).IsRequired().HasMaxLength(12);
    boyEntity.Property(p => p.SomeBoyishProperty).IsOptional().HasMaxLength(14);
}

如果你不想在配置中重复配置,我会在基类上使用DataAnnotations属性来强制名称是必需的。

您还需要强制Id属性在数据库中自动生成。在fluent API中使用Map方法时,这不会按照惯例发生。您可以看到,我添加了流畅调用,以便在GirlBoy映射中实现这一点。

我重做了Arturo的解决方案。这个解决方案太长了描述为评论。所以Arturo:谢谢你给我的想法。帽子!

Arturo建议使用Data Annotations。我不想使用那个方法的原因是,类的建模不一定与某个数据库表示相对应。我有点假设,但是如果我想让男孩名字的最大长度小于女孩名字的最大长度,那么数据注释就没有用了。

此外,有一些事情必须使用流畅的API来完成。例如,您不能使用DataAnnotations说System.DateTime在数据库中具有DateTime2格式。

如果你还没有猜到的话:我的问题描述是高度简化的。这三个类都有很多属性,需要大量流畅的API配置

Arturo的评论帮助我找到了以下解决方案:

internal class ChildConfig<T> : EntityTypeConfiguration<T> where T : Child
{
    public ChildConfig(...)
    {
        // configure all Child properties
        this.Property(p => p.Name)....
    }
}
internal class BoyConfig : ChildConfig<Boy>
{
    public BoyConfig(...) : base (...)
    {
        // the base class will configure the Child properties
        // configure the Boy properties here
        this.Property(p => p.SomeBoyishProperty)...
    }
}

And in MyDbContext:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Configurations.Add(new BoyConfig(...));
    modelBuilder.Configuration.Add(new GirlConfig(...));
}