Entity Framework в многоуровневых архитектурах - Сохранение изменений
ОГЛАВЛЕНИЕ
Сохранение изменений
Теперь, после рассмотрения приложения, использующего простое извлечение, можно рассмотреть сохранение изменений данных. При изменении пользователем клиента представление CustomerView связано с экземпляром соответствующей сущности Customer (см. рис. 5). Представление CustomerView создает событие для презентатора, который в свою очередь запрашивает экземпляр сущности Customer из нижних слоев.
Рис. 5. Изменение клиентов
При изменении пользователем клиента и сохранении изменений сущность передается от презентатора в нижние слои с помощью кода, показанного на рис. 6. Этот код вычисляет, выполнил ли пользователь добавление или изменение клиента, и затем вызывает соответствующий метод слоя служб, передавая сущность.
Рис. 6. SaveCustomer в презентаторе
public virtual void view_SaveCustomer()
{
Customer customer = view.CurrentCustomer;
var svc = new NWServiceClient();
switch (view.Mode)
{
case ViewMode.EditMode:
svc.UpdateCustomer(customer);
break;
case ViewMode.AddMode:
svc.AddCustomer(customer);
break;
default:
break;
}
view.CurrentCustomer = FindCustomer();
}
Затем слой служб передает управление бизнес-слою, который сохраняет сущность клиента в базе данных. Поскольку сущность клиента больше не является частью контекста ObjectContext, она должна быть снова объединена с классом ObjectContext с помощью его метода Attach, как показано в приведенном ниже коде. После объединения сущности с контекстом свойства сущности должны быть помечены как измененные. Это можно выполнить с помощью диспетчера ObjectStateManager контекста и вызова метода SetModified для каждого свойства. Теперь, когда контексту известно об изменении сущности, может быть вызван метод SaveChanges, который создает команду SQL UPDATE и выполняет ее для базы данных:
public void UpdateCustomer(Customer customer)
{
context.Attach(customer);
customer.SetAllModified(context); // custom extension method
context.SaveChanges();
}
Обратите внимание, что код в методе UpdateCustomer использует метод расширения, который я назвал SetAllModified<T>, что упрощает установку состояния всех свойств для изменяемой сущности. SetAllModified<T> получает экземпляр ObjectStateEntry для определенной сущности T. Затем он получает список имен всех свойств для сущности и итеративно вызывает SetModifiedProperty для каждого свойства:
public static void SetAllModified<T>(this T entity, ObjectContext context)
where T : IEntityWithKey
{
var stateEntry = context.ObjectStateManager. GetObjectStateEntry(entity.EntityKey);
var propertyNameList = stateEntry.CurrentValues.DataRecordInfo. FieldMetadata.Select
(pn => pn.FieldType.Name);
foreach (var propName in propertyNameList)
stateEntry.SetModifiedProperty(propName);
}
Другим способом сохранения сущности является вызов метода Refresh контекста. Это указывает контексту на необходимость получения данных для экземпляра сущности и обновление значений свойств по значениям базы данных. Перечислитель RefreshMode для ClientWins заменяет исходные значения на последние значения в базе данных, таким образом обеспечивая осуществление стратегии «побеждает последний».
Перечислитель RefreshMode для StoreWins переписывает исходные и текущие значения в кэше сущности, используя значения базы данных. ClientWins – подходящая стратегия для принципа «побеждает последний», а стратегия StoreWins подходит для случаев необходимости отмены изменений и обновления представления пользовательского интерфейса с последними значениями в базе данных:
context.Refresh(RefreshMode.ClientWins, customer); // Last in wins
Платформа Entity Framework обеспечивает выполнение оптимистичного параллелизма при создании команд обновления и удаления. Это достигается путем включения исходных значений в предложение WHERE для всех свойств, для атрибута ConcurrencyMode которых установлено значение Fixed.
По умолчанию модели создаются без полей, указанных как поля параллелизма. Это означает, что сохранение пользователем изменений может привести к непреднамеренной перезаписи изменений другого пользователя. При изменении другим пользователем значения при открытом представлении CustomerView и необходимости использования оптимистичного параллелизма это можно осуществить путем установки атрибута ConcurrencyMode для типа EntityType в концептуальной модели.
Изменение файла EDM и установка для ConcurrencyMode значения Fixed указывает платформе Entity Framework на добавление этого столбца в предложение WHERE всех команд Update или Delete Таким образом, если соответствующая строка не будет найдена, возникает исключение OptimisticConcurrencyException. На рис. 7 показано данное создание исключения при изменении региона клиента в базе данных непосредственно перед попыткой изменения пользователем этого же региона.
Рис. 7. Исключение OptimisticConcurrencyException
Можно перехватить это исключение и выполнить любое соответствующее действие. Например, можно перехватить исключение, зарегистрировать его, а затем переписать изменения пользователя, как показано ниже:
catch (OptimisticConcurrencyException e){
context.Refresh(RefreshMode.ClientWins, customer); // Last in wins
logger.Write(e);
context.SaveChanges();
}