我的企业应用程序中有一个复杂的对象层次结构。我会尽量保持简单、抽象,但仍能代表我正在处理的问题。
我的项目处理同一类型对象的多种样式。为此,我们为实体对象实现了 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: An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details. ---> System.Data.Entity.Core.UpdateException: Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values.
这样,我就明白它没有正确的关系方向和映射的键。所以我再次在 DbContext 中显式设置它:
modelBuilder.Entity<NewWidgetA>()
.HasRequired(w => w.Foo)
.WithRequiredDependent();
但是,这给了我错误:
System.InvalidOperationException: A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column: 'WidgetId'.
我查看了“some other”“questions”,这些答案都没有帮助我。
作为最后的努力,我尝试使用带有 Func 的 .WithRequiredDependent()
重载。但是,因为它与我映射的类型不完全相同,因为我将该属性作为抽象基础,所以它会提示。因此,我尝试像这样转换它:
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);
但是,这也会产生错误:
The ForeignKeyAttribute on property 'Widget' on type '...Foo' is not valid. The foreign key name 'WidgetId' was not found on the dependent type 'NewWidgetA'. The Name value should be a comma separated list of foreign key property names.
这让我相信我无法用抽象属性来做我想做的事情。有没有办法映射我所缺少的这种关系?我不想为每个属性提供特定的引用属性,因为我知道一两个月内会有更多类型出现,并且属性列表将变得难以处理。
最佳答案
这是可能的,但仅限于单向(仅在 Widget
侧具有导航属性)一对一 Shared Primary Key Association ,其中 Widget
端是主体,Details
端是从属。
首先从 Details
实体中删除导航和 FK 属性:
public class FooDetails {
public int Id { get; set; }
// ...
}
public class BarDetails {
public int Id { get; set; }
// ...
}
并使用以下流畅的配置:
modelBuilder.Entity<NewWidgetA>()
.HasRequired(w => w.Foo)
.WithRequiredPrincipal();
modelBuilder.Entity<NewWidgetB>()
.HasRequired(w => w.Foo)
.WithRequiredPrincipal();
modelBuilder.Entity<NewWidgetB>()
.HasRequired(w => w.Bar)
.WithRequiredPrincipal();
注意 WithRequiredPrincipal()
调用。它告诉 EF (1) Widget
是主体,(2) 没有从 Details
到 Widget
的导航属性。
生成的数据库架构如下所示:
CreateTable(
"dbo.BaseWidget",
c => new
{
Id = c.Int(nullable: false, identity: true),
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.ExistingWidget",
c => new
{
Id = c.Int(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.BaseWidget", t => t.Id)
.Index(t => t.Id);
CreateTable(
"dbo.NewWidgetBase",
c => new
{
Id = c.Int(nullable: false),
EmployeeNumber = c.Int(nullable: false),
EffectiveDate = c.DateTime(nullable: false),
Discriminator = c.String(nullable: false, maxLength: 128),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.BaseWidget", t => t.Id)
.Index(t => t.Id);
CreateTable(
"dbo.FooDetails",
c => new
{
Id = c.Int(nullable: false),
Data = c.String(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.NewWidgetBase", t => t.Id)
.Index(t => t.Id);
CreateTable(
"dbo.BarDetails",
c => new
{
Id = c.Int(nullable: false),
Data = c.String(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.NewWidgetBase", t => t.Id)
.Index(t => t.Id);
关于c# - 将引用属性映射到抽象父级,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46896221/