c# - 将引用属性映射到抽象父级

标签 c# entity-framework inheritance entity-framework-6 tph

我的企业应用程序中有一个复杂的对象层次结构。我会尽量保持简单、抽象,但仍能代表我正在处理的问题。

我的项目处理同一类型对象的多种样式。为此,我们为实体对象实现了 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) 没有从 DetailsWidget 的导航属性。

生成的数据库架构如下所示:

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/

相关文章:

c# - 在 Xamarin Studio 中以编程方式创建 NSView

entity-framework - 首先更新数据库模型 (.edmx) 模型

c# - 为什么没有具有相同基类和派生类名称的循环继承

c++ - C++中的继承和多文件

c# - NHibernate QueryOver<> - 通过 SubQuery 聚合函数

c# - Xamarin.Android 中的文件提供程序异常

c# - 具有 Oracle 托管驱动程序将 NUMBER(10) 映射到 long 的 Entity Framework 不起作用

c# - 使用 Entity Framework 处理自定义字段

javascript - ES6类语法不是给我们提供了 "classical"继承吗?

c# - 为什么断点条件执行失败?