Как определить отношение "многие ко многим" с помощью 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");
});
Зачем?. Почему я должен добавить конфигурацию как из сущностей, где я читал здесь, так и из многих других мест, должна подойти конфигурация для одной из сущностей.
В каком случае я должен добавить конфигурацию для обеих сущностей, участвующих в отношениях?
Мне нужно это понять. Зачем. Пожалуйста помоги.
1 ответ
Условия Left
а также Right
в MapLeftKey
а также MapRightKey
в сопоставлении "многие ко многим" с Fluent API может быть неправильно понято, и я думаю, что ваша проблема вызвана этим недоразумением.
Можно подумать, что это означает, что они описывают столбцы "слева" и "справа" в таблице соединений "многие ко многим". Это на самом деле так, если вы позволите EF Code-First создать базу данных и присоединиться к таблице на основе вашего отображения Fluent.
Но это не обязательно тот случай, когда вы создаете отображение в существующую базу данных.
Чтобы проиллюстрировать это на примере прототипа "многие ко многим" User
-Role
модель предполагает, что у вас есть существующая база данных с Users
, Roles
а также RoleUsers
Таблица:
Теперь вы хотите отобразить эту схему таблицы на простую модель:
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
из 2
что вызывает нарушение ограничения внешнего ключа, потому что нет пользователя с UserId
2
в Users
Таблица.
Другими словами, приведенное выше сопоставление настроило столбец RoleId
как внешний ключ к таблице Users
и колонна UserId
как внешний ключ к таблице Roles
, Чтобы исправить отображение, мы должны использовать имя левого столбца в таблице соединений в MapRightKey
и "правильный" столбец в MapLeftKey
:
m.MapLeftKey("UserId");
m.MapRightKey("RoleId");
На самом деле, глядя на Intellisense, описание проясняет, что на самом деле означают "левый" и "правый":
MapLeftKey
Настраивает имя столбца (ов) для левого внешнего ключа. Левый внешний ключ представляет свойство навигации, указанное в вызове HasMany.
MapRightKey
Настраивает имя столбца (-ов) для правильного внешнего ключа. Правый внешний ключ представляет свойство навигации, указанное в вызове WithMany.
Таким образом, "влево" и "вправо" относятся к порядку, в котором объекты отображаются в отображении Fluent, а не к порядку столбцов в таблице соединений. Порядок в таблице на самом деле не имеет значения, вы можете изменить его, не нарушая ничего, потому что INSERT
отправленный EF является "расширенным" INSERT
он также содержит имена столбцов, а не только значения.
возможно MapFirstEntityKey
а также MapSecondEntityKey
было бы менее вводящим в заблуждение выбором этих имен методов - или, возможно, MapSourceEntityKey
а также MapTargetEntityKey
,
Это был длинный пост о двух словах.
Если мое предположение верно, что это как-то связано с вашей проблемой, то я бы сказал, что ваше первое отображение неверно и вам нужно только второе и правильное отображение.