1

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

我的项目处理同一类型对象的几种样式。为此,我们为实体对象实现了 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 值应该是逗号分隔的外键属性名称列表。

这让我相信我无法用抽象属性做我想做的事情。有没有办法映射我缺少的这种关系?我不想为每个都有一个特定的参考属性,因为我知道一两个月内会有更多类型,并且属性列表会变得笨拙。

4

1 回答 1

1

这是可能的,但只能使用单向(仅在一侧具有导航属性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) the是主体,(2) toWidget没有导航属性。 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);
于 2017-10-25T06:52:14.040 回答