我在企业应用程序中有一个复杂的对象层次结构。我会尽量保持简单、抽象,但仍能代表我正在处理的内容。
我的项目处理同一类型对象的几种样式。为此,我们为实体对象实现了 TPT 结构:
public abstract class BaseWidget {
public int Id { get; set; }
// etc...
}
// About a dozen concrete implementations already exist and work great!
public class ExistingWidget : BaseWidget {
// Other properties
}
现在我正在做一种新的类型。我们在对象上有共同的属性,但是根据子类型需要一些不同的细节集。为此,我设置了 TPH,因为该类型的属性在所有子类型中都是相同的。唯一的区别是需要哪些细节对象。
public abstract NewWidgetBase : BaseWidget {
public int EmployeeNumber { get; set; }
public DateTime EffectiveDate { get; set; }
}
public NewWidgetA : NewWidgetBase {
}
public NewWidgetB : NewWidgetBase {
}
我将它映射到我的 DbContext 中,如下所示:
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<NewWidgetBase>()
.Map<NewWidgetA>(w => w.Requires("Discriminator").HasValue("a"))
.Map<NewWidgetB>(w => w.Requires("Discriminator).HasValue("b"));
此时,我已经使用了集成测试并成功检查了我可以保存到两个表中。
现在,我想添加细节:
public class FooDetails {
public int Id { get; set; }
public int NewWidgetId { get; set; }
// ...
[ForeignKey(nameof(NewWidgetId))]
public NewWidgetBase NewWidget { get; set; }
}
public class BarDetails {
public int Id { get; set; }
public int NewWidgetId { get; set; }
// ...
[ForeignKey(nameof(NewWidgetId))]
public NewWidgetBase NewWidget { get; set; }
}
然后我将这些引用属性添加到我的适当NewWidget
对象中。
public class NewWidgetA {
// ...
public FooDetails Foo { get; set; }
}
public class NewWidgetB {
// ...
public FooDetails Foo { get; set; }
public BarDetails Bar { get; set; }
}
我试着执行这个,假设典型的映射可以工作,并得到以下错误:
System.Data.Entity.Infrastructure.DbUpdateException:保存不为其关系公开外键属性的实体时发生错误。EntityEntries 属性将返回 null,因为无法将单个实体标识为异常源。通过在实体类型中公开外键属性,可以更轻松地在保存时处理异常。有关详细信息,请参阅 InnerException。---> System.Data.Entity.Core.UpdateException:无法确定相关操作的有效顺序。由于外键约束、模型要求或存储生成的值,可能存在依赖关系。
有了这个,我明白它没有正确的关系方向和映射键。所以我再次在 DbContext 中明确设置它:
modelBuilder.Entity<NewWidgetA>()
.HasRequired(w => w.Foo)
.WithRequiredDependent();
但是,这给了我错误:
System.InvalidOperationException:ReferentialConstraint 中的依赖属性映射到存储生成的列。列:'WidgetId'。
作为最后的努力,我尝试使用.WithRequiredDependent()
需要 Func 的重载。但是,因为它与我正在映射的类型不完全相同,因为我将属性作为抽象基础,所以它会抱怨。因此,我尝试像这样投射它:
modelBuilder.Entity<NewWidgetA>()
.HasRequired(w => w.Foo)
.WithRequiredDependent(f => (NewWidgetA)f.Widget);
modelBuilder.Entity<NewWidgetB>()
.HasRequired(w => w.Foo)
.WithRequiredDependent(f => (NewWidgetB).Widget);
modelBuilder.Entity<NewWidgetB>()
.HasRequired(w => w.Bar)
.WithRequiredDependent(b => (NewWidgetB).Widget);
但是,这也会产生错误:
类型“...Foo”的属性“Widget”上的 ForeignKeyAttribute 无效。在依赖类型“NewWidgetA”上找不到外键名称“WidgetId”。Name 值应该是逗号分隔的外键属性名称列表。
这让我相信我无法用抽象属性做我想做的事情。有没有办法映射我缺少的这种关系?我不想为每个都有一个特定的参考属性,因为我知道一两个月内会有更多类型,并且属性列表会变得笨拙。