EF Core 中带有复合键的外键
Foreign key with composite key in EF Core
我有以下 classes:
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,因此我想跳过通过 属性 引用进行导航。 Value 是一个简单的实体,但我需要它作为我的 ProductAttribute class 中的列表引用,因为您看到 class 具有复合键。但我想要一个与 ProductAttribute 之间的级联删除的必需关系
和产品属性值。
这个项目是外部模块,所以我的流畅 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 以通过此场景?
写下你的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 的 table ProductAttributeValues
中。如果你想让 table ProductAttributeValues
中的复合外键更具可读性,那么你可以更新你的模型 ProductAttributeValue
模型 class 如下:
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});
});
现在 table ProductAttributeValues
中的复合外键将生成为 ProductId
和 AttributeId
.
谢谢。
为了根据需要配置所需的关系和级联删除,您可以在ProductAttribute
实体配置块中使用以下内容:
b.HasMany(e => e.Values)
.WithOne()
.IsRequired();
IsRequired
就足够了,因为按照惯例,级联删除对于必需关系是打开的,对于可选关系是关闭的。当然,如果需要,您可以添加 .OnDelete(DeleteBehavior.Cascade)
- 这将是多余的,但不会造成伤害。
请注意,关系应该在一个地方配置。因此,在 ProductAttribute
或 ProductAttributeValue
中执行此操作,但永远不要在两者中执行(容易出错,可能会导致意外的冲突或覆盖配置问题)。
为了完整起见,以下是如何在 ProductAttributeValue
配置中进行相同的配置(由于缺少导航 属性,需要显式提供 HasOne
通用类型参数):
b.HasOne<ProductAttribute>()
.WithMany(e => e.Values)
.IsRequired();
我有以下 classes:
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,因此我想跳过通过 属性 引用进行导航。 Value 是一个简单的实体,但我需要它作为我的 ProductAttribute class 中的列表引用,因为您看到 class 具有复合键。但我想要一个与 ProductAttribute 之间的级联删除的必需关系
和产品属性值。
这个项目是外部模块,所以我的流畅 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 以通过此场景?
写下你的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 的 table ProductAttributeValues
中。如果你想让 table ProductAttributeValues
中的复合外键更具可读性,那么你可以更新你的模型 ProductAttributeValue
模型 class 如下:
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});
});
现在 table ProductAttributeValues
中的复合外键将生成为 ProductId
和 AttributeId
.
谢谢。
为了根据需要配置所需的关系和级联删除,您可以在ProductAttribute
实体配置块中使用以下内容:
b.HasMany(e => e.Values)
.WithOne()
.IsRequired();
IsRequired
就足够了,因为按照惯例,级联删除对于必需关系是打开的,对于可选关系是关闭的。当然,如果需要,您可以添加 .OnDelete(DeleteBehavior.Cascade)
- 这将是多余的,但不会造成伤害。
请注意,关系应该在一个地方配置。因此,在 ProductAttribute
或 ProductAttributeValue
中执行此操作,但永远不要在两者中执行(容易出错,可能会导致意外的冲突或覆盖配置问题)。
为了完整起见,以下是如何在 ProductAttributeValue
配置中进行相同的配置(由于缺少导航 属性,需要显式提供 HasOne
通用类型参数):
b.HasOne<ProductAttribute>()
.WithMany(e => e.Values)
.IsRequired();