ef core - 两个一对一的主键
ef core - two one to one on one principal key
得到以下数据模型:
class EntityA
{
Guid Id { get; set; }
//Property1 and Property2 will never be the same
EntityB Property1 { get; set; }
EntityB Property2 { get; set; }
}
class EntityB
{
int Id { get; set; }
EntityA EntityAProperty { get; set; }
}
但我无法配置关系。
EntityA 引用了两个不同的 EntityB。
请给我一些关于如何配置它的建议。
尝试了类似的东西(对于 属性1 和 属性2):
e.HasOne(x => x.Property1)
.WithOne()
.HasForeignKey<EntityB>(x => x.Property1Id)
.IsRequired(false);
或
e.HasOne(x => x.Property1)
.WithOne(x => x.EntityB)
.HasForeignKey<EntityB>(x => x.Property1Id)
.IsRequired(false);
第一个告诉我
The best match for foreign key properties {'Id' : int} are incompatible with the principal key {'Id' : Guid}.
第二个告诉我不能将 属性 EntityB 用于两个关系。
您的导航有问题 属性 EntityB.EntityAProperty
。对于 EntityA
中的每条记录,EntityB 中将有两条记录在 属性 中具有相同的值,因此它不能用作您的 one-to-one FK,因为 EF 会尝试放置对其的唯一约束(在 EntityB.EntityAPropertyId
列中)。
我能想到的所有解决方案都需要更改您的模型:
解决方案 1: 删除有问题的导航 属性 EntityB.EntityAProperty
。使 EntityB
成为关系的主要部分,而不是 EntityA
(根据模型的语义,这可能不是acceptable)。这意味着在物理 table 中,您将拥有这些列:
EntityA:
Id (PK)
Property1Id (FK on EntityB.Id, unique constraint)
Property2Id (FK on EntityB.Id, unique constraint)
EntityB:
Id (PK)
这不能保证完整的参照完整性,当 creating/updating EntityA
实例时,您必须在代码中添加一些检查,特别是检查 Property1Id
值是否未在另一个实例中使用实体的 Property2Id
,反之亦然。此外,检查 Entity1
的同一实例中两个属性的值是否不同。
您实体的变化:
class EntityA
{
Guid Id { get; set; }
EntityB Property1 { get; set; }
EntityB Property2 { get; set; }
int Property1Id { get; set; } //New
int Property2Id { get; set; } //New
}
class EntityB
{
int Id { get; set; }
//Removed EntityA property
}
OnModelCreating
中流畅的 API 映射:
modelBuilder.Entity<EntityA>()
.HasOne<EntityB>(a => a.Property1)
.WithOne() //No navigation property in EntityB
.HasForeignKey<EntityA>(a => a.Property1Id);
modelBuilder.Entity<EntityA>()
.HasOne<EntityB>(a => a.Property2)
.WithOne() //No navigation property in EntityB
.HasForeignKey<EntityA>(a => a.Property2Id);
方案二:反过来说:主体实体是EntityA
,所以FK列和唯一约束放在EntityB
table。这似乎是语义上最好的实现,但有一个问题:请记住,由于违反了唯一约束,您不能仅使用 EntityB
中的一列作为 EntityA
中的 ID(您将有两条记录相同 EntityA.Id
)。因此,您必须在 EntityB
中添加两列,一列用于 Property1
,一列用于 Property2
。由于 EntityB
中的记录仅同时用于其中一个属性,因此每条记录中的两列之一将为空。看起来确实有点勉强,而且 FK 列稀疏,50% 的记录中有 null 值。
这些将是 tables:
EntityA:
Id (PK)
EntityB:
Id (PK)
EntityAProperty1Id (FK on EntityA.Id, unique constraint)
EntityAProperty2Id (FK on EntityA.Id, unique constraint)
此解决方案要求您在代码中执行与前一个解决方案相同的检查。
您实体的变化:
class EntityA
{
Guid Id { get; set; }
EntityB Property1 { get; set; }
EntityB Property2 { get; set; }
}
class EntityB
{
int Id { get; set; }
//Removed EntityA property
EntityA EntityAProperty1 { get; set; } //New
EntityA EntityAProperty2 { get; set; } //New
int EntityAProperty1Id { get; set; } //New
int EntityAProperty2Id { get; set; } //New
}
OnModelCreating
中流畅的 API 映射:
modelBuilder.Entity<EntityB>()
.HasOne<EntityA>(b => b.EntityAProperty1)
.WithOne(a => a.Property1)
.HasForeignKey<EntityB>(b => b.EntityAProperty1Id);
modelBuilder.Entity<EntityB>()
.HasOne<EntityA>(b => b.EntityAProperty2)
.WithOne(a => a.Property2)
.HasForeignKey<EntityB>(b => b.EntityAProperty2Id);
考虑一个不同的解决方案:解决方案 3: 放弃你的 one-to-one 关系并使用恰好有两个值的 one-to-many .定义一个集合 属性 并按位置处理其值,而不是具有两个属性 Property1
和 Property2
:第一个是 Property1
,第二个是 Property2
.我不知道这在功能上是否有意义,也许每个 属性 的用途完全不同,将它们放在一个列表中没有意义,但以这种方式处理它们会更容易。您可以保持 public 属性不变,并使用由 EF 映射并保存到数据库 tables 的底层私有字段。您会告诉 EF 忽略 public 属性并使用它们的 get
和 set
方法来使用底层列表。有了这个,您可以继续使用您的导航 属性 EntityB.EntityAProperty
。您仍然需要编写一些代码来检查
列表中只有两个值。
另一个解决方案,有点off-topic:解决方案4:考虑在你的EntityB
实体中使用继承。您定义了两个 类 EntityB1
和 EntityB2
,它们只扩展了父 EntityB
而没有添加任何 属性。您使用派生类型而不是父类型定义 EntityA
中的属性:
class EntityB
{
int Id { get; set; }
}
class EntityB1 : EntityB {}
class EntityB2 : EntityB {}
class EntityA
{
Guid Id { get; set; }
EntityB1 Property1 { get; set; }
EntityB2 Property2 { get; set; }
}
由于 Property1
和 Property2
具有不同的数据类型,您不再与同一实体有两种关系。 EF 应该能够按照惯例解决所有问题。可能您不需要添加明确的流畅 API 映射。
我会推荐解决方案 1,但前提是 EntityB
是关系中的主要部分在语义上有意义。否则我会推荐解决方案 4。
有关 EF Core 将 one-to-one 关系映射到物理 table 的方式的更多信息,请参见 here。仅在依赖 table 中添加 FK 列。没有列添加到主体 table。与 one-to-many 关系相同,只是为了确保关系是 one-to-one 而不是 one-to-many,还在该 FK 列上创建一个唯一约束。
EF 按照惯例为两个实体之间的 one-to-one 关系执行此操作,这两个实体在关系的两侧都有导航 属性(在解决方案 2 场景中就是这种情况,但不能用于解决方案 1)。
This page 还包含与您的问题相关的信息。
抱歉,这个答案比我预期的要长。我有点忘乎所以了。
得到以下数据模型:
class EntityA
{
Guid Id { get; set; }
//Property1 and Property2 will never be the same
EntityB Property1 { get; set; }
EntityB Property2 { get; set; }
}
class EntityB
{
int Id { get; set; }
EntityA EntityAProperty { get; set; }
}
但我无法配置关系。 EntityA 引用了两个不同的 EntityB。 请给我一些关于如何配置它的建议。
尝试了类似的东西(对于 属性1 和 属性2):
e.HasOne(x => x.Property1)
.WithOne()
.HasForeignKey<EntityB>(x => x.Property1Id)
.IsRequired(false);
或
e.HasOne(x => x.Property1)
.WithOne(x => x.EntityB)
.HasForeignKey<EntityB>(x => x.Property1Id)
.IsRequired(false);
第一个告诉我
The best match for foreign key properties {'Id' : int} are incompatible with the principal key {'Id' : Guid}.
第二个告诉我不能将 属性 EntityB 用于两个关系。
您的导航有问题 属性 EntityB.EntityAProperty
。对于 EntityA
中的每条记录,EntityB 中将有两条记录在 属性 中具有相同的值,因此它不能用作您的 one-to-one FK,因为 EF 会尝试放置对其的唯一约束(在 EntityB.EntityAPropertyId
列中)。
我能想到的所有解决方案都需要更改您的模型:
解决方案 1: 删除有问题的导航 属性
EntityB.EntityAProperty
。使EntityB
成为关系的主要部分,而不是EntityA
(根据模型的语义,这可能不是acceptable)。这意味着在物理 table 中,您将拥有这些列:EntityA: Id (PK) Property1Id (FK on EntityB.Id, unique constraint) Property2Id (FK on EntityB.Id, unique constraint) EntityB: Id (PK)
这不能保证完整的参照完整性,当 creating/updating
EntityA
实例时,您必须在代码中添加一些检查,特别是检查Property1Id
值是否未在另一个实例中使用实体的Property2Id
,反之亦然。此外,检查Entity1
的同一实例中两个属性的值是否不同。您实体的变化:
class EntityA { Guid Id { get; set; } EntityB Property1 { get; set; } EntityB Property2 { get; set; } int Property1Id { get; set; } //New int Property2Id { get; set; } //New } class EntityB { int Id { get; set; } //Removed EntityA property }
OnModelCreating
中流畅的 API 映射:modelBuilder.Entity<EntityA>() .HasOne<EntityB>(a => a.Property1) .WithOne() //No navigation property in EntityB .HasForeignKey<EntityA>(a => a.Property1Id); modelBuilder.Entity<EntityA>() .HasOne<EntityB>(a => a.Property2) .WithOne() //No navigation property in EntityB .HasForeignKey<EntityA>(a => a.Property2Id);
方案二:反过来说:主体实体是
EntityA
,所以FK列和唯一约束放在EntityB
table。这似乎是语义上最好的实现,但有一个问题:请记住,由于违反了唯一约束,您不能仅使用EntityB
中的一列作为EntityA
中的 ID(您将有两条记录相同EntityA.Id
)。因此,您必须在EntityB
中添加两列,一列用于Property1
,一列用于Property2
。由于EntityB
中的记录仅同时用于其中一个属性,因此每条记录中的两列之一将为空。看起来确实有点勉强,而且 FK 列稀疏,50% 的记录中有 null 值。这些将是 tables:
EntityA: Id (PK) EntityB: Id (PK) EntityAProperty1Id (FK on EntityA.Id, unique constraint) EntityAProperty2Id (FK on EntityA.Id, unique constraint)
此解决方案要求您在代码中执行与前一个解决方案相同的检查。
您实体的变化:
class EntityA { Guid Id { get; set; } EntityB Property1 { get; set; } EntityB Property2 { get; set; } } class EntityB { int Id { get; set; } //Removed EntityA property EntityA EntityAProperty1 { get; set; } //New EntityA EntityAProperty2 { get; set; } //New int EntityAProperty1Id { get; set; } //New int EntityAProperty2Id { get; set; } //New }
OnModelCreating
中流畅的 API 映射:modelBuilder.Entity<EntityB>() .HasOne<EntityA>(b => b.EntityAProperty1) .WithOne(a => a.Property1) .HasForeignKey<EntityB>(b => b.EntityAProperty1Id); modelBuilder.Entity<EntityB>() .HasOne<EntityA>(b => b.EntityAProperty2) .WithOne(a => a.Property2) .HasForeignKey<EntityB>(b => b.EntityAProperty2Id);
考虑一个不同的解决方案:解决方案 3: 放弃你的 one-to-one 关系并使用恰好有两个值的 one-to-many .定义一个集合 属性 并按位置处理其值,而不是具有两个属性
Property1
和Property2
:第一个是Property1
,第二个是Property2
.我不知道这在功能上是否有意义,也许每个 属性 的用途完全不同,将它们放在一个列表中没有意义,但以这种方式处理它们会更容易。您可以保持 public 属性不变,并使用由 EF 映射并保存到数据库 tables 的底层私有字段。您会告诉 EF 忽略 public 属性并使用它们的get
和set
方法来使用底层列表。有了这个,您可以继续使用您的导航 属性EntityB.EntityAProperty
。您仍然需要编写一些代码来检查 列表中只有两个值。另一个解决方案,有点off-topic:解决方案4:考虑在你的
EntityB
实体中使用继承。您定义了两个 类EntityB1
和EntityB2
,它们只扩展了父EntityB
而没有添加任何 属性。您使用派生类型而不是父类型定义EntityA
中的属性:class EntityB { int Id { get; set; } } class EntityB1 : EntityB {} class EntityB2 : EntityB {} class EntityA { Guid Id { get; set; } EntityB1 Property1 { get; set; } EntityB2 Property2 { get; set; } }
由于
Property1
和Property2
具有不同的数据类型,您不再与同一实体有两种关系。 EF 应该能够按照惯例解决所有问题。可能您不需要添加明确的流畅 API 映射。
我会推荐解决方案 1,但前提是 EntityB
是关系中的主要部分在语义上有意义。否则我会推荐解决方案 4。
有关 EF Core 将 one-to-one 关系映射到物理 table 的方式的更多信息,请参见 here。仅在依赖 table 中添加 FK 列。没有列添加到主体 table。与 one-to-many 关系相同,只是为了确保关系是 one-to-one 而不是 one-to-many,还在该 FK 列上创建一个唯一约束。
EF 按照惯例为两个实体之间的 one-to-one 关系执行此操作,这两个实体在关系的两侧都有导航 属性(在解决方案 2 场景中就是这种情况,但不能用于解决方案 1)。
This page 还包含与您的问题相关的信息。
抱歉,这个答案比我预期的要长。我有点忘乎所以了。