简体   繁体   English

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

[英]Complex relationship between entities with Entity Framework

Maybe it is a duplicate but I couldn't find any topic like this. 也许这是重复但我找不到这样的话题。

I am using Entity Framework and have two tables in my database: 我正在使用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; }
}

And there is no relation between B1 and A1 or B2 and A1. B1和A1或B2和A1之间没有关系。 It is like 3 one way relation. 它就像3个单向关系。 How can you do that in Entity Framework? 你怎么能在Entity Framework中做到这一点?

I am getting this error: 我收到此错误:

An error occurred while saving entities that do not expose foreign key properties for their relationships 保存不公开其关系的外键属性的实体时发生错误

Does anyone know how to handle this ? 有谁知道如何处理这个?

Thanks in advance 提前致谢

If the A1 table contains the B1_Id and the B2_Id pointing at the same B table, but you expect a B record to only ever be associated to an A once, then as far as I know about how mapping goes, that is not possible. 如果A1表包含指向同一个B表的B1_Id和B2_Id,但是您希望B记录只与A关联一次,那么就我所知的映射方式而言,这是不可能的。 There would be nothing stopping you from associating the same B record as B1 or B2 on different A records, so how would B's A reference ever resolve legally? 没有什么可以阻止你在不同的A记录中将相同的B记录与B1或B2相关联,那么B的A参考将如何合法地解决? Entities reflect data state, so if it's legal/illegal from a data schema, it's the same for the entity. 实体反映数据状态,因此如果它是数据模式的合法/非法,则对于实体来说是相同的。 Having B Ids on A forms a many to 1, or can form a 1 to one, but to share 2x FKs to B on A, you would need the DB to support alternate FKs for B, which it doesn't. 在A上有B ID会形成多对1,或者可以形成一对一,但是要在A上分享2x FK到B,你需要DB支持B的备用FK,而不是。

You can have A hold the IDs for 2 records in B, but B cannot map a single reference back to A, it has to fake it. 您可以让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(); }
   }
}

The caveat being you can't use BA in any Linq expression going to EF because as far as EF is concerned, it doesn't know about that property. 需要注意的是,你不能在任何进入EF的Linq表达中使用BA,因为就EF而言,它并不知道该属性。

The alternative is using a 1-to-many where the AId lives on B, and then dealing with restricting the allowable operations against the collection on the A side. 替代方案是使用Ad存在于B上的1对多,然后处理限制A侧的集合的允许操作。 The issue there would be that the B1 & B2 order would not be reliable unless explicitly determinable by properties on the B record. 问题是B1和B2订单不可靠,除非B记录上的属性明确可确定。 A can expose a B1 and B2 unmapped property from the collection, but would have to determine which of the two elements to consider for each, or simply expose the collection. A可以从集合中公开B1和B2未映射的属性,但必须确定要为每个元素考虑哪两个元素,或者只是公开集合。 Ultimately it is your business logic that would have to control the fact that an A should only ever have 2 B references since a database cannot enforce that, nor a 2 to 1 relationship where B could get back to A. 最终,你的业务逻辑必须控制一个事实,即A应该只有2个B引用,因为数据库不能强制执行,也不是2到1的关系,其中B可以回到A.

Since you don't indicate in which EF version you're using let's look at the two current versions, EF 6.2.0 and EF-core 2.2.4. 由于您没有指出您正在使用的EF版本,因此请查看两个当前版本,即EF 6.2.0和EF-core 2.2.4。

With EF6, it's easy. 使用EF6,很容易。 The mapping... 映射......

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

...produces the following database model (ignoring indexes): ...生成以下数据库模型(忽略索引):

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

....in which the fields with _ are foreign keys, one of which can have cascaded delete. ....其中带_的字段是外键,其中一个可以有级联删除。

With ef-core, it's not so straightforward, even buggy, seemingly. 有了ef-core,它看起来并不那么简单,甚至是错误。 The first impulse is the EF6 equivalent: 第一个冲动是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();

But the generated model isn't what one would expect: 但生成的模型并不是人们所期望的:

  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;

One of the AB associations is interpreted as 1:1. 其中一个AB关联被解释为1:1。 In my opinion that's a bug. 在我看来,这是一个错误。 The WithMany instruction shouldn't leave any room for interpretation. WithMany指令不应留下任何解释空间。 Two seemingly identical mappings produce quite different database relationships. 两个看似相同的映射产生完全不同的数据库关系。 That can't be right. 这不可能是正确的。

That said, it's easy (but shouldn't be necessary) to get EF on the right track by naming the FK columns: 也就是说,通过命名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);

Producing (ignoring indexes): 生成(忽略索引):

  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;

Note that the foreign key fields must be set required explicitly (if they are). 请注意,必须明确设置所需的外键字段(如果是)。 Well, that's just an implementation detail. 嗯,这只是一个实现细节。

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

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