Отношение многие-ко-многим (many-to-many) между таблицами

176

Entity Framework поддерживает реализацию отношения многие-ко-многим (many-to-many) между таблицами в базе данных. Такая связь в реляционных базах данных обеспечивается за счет использования промежуточной таблицы, с которой связываются две другие таблицы через отношение один-ко-многим. В результате между этими двумя таблицами образуется связь многие-ко-многим. Фактически в такой связи нет главной и зависимой таблицы, как мы описывали это для связей один-к-одному и один-ко-многим, промежуточная таблица является зависимой от двух главных таблиц.

Давайте продолжим рассмотрение нашего примера модели, в которой мы определили таблицу покупателя и его заказы. В таблице заказов (Orders) мы использовали поля, описывающие наименование товара и его стоимость. Очевидно, что использование такой структуры базы данных является неэффективным. Более правильный подход, предусматривает создание таблицы Product, в которой содержится коллекция товаров для магазина, а между таблицами Product и Customer нужно создать отношение многие-ко-многим за счет использования промежуточной таблицы Orders. То есть в контексте нашего примера связь многие-ко-многим говорит о том, что один покупатель может заказать несколько товаров, и один товар может быть заказан несколькими покупателями.

Давайте реализуем такую модель в Code-First. Ниже показана структура модели, при которой Entity Framework автоматически распознает связь многие-ко-многим между двумя таблицами Customers и Products (мы удалили класс Profile, который использовали при обсуждении отношения один-к-одному в предыдущей статье, а также класс Order):

public class Customer
{
        public int CustomerId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
        public int Age { get; set; }
        public byte[] Photo { get; set; }

        // Заказанные покупателем товары
        public List<Product> Products { get; set; }
}

public class Product
{
        public int ProductId { get; set; }
        public string ProductName { get; set; }
        public string Description { get; set; }
        public decimal Cost { get; set; }

        // Список покупателей, заказавших этот товар
        public List<Customer> Customers { get; set; }
}

Если вы запустите приложение, то Entity Framework автоматически воссоздаст базу данных, т.к. модель изменилась. Code-First сможет распознать связь между этими классами как многие-ко-многим и создаст промежуточную таблицу, которая содержит внешние ключи, ссылающиеся на таблицы Customers и Products, как показано на рисунке ниже:

Создание трех таблиц при определении отношения многие-ко-многим

Стоит отметить, что эти внешние ключи являются и первичными ключами промежуточной таблицы. Имя промежуточной таблицы состоит из имен двух связанных таблиц, в нашем примере она имеет имя ProductCustomers. Имена сгенерированных внешних ключей следуют тем же соглашениям, что мы описали ранее. После того, как отношение многие-ко-многим определено, Entity Framework знает, какие нужно использовать SQL-операторы для вставки, удаления и обновления данных в связанных таблицах.

Связь многие-ко-многим можно также настроить, используя средства Fluent API. Это может потребоваться тогда, когда вы используете несколько связей между таблицами, чтобы явно указать пару навигационных свойств, обеспечивающих реализацию этого отношения. Ниже показан пример этой настройки:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Customer>()
        .HasMany(c => c.Products)
        .WithMany(p => p.Customers)
        .Map(m =>
        {
            // Ссылка на промежуточную таблицу
            m.ToTable("Orders");

            // Настройка внешних ключей промежуточной таблицы
            m.MapLeftKey("CustomerId");
            m.MapRightKey("ProductId");
        });
}

Обратите внимание, что здесь мы явно задаем имя промежуточной таблицы при вызове вспомогательного метода Map(), в результате чего Entity Framework создаст при запуске примера таблицу с именем Orders, а не ProductCustomers.

Мы также явно задаем имена внешних ключей, используя методы MapLeftKey() и MapRightKey() объекта конфигурации ManyToManyAssociationMappingConfiguration, который передается методу Map() в параметре делегата Action. Эти методы указывают левый и правый ключи для промежуточной таблицы. Чтобы их не перепутать, вы должны запомнить простое правило, левый ключ указывает на класс сущности, для которого мы вызвали метод HasMany() ранее, а правый ключ указывает на класс сущности, для которого мы вызвали метод WithMany(). Если вы их перепутаете ничего страшного не произойдет, просто внешний ключ CustomerId будет ссылаться на таблицу Product, а внешний ключ ProductId на таблицу Customer.

Пройди тесты
Лучший чат для C# программистов