Работа с параллелизмом с Entity Framework в приложении ASP.NET MVC
В предыдущих уроках вы работали со связанными данными. В этом уроке мы рассмотрим вопросы одновременного доступа. Вы создадите страницы, работающие с сущностью Department, и страницы для редактирования и удаления сущностей Department будут также обрабатывать ошибки параллелизма. Результаты работы изображены на иллюстрациях. Конфликты одновременного доступа Конфликт одновременного доступа возникает, когда один пользователь просматривает данные об одной сущности и далее редактирует их, и в это же время другой пользователь обновляет эти же самые данные перед тем, как изменения, внесённые первым пользователем, сохраняются в базу. Если EF не настроен для обнаружения подобных конфликтов, тот, кто последним обновит базу данных, перезапишет изменения, внесённые ранее. Во многих приложения риск не критичен: если есть несколько пользователей, или несколько обновлений, или перезапись изменений не очень критична, то цена программирования, ориентированного на параллелизм, будет выше чем выгода от этого. В таком случае, настраивать приложения для обработки подобных ситуаций необязательно Pessimistic Concurrency (Locking) Если приложение нуждается в предотвращении случайной потери данных в результате конфликтов одновременного доступа, одним из методов решения проблемы является блокировка таблиц. Это называется пессимистичный параллелизм (pessimistic concurrency). Например, перед загрузкой записи из базы данных, вы запрашиваете блокировку на read-only или на update доступ. Если вы блокируете таким образом доступ на изменение, ни один другой пользователь не может блокировать данную запись на доступ только-чтение или изменение, так как они получают только копию данных. Если вы блокируете запись на доступ только-чтение, другие также могут заблокировать его на доступ только-чтение, но только не на изменение. Управление блокировками имеет свои недостатки. Программирование может быть слишком сложным, блокировки нуждаются в серьёзных ресурсах базы данных, и накладные расходы по загрузке возрастают по мере возрастания количества пользователей приложения. В связи с этим не все СУБД поддерживают пессимистичный параллелизм. Entity Framework не предоставляет встроенного механизма для обеспечения пессимистичного параллелизма, и в данном уроке этот подход не будет рассматриваться. Optimistic Concurrency В качестве альтернативы пессимистичному параллелизму (pessimistic concurrency) выступает оптимистичный параллелизм (optimistic concurrency). Optimistic concurrency позволяет конфликтам одновременного доступа случиться, но позволяет адекватно среагировать на подобные ситуации. Например, Джон открывает страницу Departments Edit, изменяет значение Budget для английского филиала с $350,000.00 на $100,000.00. Перед нажатием Джоном кнопки Save, Джейн открывает ту же страницу и изменяет значение Start Date на 1/1/1999. Джон нажимает кнопку Save первым и видит свои изменения, и в этот момент на кнопку нажимает Джейн. Что следует за этим, зависит от того, как вы обрабатываете подобного рода ситуации. Их можно обрабатывать следующими методами:
Этот метод может уменьшить количество ситуаций с потерей данных, но не сможет помочь при редактировании одного свойства сущности. Однако использование этого метода нечасто можно встретить в веб-приложениях в связи с большим количеством данных, которым необходимо управлять для отслеживания старых и новых значений свойств. Управление большими массивами данных может сказаться на производительности приложения.
Обнаружение конфликтов одновременного доступа Можно разрешать подобные конфликты обработкой исключений OptimisticConcurrencyException, выбрасываемого EF. Для того, чтобы узнать, когда выбрасывать данное исключение, EF должен уметь определять момент возникновения конфликта. Поэтому необходимо правильно настроить базу данных и модель данных. Можно воспользоваться следующими вариантами для подобной настройки:
Тип данных данного столбца обычно timestamp, но на самом деле он не хранит дату или время. Вместо этого, значение равно цифре, увеличивающейся на единицу при каждом обновлении данных (такой же тип данных может иметь тип rowversion в последних версиях SQL Server). В запросах Update или Delete, Оператор Where включает исходное значение «следящего» столбца. Если запись в процессе обновления редактируется пользователем, значение в данном столбце отличается от исходного, поэтому запросы Update и Delete не смогут найти данную запись. Когда EF обнаруживает, что запросом Update или Delete ничего не было обновлено, он расценивает это как возникновение конфликта одновременного доступа.
Как один из вариантов, если ничего в записи не изменилось с момента её первой загрузки, оператор Where не возвратит запись для обновления, что EF воспримет как конфликт. Данный вариант эффективен в той же мере, как и вариант «следящего» столбца. Однако в случае наличия таблицы с множеством столбцов, в результате использования этого подхода могут возникнуть большое количество операторов Where и необходимость в управлении больших массивов данных и состояний. Данный подход не рекомендуется к использованию в большинстве случаев. В данном уроке мы добавим «следящий» столбец к сущности Department, создадим контроллер и представления и протестируем всё в связке. Note если вы реализуете параллелизм без «следящего» столбца, вы должны отметить все непервичные ключи атрибутом ConcurrencyCheck, определив для EF, что в операторе Where запросов Update будут включаться все столбцы Добавление «следящего» столбца к сущности Department В Models\Department.cs добавьте «следящий» столбец:
Атрибут Timestamp определяет, что данный столбец будет включен в оператор Where запросов Update и Delete. Создание контроллера Создайте контроллер Department и представления: В Controllers\DepartmentController.cs добавьте using: using System.Data.Entity.Infrastructure; Измените LastName на FullName во всём файле (четыре вхождения) чтобы в выпадающих списках факультетских администраторов отображалось полное имя вместо фамилии. Замените код метода HttpPost Edit на:
Представление отобразит исходное значение «следящего» столбца в скрытом поле. При создании экземпляра department, этот объект не будет иметь значения в свойстве Timestamp. Затем, после создания EF запроса Update, запрос будет включать оператор Where с условием поиска записи с исходным значением Timestamp. Если запросом Update не будет обновлена ни одна запись, EF выбросит исключение DbUpdateConcurrencyException, и код в блоке catch возвратит связанную с исключением сущность Department. Эта сущность имеет исходные и новые значения свойств:
Далее, код добавляет сообщение об ошибке для каждого столбца, имеющего в базе данных значения, отличающиеся от того, что ввёл пользователь на странице Edit:
При ошибке отображается подробное сообщение:
Наконец, код устанавливает значение свойства Timestamp для объекта Department в новое значение, полученное из базы данных. Это новое значение будет сохранено в скрытом поле при обновлении страницы Edit, и при следующем нажатии Save, будут перехвачены только те ошибки параллелизма, которые возникли с момента перезагрузки страницы. В Views\Department\Edit.cshtml добавьте скрытое поле для сохранения значения Timestamp, сразу после скрытого поля для свойства DepartmentID: @Html.HiddenFor(model => model.Timestamp) В Views\Department\Index.cshtml замените код, так, чтобы сдвинуть ссылки записей влево и изменить заголовки страницы и столбцов:
Проверка работы Optimistic Concurrency Запустите проект и щёлкните на Departments: Щёлкните ссылку Edit и затем в новом окне браузера откройте ещё одну страницу Edit. Окна должны отображать идентичную информацию. Измените поле в первом окне браузера и нажмите Save. Отобразится страница Index с изменёнными данными. Измените то же самое поле на другое значение во втором окне браузер. Нажмите Save, чтобы увидеть сообщение об ошибке: Нажмите Save ещё раз. Значение, которое вы ввели во втором окне браузера, сохранилось в базе данных и вы увидите, что изменения появились на странице Index. Добавление страницы Delete Для страницы Delete вопросы параллелизма обрабатываются подобным образом. При отображении методом HttpGet Delete окна подтверждения, представление включает исходное значение Timestamp в скрытом поле. Это значение доступно методу HttpPost Delete, который вызывается, когда пользователь подтверждает удаление. Когда EF создаёт запрос Delete, этот запрос включает оператор Where с исходным значением Timestamp. Если запрос ничего не возвратил, выбрасывается исключения параллелизма, и метод HttpGet Delete вызывается с параметром ошибки, установленным в true для перезагрузки страницы подтверждения с сообщением об ошибке. В DepartmentController.cs замените код метода HttpGet Delete на:
Метод принимает необязательный параметр, определяющий, необходимо ли перезагрузить страницу после ошибки параллелизма. Если параметр установлен в true, сообщение об ошибке пересылается в представление в свойстве ViewBag. Замените код метода HttpPost Delete (DeleteConfirmed) на:
Изначально метод принимал только значение ID записи:
Мы изменили этот параметр на сущность Department, что даёт нам доступ к свойству Timestamp. public ActionResult DeleteConfirmed(Department department) Если выбрасывается ошибка параллелизма, код перезагружает страницу подтверждения с усановленным параметром ошибки. В Views\Department\Delete.cshtml замените код на код, обеспечивающий форматирование и поле для сообщения об ошибке:
Этот код добавляет сообщение об ошибке между заголовками h2 и h3: <p class="error">@ViewBag.ConcurrencyErrorMessage</p> Он заменяет LastName на FullName в поле Administrator:
И, наконец, добавляются скрытые поля для DepartmentID и Timestamp:
Откройте в разных окнах браузера страницу Departments Index. В первом окне нажмите Edit и измените одно из значений, но не нажимайте Save: Во втором окне нажмите Delete на том же факультете. Появится окно подтверждения. Нажмите Save в первом окне браузера. Изменения подтвердятся. Теперь нажмите Delete во втором окне браузера, чтобы увидеть сообщение об ошибке параллелизма. Данные обновятся. Если вы нажмёте Delete еще раз, то откроется страница Index с подтверждением об удалении записи факультета. Мы закончили вступление в обработку конфликтов одновременного доступа. Для дополнительной информации смотрите Optimistic Concurrency Patterns и Working with Property Values. В следующем уроке мы покажем вам как реализовать наследование для сущностей Instructor и Student. Это перевод оригинальной статьи Handling Concurrency with the Entity Framework in an ASP.NET MVC Application. Благодарим за помощь в переводе Александра Белоцерковского. |