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

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

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

Например, возьмите отношения 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? Как я мог сделать это в целом?

Ответ 1

Джули Лерман в своей книге Programming 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 в одном и том же графе.)

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

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

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

Ответ 2

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

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

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

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

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

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

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

Ответ 3

Итак, вы хорошо поработали над методом обновления для вашего сводного корня, посмотрите на эту модель домена:

 public class ProductCategory : EntityBase<Guid>
    {
      public virtual string Name { get; set; }
    }

    public class Product : EntityBase<Guid>, IAggregateRoot
    {
    private readonly IList<ProductCategory> _productCategories = new List<ProductCategory>();

    public void AddProductCategory(ProductCategory productCategory)
        {
            _productCategories.Add(productCategory);
        }
    }

это был только продукт, который имеет категорию продукта. Я только что создал ProductRepository, так как мой агрегат - продукт (не категория продукта), но я хочу добавить категорию продукта при создании или обновлении продукта на уровне сервиса:

public CreateProductResponse CreateProduct(CreateProductRequest request)
        {
            var response = new CreateProductResponse();
         try
            {
                var productModel = request.ProductViewModel.ConvertToProductModel();   
                Product product=new Product();
                product.AddProductCategory(productModel.ProductCategory);
                _productRepository.Add(productModel);
                _unitOfWork.Commit();
            }
            catch (Exception exception)
            {
                response.Success = false;
            }
            return response;
        }

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

product.AddProductCategory(productModel.ProductCategory);

теперь для обновления одного и того же объекта вы можете запросить ProductRepository и получить объект и внести изменения в него. обратите внимание, что для извлечения объекта и объекта значения и агрегата отдельно вы можете написать службу запроса или readOnlyRepository:

 public class BlogTagReadOnlyRepository : ReadOnlyRepository<BlogTag, string>, IBlogTagReadOnlyRepository
    {
        public IEnumerable<BlogTag> GetAllBlogTagsQuery(string tagName)
        {
            throw new NotImplementedException();
        }
    }

надеюсь, что это поможет