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 .定义一个集合 属性 并按位置处理其值,而不是具有两个属性 Property1Property2:第一个是 Property1,第二个是 Property2.我不知道这在功能上是否有意义,也许每个 属性 的用途完全不同,将它们放在一个列表中没有意义,但以这种方式处理它们会更容易。您可以保持 public 属性不变,并使用由 EF 映射并保存到数据库 tables 的底层私有字段。您会告诉 EF 忽略 public 属性并使用它们的 getset 方法来使用底层列表。有了这个,您可以继续使用您的导航 属性 EntityB.EntityAProperty。您仍然需要编写一些代码来检查 列表中只有两个值。

  • 另一个解决方案,有点off-topic:解决方案4:考虑在你的EntityB实体中使用继承。您定义了两个 类 EntityB1EntityB2,它们只扩展了父​​ 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; }
    }
    

    由于 Property1Property2 具有不同的数据类型,您不再与同一实体有两种关系。 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 还包含与您的问题相关的信息。

抱歉,这个答案比我预期的要长。我有点忘乎所以了。