Общий репозиторий для обновления всего агрегата

domain-driven-design entity-framework entity-framework-6 onion-architecture repository-pattern

Вопрос

Я использую шаблон репозитория для обеспечения доступа и сохранения моих агрегатов.

Проблема заключается в обновлении агрегатов, которые состоят из отношения сущностей.

Например, возьмите отношения Order и OrderItem . Совокупный корень - это Order который управляет собственной коллекцией OrderItem . Таким образом, OrderRepository будет отвечать за обновление всего агрегата (не будет OrderItemRepository ).

Перенос данных обрабатывается с использованием Entity Framework 6.

Обновить метод репозитория ( DbContext.SaveChanges() происходит в другом месте):

public void Update(TDataEntity item)
{
    var entry = context.Entry<TDataEntity>(item);

    if (entry.State == EntityState.Detached)
    {
        var set = context.Set<TDataEntity>();

        TDataEntity attachedEntity = set.Local.SingleOrDefault(e => e.Id.Equals(item.Id));

        if (attachedEntity != null)
        {
            // If the identity is already attached, rather set the state values
            var attachedEntry = context.Entry(attachedEntity);
            attachedEntry.CurrentValues.SetValues(item);
        }
        else
        {
            entry.State = EntityState.Modified;
        }
    }
}

В моем примере выше будет обновляться только объект Order , а не связанная с OrderItem коллекция OrderItem .

Должен ли я прикрепить все OrderItem ? Как я мог сделать это в общем?

Принятый ответ

Джулия Лерман дает хороший способ справиться с тем, как обновить весь агрегат в своей книге « Программирование Entity Framework: DbContext» .

Как она пишет:

Когда график соединения отключен на сервере, сервер не будет знать состояние объектов. Вам нужно предоставить способ для обнаружения состояния, чтобы контекст мог быть осведомлен о состоянии каждого объекта.

Этот метод называется painting the state .

В основном это два способа:

  • Итерацию по графику с использованием ваших знаний о модели и задание состояния для каждого объекта
  • Создайте общий подход для отслеживания состояния

Второй вариант действительно хорош и состоит в создании интерфейса, который реализует каждый объект в вашей модели. Джулия использует интерфейс IObjectWithState который сообщает текущему состоянию объекта:

 public interface IObjectWithState
 {
  State State { get; set; }
 }
 public enum State
 {
  Added,
  Unchanged,
  Modified,
  Deleted
 }

Первое, что вам нужно сделать, - автоматически установить на Unchanged все сущности, полученные из БД, путем подключения события, добавляющего конструктор в ваш класс Context :

public YourContext()
{
 ((IObjectContextAdapter)this).ObjectContext
  .ObjectMaterialized += (sender, args) =>
 {
  var entity = args.Entity as IObjectWithState;
  if (entity != null)
  {
   entity.State = State.Unchanged;
  }
 };
}

Затем измените классы Order и OrderItem для реализации интерфейса IObjectWithState и вызовите метод ApplyChanges принимающий корневой объект как параметр:

private static void ApplyChanges<TEntity>(TEntity root)
 where TEntity : class, IObjectWithState
{
 using (var context = new YourContext())
 {
  context.Set<TEntity>().Add(root);

  CheckForEntitiesWithoutStateInterface(context);

  foreach (var entry in context.ChangeTracker
  .Entries<IObjectWithState>())
  {
   IObjectWithState stateInfo = entry.Entity;
   entry.State = ConvertState(stateInfo.State);
  }
  context.SaveChanges();
 }
}

private static void CheckForEntitiesWithoutStateInterface(YourContext context)
{
 var entitiesWithoutState =
 from e in context.ChangeTracker.Entries()
 where !(e.Entity is IObjectWithState)
 select e;

 if (entitiesWithoutState.Any())
 {
  throw new NotSupportedException("All entities must implement IObjectWithState");
 }
}

И последнее, но не менее важное: не забудьте установить правильное состояние вашего графика, прежде чем вызывать ApplyChanges ;-) (вы могли бы даже смешивать Modified и Deleted состояния на одном и том же графике)

Джулия предлагает пойти еще дальше в своей книге:

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

Но поскольку мой ответ уже слишком длинный, идите читать ее книгу, если хотите узнать больше ;-)


Популярные ответы

Мой упрямый (DDD-специфический) ответ будет:

  1. Отрежьте объекты EF на уровне данных.

  2. Убедитесь, что ваш уровень данных возвращает только объекты домена (а не объекты EF).

  3. Забудьте о ленивой загрузке и IQueryable() добра (прочитайте: кошмар) EF.

  4. Рассмотрите возможность использования базы данных документов.

  5. Не используйте общие репозитории.

Единственный способ, который я нашел, чтобы сделать то, что вы просите в EF, - это сначала удалить или деактивировать все элементы заказа в базе данных, которые являются дочерним по заказу, затем добавить или повторно активировать все элементы заказа в базе данных, которые теперь являются частью вашего недавно обновленный заказ.



Лицензировано согласно: CC-BY-SA with attribution
Не связан с Stack Overflow
Является ли этот КБ законным? Да, узнайте, почему
Лицензировано согласно: CC-BY-SA with attribution
Не связан с Stack Overflow
Является ли этот КБ законным? Да, узнайте, почему