Коллекция "многие ко многим" одного и того же объекта с двусторонними отношениями

Предположим, у меня есть объект виджета, и я хочу отслеживать другие виджеты, которые соседствуют с каждым. Если первый виджет примыкает ко второму виджету, то верно и обратное-второй примыкает к первому.

В идеале у меня была бы одна коллекция на сущности и я мог бы свободно настроить сущность для такого рода отношений.

public class Widget
{
    // ...

    public virtual ICollection<Widget> Adjacent { get; set; }
}

однако, когда я пытаюсь это...

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

...Entity Framework это не нравится все.

свойство навигации "смежный", объявленное в типе "виджет", не может быть обратным самому себе.

есть ли способ настроить сущность, которая достигает этой цели, или я собираюсь застрять, создавая свойства навигации родительской/дочерней коллекции или отдельные контейнеры отношений?

1 ответов


вам нужно ввести другую коллекцию внутри виджета, что-то вроде.

public virtual ICollection<Widget> AdjacentFrom { get; set; }
public virtual ICollection<Widget> AdjacentTo { get; set; }

по умолчанию без конфигурации fluent-api этот код только создаст таблицу контейнера WidgetWidgets в базе данных, которая содержит две колонки Widget_Id и Widget_Id1.


но вам нужно быть последовательным, чтобы использовать только одну коллекцию для создания смежных отношений. Если вы используете AdjacentTo коллекция для создания смежного отношение.

widget1.AdjacentTo.Add(widget2);

после сохранения widget1.AdjacentTo будет widget2 и widget2.AdjacentFrom будет widget1.

Widget_Id   Widget_Id1
    2           1

но если вы снова введете с AdjacentFrom коллекция для создания смежных отношений.

widget1.AdjacentFrom.Add(widget2);

после сохранения widget1.AdjacentFrom и widget1.AdjacentTo будет widget2. То же самое происходит с widget2.

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