多对多的同一实体集合,具有双向关系

假设我有一个widget实体,我想跟踪与每个小部件相邻的其他小部件。 如果第一小部件与第二小部件相邻,则反之亦然 – 第二小部件与第一小部件相邻。

理想情况下,我会在实体上有一个集合,并且可以流畅地配置实体以实现这种关系。

public class Widget { // ... public virtual ICollection Adjacent { get; set; } } 

但是,当我试着……

 modelBuilder.Entity .HasMany(w => w.Adjacent) .WithMany(w => w.Adjacent); 

……entity framework根本不喜欢它。

在’Widget’类型上声明的导航属性’Adjacent’不能与其自身相反。

有没有办法配置实现此目标的实体,或者我将不会创建父/子集合导航属性或单独的关系容器?

您需要在窗口小部件中引入另一个集合,例如。

 public virtual ICollection AdjacentFrom { get; set; } public virtual ICollection AdjacentTo { get; set; } 

默认情况下,没有fluent-api配置,此代码仅在数据库中创建WidgetWidgets的容器表,其中包含两列Widget_IdWidget_Id1


但是你需要保持一致,只使用其中一个集合来建立相邻的关系。 如果使用AdjacentTo集合建立相邻关系。

 widget1.AdjacentTo.Add(widget2); 

保存后widget1.AdjacentTo将具有widget2widget2.AdjacentFrom将具有widget1

 Widget_Id Widget_Id1 2 1 

但是,如果再次使用AdjacentFrom集合输入以建立相邻关系。

 widget1.AdjacentFrom.Add(widget2); 

保存后widget1.AdjacentFromwidget1.AdjacentTo将有widget2widget2

 Widget_Id Widget_Id1 2 1 1 2 

复合唯一键不能阻止插入第二条记录,因为第二条记录不被视为重复行。 但是通过添加检查约束有一种解决方法,您可以在迁移中添加此约束。

 Sql("alter table WidgetWidgets add constraint CK_Duplicate_Widget check (Widget_Id > Widget_Id1)"); 

要选择所有相邻的,您可以添加另一个集合,例如。

 [NotMapped] public ICollection Adjacent { get { return (AdjacentFrom ?? new Widget[0]).Union((AdjacentTo ?? new Widget[0])).Distinct().ToArray(); } } 

添加检查约束后,您可以使用此扩展来添加或删除相邻。

 public static class WidgetDbExtension { public static void AddAdjacent(this Widget widget1, Widget widget2) { if (widget1.Id < widget2.Id) { widget1.AdjacentTo.Add(widget2); } else { widget2.AdjacentTo.Add(widget1); } } public static void RemoveAdjacent(this Widget widget1, Widget widget2) { if (widget1.Id < widget2.Id) { widget1.AdjacentTo.Remove(widget2); } else { widget2.AdjacentTo.Remove(widget1); } } }