【问题标题】:Foreign key with composite key in EF CoreEF Core 中具有复合键的外键
【发布时间】:2019-06-23 19:04:04
【问题描述】:

我有以下课程:

    public class ProductAttribute 
    {
        public Guid ProductId { get; set; }

        public Guid AttributeId { get; set; }

        public List<ProductAttributeValue> Values { get; set; }

        public object[] GetKeys()
        {
            return new object[] {ProductId, AttributeId};
        }
    }

    public class Product
    {
        public Guid Id { get; set; }

        public string Name { get; set; }
    }

    public class Attribute
    {
        public Guid Id { get; set; }

        public string Name { get; set; }
    }

    public class ProductAttributeValue
    {
        public Guid Id { get; set; }

        public string Name { get; set; }
    }

在原始情况下 Product 和 Attribute 是 AggregateRoot 所以我想通过属性引用跳过导航。值是一个简单的实体,但我需要它作为我的 ProductAttribute 类中的列表引用,因为您看到该类具有复合键。但我想要与 ProductAttribute 之间的级联删除的必需关系 和产品属性值。

这个项目是外部模块,所以我的 fluent API 配置是在目标应用程序 DbContext OnModelCreating 中调用的扩展。我应该配置所有属性和引用,否则不起作用。

        builder.Entity<ProductAttribute>(b =>
        {
            b.ToTable("ProductAttributes");

            b.HasKey(x => new {x.ProductId, x.AttributeId});

            //I should config ProductAttributeValue one-to-many manually here
        }

        builder.Entity<Product>(b =>
        {
            b.ToTable("Products");

            b.HasKey(x => x.Id);
        }

        builder.Entity<Attribute>(b =>
        {
            b.ToTable("Attributes");

            b.HasKey(x => x.Id);
        }

        builder.Entity<ProductAttributeValue>(b =>
        {
            b.ToTable("ProductAttributeValues");

            b.HasKey(x => x.Id);

            //I should config ProductAttribute many-to-one manually here
        }

如何为 ProductAttribute 实体配置 Fluent API 以传递此场景?

【问题讨论】:

  • 我已经跳过了我的模型构建器配置,因为 SOF
  • “那个班级没有PK”是什么意思? EF Core 需要 PK,因此您可能已经通过 { ProductId, AttributeId } 设置了复合 PK。问题是什么?流畅地定义复合 PK 应该足以让 EF Core 在ProductAttributeValue中创建可选的阴影复合 FK@
  • @IvanStoev 因为这个项目是一个外部模块,我需要手动创建构建器配置。你能指导我如何在ProductAttributeValue 中手动创建这个阴影吗?提前致谢
  • @CS-Beginner 我仍然不明白您的模型/代码有什么问题(并且根本没有问题)。在 EF Core 中,我们更喜欢约定而不是配置,按照约定,您的模型应该没问题 - ProductAttributeValue 表应该有两个可以为空的 Guid 列形成一个 FK。如果您想与级联删除建立 必需 关系,则应在问题中说明。
  • 没错,我想要与级联删除的必需关系。抱歉错过信息。固定的。 @IvanStoev

标签: entity-framework ef-code-first entity-framework-core


【解决方案1】:

如下编写你的ProductAttribute 配置:

modelBuilder.Entity<ProductAttribute>(b =>
{
    b.ToTable("ProductAttributes");

    b.HasKey(x => new {x.ProductId, x.AttributeId});

    b.HasMany(pa => pa.Values).WithOne().IsRequired();
});

但存在可读性问题。这会将ProductAttributeProductId 和ProductAttributeAttributeId 列作为复合外键添加到shadow property 的表ProductAttributeValues。如果您想让表ProductAttributeValues 中的复合外键更具可读性,那么您可以更新您的模型ProductAttributeValue 模型类,如下所示:

public class ProductAttributeValue
{
    public Guid Id { get; set; }

    public Guid ProductId { get; set; }

    public Guid AttributeId { get; set; }

    public string Name { get; set; }
}

然后更新ProductAttribute配置如下:

modelBuilder.Entity<ProductAttribute>(b =>
{
    b.ToTable("ProductAttributes");

    b.HasKey(x => new {x.ProductId, x.AttributeId});

    b.HasMany(pa => pa.Values).WithOne().HasForeignKey(pa => new {pa.ProductId, pa.AttributeId});
});

现在表ProductAttributeValues中的复合外键将生成为ProductId和AttributeId。

谢谢。

【讨论】:

  • 我会将键更改为b.HasKey(x =&gt; new {x.ProductId, x.AttributeId, x.Id}); 以简化索引。
  • 是的!可!谢谢!让我们看看 OP 对答案的看法!
  • 注意到您一直在提议更改 OP 模型。这不是必需的。没有任何更改是强制性的。只需使用 fluent API 即可定义所需的 FK 关系。或者根本没有配置/模型更改 - OP 模型/代码有什么问题?问题和答案都不清楚。答案应该指出问题,然后提供解决方案,而不是仅仅说“这样做”。
  • 我认为@IvanStoev 的意思是,这种场景的显式配置并不是真正必要的,因为 EF Core 约定将产生类似的模型,只是使用影子属性而不是外键的显式属性.请参阅 docs.microsoft.com/en-us/ef/core/modeling/shadow-properties Shadow Properties 提供的核心增强功能是您确实可以从实体中省略外键属性,而在 EF6 中它们几乎总是必需的。
  • @DavidBrowne-Microsoft 但是会为影子属性生成数据库表中的列,不是吗?
【解决方案2】:

为了根据需要配置所需的关系并级联删除,您可以在ProductAttribute实体配置块内使用以下内容:

b.HasMany(e => e.Values)
    .WithOne()
    .IsRequired();

IsRequired 就足够了,因为按照惯例,级联删除对必需关系打开,对可选关系关闭。当然,如果您愿意,您可以添加.OnDelete(DeleteBehavior.Cascade) - 这将是多余的,但不会造成伤害。

请注意,应在一个地方配置关系。因此,请在 ProductAttribute 或 ProductAttributeValue 中执行此操作,但不要同时在两者中执行此操作(容易出错,可能导致意外的冲突或覆盖配置问题)。

为了完整起见,您可以在 ProductAttributeValue 配置中进行相同的配置(由于缺少导航属性,需要显式提供 HasOne 泛型类型参数):

b.HasOne<ProductAttribute>()
    .WithMany(e => e.Values)
    .IsRequired();

【讨论】:

  • 实际上,您提出了与我所做的相同的事情,只是做了一些解释并来自不同的实体。顺便说一句,谢谢。
  • @TanvirArjel 不同之处在于(1)我在提出解决方案之前首先澄清了 OP 问题,(2)我不提出模型更改,您的答案仍然包含无效行 .HasForeignKey(pa =&gt; new {pa.ProductId, pa.AttributeId} - a在您对未知问题的初始答案中,建议模型更改的剩余部分:) 除此之外,这不是一个想法,也不是一个提议 - 只是正确使用 OP 模型/要求所需的流式 API :)
  • 有时只是为了可读性而需要更改模型。我已经在我的答案评论部分澄清了这一点。看看如果你接受我之前的回答,那么表ProductAttributeValues 中的外键(复合)将是ProductId 和AttributeId。由于 shadow 属性,现在它将生成为ProductAttributeProductId 和ProductAttributeAttributeId。这就是为什么我个人更喜欢显式属性。你不能说我的方法是错误的!可能你喜欢不同的方法。
  • 更重要的是!并非所有提问者都是像您这样的高级专家。这就是为什么我总是试图让事情变得明确和清晰而不是隐含的。 :)
猜你喜欢
  • 2020-06-07
  • 1970-01-01
  • 1970-01-01
  • 2020-07-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多