Обновление объекта и отношений с помощью кода Entity Framework в первую очередь и ASP.Net MVC

#asp.net-mvc-3 #entity-framework-4.1

#asp.net-mvc-3 #entity-framework-4.1

Вопрос:

Сначала я использую код Entity Framework, и пока у меня есть рабочий код, мне приходится выполнять строго ненужные вызовы базы данных для обработки следующего обновления.

У меня есть простой класс POCO для альбома с набором связанных тегов:

 public class Album
{
    public int Id { get; set; }
    public string Title { get; set; }
    public decimal Price { get; set; }
    public virtual IList<Tag> Tags { get; private set; }
}

public class Tag
{
    public int Id { get; set; }
    public string Name { get; set; }
}
  

Это обновляется с помощью формы MVC — с тегами, представленными рядом флажков.

Итак, когда я перехожу к своему методу обновления в репозитории, у меня есть класс album, заполненный списком тегов — теоретически все, что мне нужно для обновления.

Однако единственный способ, который я смог найти, чтобы обновить список тегов (удалить все, которые были установлены ранее, но теперь не отмечены, и добавить все, которые в настоящее время отмечены), — это извлечь исходный альбом из контекста и обновить его.

И, во-вторых, потому что в моей реализации поле Name тега помечено [Обязательно], и что в моем объекте Album, заполненном из формы, у меня есть только идентификаторы тегов, я также должен получить каждый тег перед обновлением.

Вот мой код:

     public void Update(Album album)
    {
        var albumToUpdate = GetById(album.Id);   // - need to retrieve album with tags in order to update tags 
        albumToUpdate.Title = album.Title;
        albumToUpdate.Price = album.Price;
        albumToUpdate.Tags.Clear();

        if (album.Tags != null)
        {
            foreach (var tag in album.Tags)
            {
                var tagToAdd = context.Tags.Find(tag.Id);   // - need to retrieve full details of tag so doesn't fail validation
                albumToUpdate.AddTag(tagToAdd);
            }
        }     
    }
  

Цените любые мысли о том, как я мог бы приспособить это с меньшим количеством обращений к базе данных. Это не очень важно для этой конкретной функции (часть инструмента администрирования сайта), но хотелось бы знать, что я делаю все наилучшим образом.

Ответ №1:

Ваш подход — перезагрузка entity graph из базы данных и объединение изменений вручную в него — на мой взгляд, правильный и лучший, что вы можете сделать.

Забудьте на мгновение, что вы используете Entity Framework. Что бы вы сделали, если бы вам пришлось писать инструкции SQL вручную? (EF — это оболочка вокруг генератора инструкций SQL.) Вы получите отправленный обратно граф объектов Album со списком Tags . Как бы вы теперь решили, какие теги вы должны написать для вставки, какие теги УДАЛИТЬ и для каких тегов оператор ОБНОВЛЕНИЯ? (Я предполагаю, что ваши отношения между Album и Tag являются «многие ко многим», поэтому вы записываете в таблицу соединений.) Если вы не знаете исходное состояние в базе данных, вы не можете решить. Существует ли отношение тегов в базе данных или нет? Вы должны запросить базу данных, чтобы найти ответ, независимо от того, используете ли вы EF или прямой SQL.

Я вижу только две альтернативы:

  • Отслеживайте изменения сущности самостоятельно. Для вашего веб-приложения MVC это будет означать, что вы должны где-то сохранить исходное состояние с предыдущим запросом GET, например, в состоянии сеанса или в скрытых полях ввода на странице. С помощью запроса POST вы можете получить исходное состояние, построить и присоединить исходный граф и объединить в него изменения.

  • Напишите хранимую процедуру, которая принимает коллекцию альбомов и тегов, и пусть SP выполняет работу по созданию соответствующих операторов SQL.

Первый способ сложен и имеет свои издержки в полезной нагрузке HTTP (скрытые поля ввода) или зависит от хрупкого состояния сеанса. А второе противоречит тому, почему вы используете ORM. Если у вас нет действительно серьезных проблем с производительностью или вы не являетесь мастером SQL, я бы не рассматривал хранимую процедуру.

Комментарии:

1. Чтобы ответить на вопрос о том, писал ли я инструкции SQL вручную, я мог бы удалить все текущие связанные теги, а затем вставить все те, которые связаны с объектом Album, переданным функции. Это действительно суть того, что я говорю — у меня уже есть все детали, необходимые для обновления базы данных, так что в этом смысле дополнительные вызовы базы данных, которые запрашивает EF, не нужны. Сказав это, я думаю, вы правы в том, что жить с дополнительными обращениями к базе данных лучше, чем пытаться обойти это.

Ответ №2:

Во-первых, я думаю, что этот шаблон обновлений каким-то образом неверен в том смысле, что вместо передачи альбома, который, как я полагаю, является точной копией или частичной копией того, который вы хотите обновить (по крайней мере, с тем же идентификатором), почему бы вам сначала не загрузить фактический и применить к нему свои изменения?

Если вы не можете этого сделать, может быть менее запутанным не передавать один и тот же объект (альбом), а вместо этого использовать объект передачи данных (DTO) или другое сообщение только с нужными вам полями, а затем применить это к загруженному альбому.

Что касается основной проблемы, заключающейся в том, как избежать загрузки каждого тега, EF должен сделать это за вас, но я не знаю, что это делает. Например, NHibernate не будет загружать отложенный объект, если вы устанавливаете отношения только потому, что вы не затронули какие-либо свойства Tag , поэтому для его использования требуется только идентификатор. Надеюсь, EF делает то же самое, но, возможно, нет (я предполагаю, что вы его профилировали).

Если EF ведет себя не так, вы можете попробовать две вещи: во-первых, пока нет каскадного обновления тега, используйте скелетный с только идентификатором (то есть создайте объект самостоятельно и просто установите идентификатор); это не сработает, если EF cascade обновит тег. Во-вторых, вы могли бы реализовать свой собственный кэш для тегов и получать их из памяти.

Комментарии:

1. Спасибо, Роб. Однако, когда вы говорите: «Почему бы вам сначала не загрузить фактический и не применить к нему свои изменения?» — это то, что я делаю. Первая часть моего вопроса заключается в том, должно ли это быть необходимым, учитывая, что передаваемый альбом заполнен всем, что мне нужно знать для обновления базы данных. Вторая проблема заключается в том, что я могу использовать скелетный тег, но EF будет проверять при обновлении и жалуется на отсутствие имени требуемого поля. Однако обратите внимание на кэширование тегов — это решило бы проблему обращений к базе данных, но все еще интересно, нужно ли вообще их извлекать.

2. Меня смутило это: var albumToUpdate = GetById(album.Id ). Если переданный альбом уже является подключенным загруженным объектом БД, вам не нужно этого делать. Не меняет вашу первоначальную проблему, хотя о том, как эффективно устанавливать теги.

3. Извините, да, возможно, не очень понятно. Альбом, переданный в этом случае, заполняется через привязку модели из сообщения формы, поэтому он не является загруженным объектом БД. Мой вопрос здесь заключается в том, должен ли я снова загрузить его из базы данных для обновления, учитывая, что объект содержит все необходимые поля, заполненные для обновления базы данных.

4. Хорошо — это тот шаблон, который я считаю немного изворотливым. Идея состоит в том, чтобы загрузить объект из базы данных, а затем сопоставить или автоматически сопоставить вашу viewmodel с объектом, чтобы он уже был присоединен. Или, если вы уверены, что все, что вам нужно, находится в объекте, просто вызовите метод AddObject EF, чтобы повторно подключить его.