实体与实体框架之间的复杂关系

时间:2019-06-26 14:23:11

标签: c# entity-framework

也许是重复的,但是我找不到这样的话题。

我正在使用Entity Framework,并且数据库中有两个表:

public class A
{
    public virtual B B1 { get; set; }
    public virtual B B2 { get; set; }
}

public class B
{
    public virtual A A1 { get; set; }
}

B1和A1或B2和A1之间没有关系。就像3种单向关系。您如何在Entity Framework中做到这一点?

我收到此错误:

  

保存不公开外键属性的实体时发生错误

有人知道如何处理吗?

预先感谢

2 个答案:

答案 0 :(得分:1)

如果A1表包含指向同一B表的B1_Id和B2_Id,但是您希望B记录仅与A关联一次,那么据我所知映射是如何进行的,那不是可能。没有什么能阻止您将相同的B记录与B1或B2关联到不同的A记录上,那么B的A引用将如何合法解决?实体反映了数据状态,因此,如果数据模式中的数据合法/非法,则该实体也是如此。在A上拥有B ID的比率为1:1,或者为1:1,但是要在A上与B共享2个FK,则需要DB支持B的备用FK,而事实并非如此。

您可以让A持有B中2条记录的ID,但是B无法将单个引用映射回A,因此必须伪造它。

public class A
{
   public int AId { get; set; }

   public virtual B B1 { get; set; }
   public virtual B B2 { get; set; }
}

public class B
{
   public int BId { get; set; }

   public virtual ICollection<A> RawA1 { get; private set; } = new List<A>();
   public virtual ICollection<A> RawA2 { get; private set; } = new List<A>();

   [NotMapped]
   public A A
   {
      get { return RawA1.SingleOrDefault() ?? RawA2.SingleOrDefault(); }
   }
}

警告是您不能在任何去EF的Linq表达式中使用B.A,因为就EF而言,它不知道该属性。

另一种方法是在AId驻留在B上的情况下使用1-to-many,然后处理限制A侧集合上允许的操作。问题在于,除非由B记录上的属性明确确定,否则B1和B2的顺序将不可靠。 A可以公开集合中的B1和B2未映射属性,但是必须确定要为每个元素考虑的两个元素中的哪个,或者简单地公开集合。最终,您的业务逻辑将必须控制一个事实,即A只能有2个B引用,因为数据库无法强制执行该引用,也不能以2对1的关系使B可以回到A。

答案 1 :(得分:1)

由于您没有指明正在使用哪个EF版本,因此让我们看一下两个最新版本EF 6.2.0和EF-core 2.2.4。

使用EF6,非常简单。映射...

modelBuilder.Entity<A>().HasRequired(a => a.B1).WithMany();
modelBuilder.Entity<A>().HasRequired(a => a.B2).WithMany().WillCascadeOnDelete(false);
modelBuilder.Entity<B>().HasRequired(b => b.A1).WithMany().WillCascadeOnDelete(false);

...产生以下数据库模型(忽略索引):

CREATE TABLE [dbo].[A] (
    [ID] [int] NOT NULL IDENTITY,
    [B1_ID] [int] NOT NULL,
    [B2_ID] [int] NOT NULL,
    CONSTRAINT [PK_dbo.A] PRIMARY KEY ([ID])
)
CREATE TABLE [dbo].[B] (
    [ID] [int] NOT NULL IDENTITY,
    [A1_ID] [int] NOT NULL,
    CONSTRAINT [PK_dbo.B] PRIMARY KEY ([ID])
)

....其中带有_的字段是外键,其中之一可以级联删除。

在使用ef-core的情况下,它看起来似乎并不那么简单,甚至没有故障。第一个冲动是EF6等效项:

modelBuilder.Entity<A>().HasOne(a => a.B1).WithMany();
modelBuilder.Entity<A>().HasOne(a => a.B2).WithMany();
modelBuilder.Entity<B>().HasOne(b => b.A1).WithMany();

但是生成的模型不是人们所期望的:

  CREATE TABLE [B] (
      [ID] int NOT NULL IDENTITY,
      [A1ID] int NULL,
      CONSTRAINT [PK_B] PRIMARY KEY ([ID])
  );
  CREATE TABLE [A] (
      [ID] int NOT NULL,
      [B1ID] int NULL,
      CONSTRAINT [PK_A] PRIMARY KEY ([ID]),
      CONSTRAINT [FK_A_B_B1ID] FOREIGN KEY ([B1ID]) REFERENCES [B] ([ID]) ON DELETE NO ACTION,
      CONSTRAINT [FK_A_B_ID] FOREIGN KEY ([ID]) REFERENCES [B] ([ID]) ON DELETE CASCADE
  );
  ALTER TABLE [B] ADD CONSTRAINT [FK_B_A_A1ID] FOREIGN KEY ([A1ID]) REFERENCES [A] ([ID]) ON DELETE NO ACTION;

A-B关联之一被解释为1:1。我认为这是一个错误。 WithMany指令不应留下任何解释的空间。两个看似相同的映射会产生完全不同的数据库关系。那不对。

也就是说,通过命名FK列很容易(但不是必须)使EF处于正确的轨道:

modelBuilder.Entity<A>().HasOne(a => a.B1).WithMany().HasForeignKey("B1_ID")
    .IsRequired();
modelBuilder.Entity<A>().HasOne(a => a.B2).WithMany().HasForeignKey("B2_ID")
    .IsRequired().OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<B>().HasOne(b => b.A1).WithMany().HasForeignKey("A1_ID")
    .IsRequired().OnDelete(DeleteBehavior.Restrict);

产生(忽略索引):

  CREATE TABLE [B] (
      [ID] int NOT NULL IDENTITY,
      [A1_ID] int NOT NULL,
      CONSTRAINT [PK_B] PRIMARY KEY ([ID])
  );
  CREATE TABLE [A] (
      [ID] int NOT NULL IDENTITY,
      [B1_ID] int NOT NULL,
      [B2_ID] int NOT NULL,
      CONSTRAINT [PK_A] PRIMARY KEY ([ID]),
      CONSTRAINT [FK_A_B_B1_ID] FOREIGN KEY ([B1_ID]) REFERENCES [B] ([ID]) ON DELETE CASCADE,
      CONSTRAINT [FK_A_B_B2_ID] FOREIGN KEY ([B2_ID]) REFERENCES [B] ([ID]) ON DELETE NO ACTION
  );
  ALTER TABLE [B] ADD CONSTRAINT [FK_B_A_A1_ID] FOREIGN KEY ([A1_ID]) REFERENCES [A] ([ID]) ON DELETE NO ACTION;

请注意,必须明确设置必需的外键字段(如果有)。好吧,这只是一个实现细节。