Подтвердить что ты не робот

Как определить отношения "многие ко многим" через Fluent API Entity Framework?

Ниже приведена моя модель:

public class TMUrl
{
    //many other properties

    //only property with type Keyword
    public List<Keyword> Keywords{get;set;} 
}

public class Keyword
{
   //many other properties

   //only property with type TMUrl
   public List<TMUrl> Urls{get;set;}
}

Таким образом, обе организации имеют отношения "многие ко многим". Я выбрал свободно api, чтобы рассказать сущность-структуру об этих отношениях, т.е.

modelBuilder.Entity<TMUrl>
               .HasMany(s => s.Keywords)
               .WithMany(s => s.URLs).Map(s =>
                {
                    s.MapLeftKey("KeywordId");
                    s.MapRightKey("UrlId");
                    s.ToTable("KeywordUrlMapping");
                });

но когда я делаю

url.Keywords.Add(dbKey); //where url is object of TMUrl, 
                         //dbKey is an existing/new object of Keyword
db.SaveChanges();

Я получаю исключение

An error occurred while saving entities that do not expose foreign key 
properties for their relationships....

InnerException:

The INSERT statement conflicted with the FOREIGN KEY constraint   
"KeywordMaster_Keyword". The conflict occurred in database "DbName", 
table "dbo.KeywordMaster", column 'Id'.The statement has been terminated.

но когда я добавляю конфигурацию из другой стороны, все работает нормально. то есть.

modelBuilder.Entity<KeyWord>
         .HasMany(s => s.URLs)
         .WithMany(s => s.Keywords)
         .Map(s =>
               {
                  s.MapLeftKey("KeywordId");
                  s.MapRightKey("UrlId");
                  s.ToTable("KeywordUrlMapping");
               });

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

В каком случае, когда я должен добавить конфигурацию для обоих объектов, участвующих в отношении?

Мне нужно это понять. Зачем. Пожалуйста, помогите.

4b9b3361

Ответ 1

Термины Left и Right в MapLeftKey и MapRightKey в сопоставлении "многие ко многим" с Fluent API могут быть неправильно истолкованы, и я думаю, ваша проблема вызвана этим недоразумением.

Можно подумать, что это означает, что они описывают столбцы, которые являются "левыми" и "правыми" в таблице соединений "многие-ко-многим". Это на самом деле случай, если вы позволите EF Code-First создать базу данных и присоединиться к таблице на основе вашего Fluent-сопоставления.

Но это не обязательно, когда вы создаете сопоставление с существующей базой данных.

Чтобы проиллюстрировать это прототипом примера "много-ко-многим" модели User - Role, предположим, что у вас есть существующая база данных с таблицами Users, Roles и RoleUsers:

Many-to-many database tables

Теперь вы хотите сопоставить эту схему таблицы с простой моделью:

public class User
{
    public User()
    {
        Roles = new List<Role>();
    }

    public int UserId { get; set; }
    public string UserName { get; set; }
    public ICollection<Role> Roles { get; set; }
}

public class Role
{
    public int RoleId { get; set; }
    public string RoleName { get; set; }
}

И вы добавляете сопоставление Fluent для объекта Users (вы должны сделать это таким образом, потому что по соглашению вышеприведенная модель будет "один ко многим", и вы не можете начать с стороны сущности Role, потому что он не имеет коллекции Users):

modelBuilder.Entity<User>()
    .HasMany(u => u.Roles)
    .WithMany()
    .Map(m =>
    {
        m.MapLeftKey("RoleId");  // because it is the "left" column, isn't it?
        m.MapRightKey("UserId"); // because it is the "right" column, isn't it?
        m.ToTable("RoleUsers");
    });

Это сопоставление неверно, и если вы попытаетесь поставить "Анна" в роль "Маркетинг"...

var anna = ctx.Users.Find(1);
var marketing = ctx.Roles.Find(2);

anna.Roles.Add(marketing);

ctx.SaveChanges();

... SaveChanges точно выдает исключение, которое у вас есть. Причина становится очевидной при захвате команды SQL, которая отправляется с помощью SaveChanges:

exec sp_executesql N'insert [dbo].[RoleUsers]([RoleId], [UserId])
values (@0, @1)
',N'@0 int,@1 int',@0=1,@1=2

Итак, EF хочет вставить здесь строку в таблицу соединений RoleUsers с RoleId из 1 и UserId of 2, которая вызывает нарушение ограничения внешнего ключа, потому что нет пользователя с UserId 2 в таблице Users.

Другими словами, приведенное выше сопоставление сконфигурировало столбец RoleId как внешний ключ в таблице Users и столбец UserId в качестве внешнего ключа в таблице Roles. Чтобы исправить отображение, мы должны использовать "левое" имя столбца в таблице соединений в столбце MapRightKey и в правом столбце в MapLeftKey:

        m.MapLeftKey("UserId");
        m.MapRightKey("RoleId");

Фактически, глядя на Intellisense, описание дает более четкое представление о том, что "Left" и "Right" действительно означает:

MapLeftKey

Настраивает имя столбца (ов) для левого внешнего ключа. The левый внешний ключ представляет свойство навигации, указанное в Вызов HasMany.

MapRightKey

Настраивает имя столбца (ов) для внешнего ключа. The правый внешний ключ представляет свойство навигации, указанное в WithMany.

Итак, "Left" и "Right" относятся к порядку, в котором объекты отображаются в сопоставлении Fluent, а не в порядке столбцов в таблице соединений. Порядок в таблице фактически не имеет значения, вы можете изменить его, не нарушая ничего, потому что INSERT, отправленный EF, является "расширенным" INSERT, который также содержит имена столбцов, а не только значения.

Возможно, MapFirstEntityKey и MapSecondEntityKey были бы менее вводящим в заблуждение выбором этих имен методов - или, может быть, MapSourceEntityKey и MapTargetEntityKey.

Это было длинное сообщение о двух словах.

Если мое предположение верно, что оно вообще имеет какое-либо отношение к вашей проблеме, я бы сказал, что ваше первое сопоставление неверно и вам нужно только второе и правильное сопоставление.