EF4 Code First - проблема отношения многих ко многим - PullRequest
0 голосов
/ 11 марта 2011

У меня возникли некоторые проблемы с моей моделью 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

Это, несмотря на то, что мои сохраненные теги имеют свой идентификатор, установленный при сохранении (хотя я не получил их из БД)

Ответы [ 2 ]

2 голосов
/ 12 марта 2011

Я думаю, я знаю, что произошло в вашем коде.Позвольте мне объяснить мое мнение в простом примере:

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 в теге !Так как вы этого не сделали, он добавляет добавленные теги как новые с временными ключами и добавляет их в таблицу 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).

1 голос
/ 11 марта 2011

Вам необходимо получить теги из БД, иначе EF будет рассматривать их как новые элементы и переопределять идентификатор.

...