繁体   English   中英

具有实体框架的实体之间的复杂关系

[英]Complex relationship between entities with 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中做到这一点?

我收到此错误:

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

有谁知道如何处理这个?

提前致谢

如果A1表包含指向同一个B表的B1_Id和B2_Id,但是您希望B记录只与A关联一次,那么就我所知的映射方式而言,这是不可能的。 没有什么可以阻止你在不同的A记录中将相同的B记录与B1或B2相关联,那么B的A参考将如何合法地解决? 实体反映数据状态,因此如果它是数据模式的合法/非法,则对于实体来说是相同的。 在A上有B ID会形成多对1,或者可以形成一对一,但是要在A上分享2x FK到B,你需要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表达中使用BA,因为就EF而言,它并不知道该属性。

替代方案是使用Ad存在于B上的1对多,然后处理限制A侧的集合的允许操作。 问题是B1和B2订单不可靠,除非B记录上的属性明确可确定。 A可以从集合中公开B1和B2未映射的属性,但必须确定要为每个元素考虑哪两个元素,或者只是公开集合。 最终,你的业务逻辑必须控制一个事实,即A应该只有2个B引用,因为数据库不能强制执行,也不是2到1的关系,其中B可以回到A.

由于您没有指出您正在使用的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;

其中一个AB关联被解释为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;

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM