EF4 Code First - проблема отношения многих ко многим
У меня возникли некоторые проблемы с моей моделью EF Code First при сохранении отношения "многие ко многим". Мои модели:
public class Event
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Event> Events { get; set; }
}
В моем контроллере я сопоставляю одну или несколько моделей TagViewModel с типом тега и отправляю их на уровень обслуживания для сохранения. В настоящее время при проверке сущностей у тега есть и идентификатор, и имя (идентификатор - это скрытое поле, а имя - это текстовое поле, на мой взгляд)
Проблема возникает, когда я сейчас пытаюсь добавить тег к событию. Давайте рассмотрим следующий сценарий:
Событие уже находится в моей базе данных, и, скажем, оно уже имеет связанные теги C#, ASP.NET
Если я сейчас отправлю следующий список тегов на слой обслуживания:
ID Name
1 C#
2 ASP.NET
3 EF4
и добавить их, сначала извлекая Событие из БД, чтобы у меня было фактическое Событие из моего DbContext, затем я просто делаю
myEvent.Tags.Add
добавить теги.. Проблема в том, что после SaveChanges()
моя БД теперь содержит этот набор тегов:
ID Name
1 C#
2 ASP.NET
3 EF4
4 C#
5 ASP.NET
Это, несмотря на то, что у моих Тегов, которые я сохраняю, есть свой ID, установленный при сохранении (хотя я не получил его из БД)
2 ответа
Я думаю, я знаю, что произошло в вашем коде. Позвольте мне объяснить мое мнение на простом примере:
using (var context = new Context())
{
// Just let assume these are your tags received from view model.
var csharp = ...;
var aspnet = ...;
var ef4 = ...;
// Now you load Event
var e = context.Events.Where(e => e.Id == someId);
// Ups first access to Tag collection triggers lazy loading which
// is enabled by default so, all current tags are loaded
e.Tags.Add(csharp);
e.Tags.Add(aspnet);
e.Tags.Add(ef4);
// Now e.Tags.Count == 5 !!! Why?
context.SaveChanges();
}
Первая проблема: потому что динамический прокси создан поверх вашего Event
экземпляр использует HashSet
за Tags
он проверяет, существует ли добавленная сущность в коллекции. Если нет, он добавляет объект, иначе пропускает объект. Чтобы иметь возможность сделать эту проверку правильно, ВЫ ДОЛЖНЫ РЕАЛИЗОВАТЬ Equals и GetHashCode в Tag! Так как вы этого не сделали, он принимает добавленные теги как новые с временными ключами и добавляет их в Tags
стол с автоматически сгенерированным ключом.
Вторая проблема: даже если вы реализуете Equals
а также GetHashCode
Вы решите только двойственность тегов C# и ASP.NET. На данный момент контекст не отслеживает тег EF4, поэтому этот тег все еще считается новым. Вы должны сообщить контексту, что тег EF4 существует в БД. Итак, начнем Attach
все теги в контексте, прежде чем вы начнете ленивую загрузку Tags
коллекция. Прикрепление объекта к контексту по умолчанию устанавливает его состояние Unchanged
:
using (var context = new Context())
{
foreach (var tag in TagsFromView)
{
context.Attach(tag);
}
// Now you load Event
var e = context.Events.Where(e => e.Id == someId);
foreach(var tag in TagsFromView)
{
// First access will trigger lazy loading but already
// attached instances of tags are used
e.Tags.Add(tag);
}
// Now you must delete all tags present in e.Tags and not
// present in TagsFromView
context.SaveChanges();
}
Это работает, если вы не создаете новые теги в своем представлении. Если вы хотите сделать это, вы не должны прикреплять новые теги к контексту. Вы должны различать существующие теги и новые теги (например, новые теги могут иметь Id = 0).
Вам нужно получить теги из базы данных, иначе EF будет рассматривать их как новые элементы и переопределять идентификатор.