获得以下数据模型:
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。 请给我一些如何配置它的建议。
尝试过(对于property1和property2):
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);
第一个告诉我
外国关键字资产的最佳匹配{&#39; Id&#39; :int}与主键不兼容{&#39; Id&#39; :Guid}。
第二个告诉我,我不能将属性EntityB用于两个关系。
答案 0 :(得分:2)
导航属性EntityB.EntityAProperty
存在问题。对于EntityA
中的每条记录,EntityB中将有两条记录在该属性中具有相同的值,因此它不能用作您的一对一FK,因为EF会尝试对其进行唯一约束它(在EntityB.EntityAPropertyId
列中)。
我能想到的所有解决方案都需要改变你的模型:
解决方案1:删除有问题的导航属性EntityB.EntityAProperty
。使EntityB
成为关系的主要部分,而不是EntityA
(根据模型的语义,这可能是不可接受的)。这意味着在物理表中您将拥有以下列:
EntityA:
Id (PK)
Property1Id (FK on EntityB.Id, unique constraint)
Property2Id (FK on EntityB.Id, unique constraint)
EntityB:
Id (PK)
这不能保证完整的引用完整性,在创建/更新EntityA
实例时,您必须在代码中添加一些检查,特别是检查在另一个实体的{{1}中未使用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
}
中的流畅API映射:
OnModelCreating
解决方案2:反过来说:主体实体是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);
,因此FK列和唯一约束放在EntityA
表中。这似乎是语义上最好的实现,但有一个问题:请记住,EntityB
中的EntityB
中只有一列可用于EntityA
中的ID,因为唯一的约束违规(您将有两条记录)相同的EntityA.Id
)。因此,您必须在EntityB
中添加两列,一列用于Property1
,另一列用于Property2
。由于EntityB
中的记录仅在其中一个属性中同时使用,因此每个记录中两列中的一列将为空。它实际上看起来有点强迫,FK列稀疏,50%的记录中都有空值。
这些是表格:
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:放弃您的一对一关系,并使用一对多的两个值。不是拥有两个属性Property1
和Property2
,而是定义一个集合属性并按位置处理它的值:第一个是Property1
,第二个是Property2
。我不知道这是否在功能上有意义,也许每个属性的使用完全不同,并将它们放在一个列表中是没有意义的,但用这种方式处理它们会更容易。您可以保留公共属性,并使用底层私有字段,该字段由EF映射并保存到数据库表中。您可以告诉EF忽略公共属性,并使用他们的get
和set
方法来使用未定列表。有了这个,您可以继续使用导航属性EntityB.EntityAProperty
。您仍然需要编写一些代码来检查它
列表中只有两个值。
另一个解决方案,有点偏离主题:解决方案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将一对一关系映射到物理表的方式的更多信息here。仅在从属表中添加FK列。没有列添加到主表。与一对多关系相同,只是为了确保关系是一对一而不是一对多,在该FK列上也创建了一个唯一约束。
EF按照约定对两个实体之间的一对一关系进行了这种关系,这两个实体在关系的两侧都有导航属性(解决方案2中的情况就是这种情况,但不能在解决方案1中使用)。
This page还包含与您的问题相关的信息。
对不起,这个答案比我想要的要长。我有点沮丧。