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();
}