ef核心 - 两个一对一的主键

时间:2018-03-11 11:16:24

标签: entity-framework entity-relationship

获得以下数据模型:

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用于两个关系。

1 个答案:

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

  • 另一个解决方案,有点偏离主题:解决方案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将一对一关系映射到物理表的方式的更多信息here。仅在从属表中添加FK列。没有列添加到主表。与一对多关系相同,只是为了确保关系是一对一而不是一对多,在该FK列上也创建了一个唯一约束。

EF按照约定对两个实体之间的一对一关系进行了这种关系,这两个实体在关系的两侧都有导航属性(解决方案2中的情况就是这种情况,但不能在解决方案1中使用)。

This page还包含与您的问题相关的信息。

对不起,这个答案比我想要的要长。我有点沮丧。