【问题标题】:Mapping reference property to abstract parent将引用属性映射到抽象父级
【发布时间】:2017-10-23 18:35:03
【问题描述】:

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

我的项目处理同一类型对象的几种样式。为此,我们为实体对象实现了 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'。

我查看了“some other”“questions”,但这些答案都没有帮助我。

作为最后的努力,我尝试使用 .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 值应该是一个逗号分隔的外键属性名称列表。

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

【问题讨论】:

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


    【解决方案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) 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);
    

    【讨论】:

    • 太棒了!这是有道理的,但有点违反直觉。一旦我开始一天的工作,我会试一试。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-16
    • 1970-01-01
    • 1970-01-01
    • 2017-03-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多