代码之家  ›  专栏  ›  技术社区  ›  Florian

ef核心-两个一对一的主密钥

  •  3
  • Florian  · 技术社区  · 6 年前

    已获取以下数据模型:

    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);
    

    第一个告诉我

    外键属性{'Id':int}的最佳匹配与主键{'Id':Guid}不兼容。

    第二个告诉我不能将属性EntityB用于两个关系。

    1 回复  |  直到 6 年前
        1
  •  8
  •   Diana    6 年前

    您的导航属性有问题 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 还包含与您的问题相关的信息。

    对不起,这个答案比我想的要长。我有点忘乎所以了。