您的导航属性有问题
EntityB.EntityAProperty
。对于中的每个记录
EntityA
EntityB中有两条记录在该属性中具有相同的值,因此不能将其用作一对一FK,因为EF会尝试对其施加唯一约束(在列中
EntityB.EntityAPropertyId
)。
我能想到的所有解决方案都需要更改您的模型:
-
解决方案1:
删除有问题的导航属性
实体B。实体属性
。制作
EntityB
关系的主体,而不是
实体A
(根据模型的语义,这可能是不可接受的)。这意味着在物理表中有以下列:
EntityA:
Id (PK)
Property1Id (FK on EntityB.Id, unique constraint)
Property2Id (FK on EntityB.Id, unique constraint)
EntityB:
Id (PK)
这并不能保证完全的引用完整性,您必须在创建/更新代码时在代码中添加一些检查
实体A
实例,具体检查
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
}
中的fluent API映射
OnModelCreating
:
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);
-
解决方案2:
另一种方法是:主体是
实体A
,因此FK列和唯一约束放置在
实体B
桌子从语义上讲,这似乎是最好的实现,但有一个问题:请记住,在
实体B
对于中的ID
实体A
由于唯一约束冲突(您将有两条记录具有相同的
EntityA.Id
)。因此,您必须在中添加两列
实体B
,一个用于
Property1
一个用于
Property2
。作为中的记录
实体B
同时仅在其中一个属性中使用,则每个记录中的两列之一将为null。它看起来确实有点强制,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
}
中的fluent API映射
OnModelCreating
:
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:
放弃你的一对一关系,使用一对多的两个值。而不是有两个属性
物业1
和
物业2
,定义集合属性并按位置处理其值:第一个是
物业1
,第二个是
物业2
。我不知道这在功能上是否有意义,也许每个属性的用途完全不同,将它们放在一个列表中没有意义,但这样处理它们会更容易。您可以保持公共属性不变,并使用底层私有字段,该字段由EF映射并保存到数据库表中。您可以告诉EF忽略公共属性并使用其
get
和
set
方法来使用不可靠列表。有了它,您可以继续使用导航属性
实体B。实体属性
。您仍然需要编写一些代码来检查
列表中只有两个值。
-
另一个有点离题的解决方案:
解决方案4:
考虑在
实体B
实体您定义了两个类
EntityB1
和
EntityB2
,仅扩展父级
实体B
不添加任何属性。在中定义属性
实体A
使用派生类型,而不是父类型:
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; }
}
像
物业1
和
物业2
具有不同的数据类型您不再具有与同一实体的两个关系。EF应该能够按照惯例解决所有问题。您可能不需要添加显式的fluent API映射。
我建议使用解决方案1,但前提是从语义上讲
实体B
是关系的主体。否则,我会推荐解决方案4。
可以找到有关EF Core将一对一关系映射到物理表的方式的更多信息
here
。FK列仅添加到从属表中。未向主体表中添加任何列。对一对多关系执行的操作与此相同,只是为了确保关系是一对一而不是一对多,还将在该FK列上创建唯一约束。
EF按照惯例对在关系两侧都具有导航属性的两个实体之间的一对一关系执行此操作(在解决方案2 scenary中是这种情况,但不能在解决方案1中使用)。
This page
还包含与您的问题相关的信息。
对不起,这个答案比我想的要长。我有点忘乎所以了。