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

时间:2016-11-16 15:00:17

标签: c# entity-framework inheritance ef-fluent-api

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

更广泛的说明 在实体框架中,我有一个班级儿童,以及两种儿童:男孩和女孩。男孩和女孩都来自儿童:

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

From several sources, for example this one我了解到这被称为TPC:每个具体类的表,我应该在OnModelCreating中使用MapInheritedProperties

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的部分可以解决此问题。

如何在Girl中构建子属性而不必在Boy属性中再次构建它?

2 个答案:

答案 0 :(得分:2)

简短回答:

如果您希望代码正常工作,请删除模型配置中对Child实体的任何引用。一旦EF知道Child作为实体,它就会强制执行以下规则:不能有2个类型Child的实体或2个实体从内存中具有相同PK的Child继承。您可以看到错误告诉您成功持久化的实体;但当EF拉出新ID时,它发现两者都有相同的ID。

LONG ANSWER

删除

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映射中实现这一点。

希望这有帮助。

答案 1 :(得分:2)

  

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

Arturo建议使用数据注释。我不想使用该方法的原因是,类的建模不必与某个数据库表示相对应。我有点假设,但如果我希望男孩姓名的最大长度小于女孩的名字,那么数据注释就不会有帮助。

此外,还有一些事情需要使用流畅的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)...
    }
}

在MyDbContext中:

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