实体框架: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的部分可以解决此问题。
如何构建子属性,而不必在女孩和男孩属性中再次这样做?
短答:
如果你想让你的代码工作,在你的模型配置中删除任何对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
方法时,这不会按照惯例发生。您可以看到,我添加了流畅调用,以便在Girl
和Boy
映射中实现这一点。
我重做了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(...));
}