Универсальный репозиторий для обновления всей совокупности


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

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

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

Сохранение данных обрабатывается с помощью сущности Рамки 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? Как я мог сделать это в общих чертах?

3 11

3 ответа:

Джулия Лерман в своей книге 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");
 }
}

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

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

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

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

Мой самоуверенный (конкретный DDD) ответ будет:

  1. Отключите объекты EF на уровне данных.

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

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

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

  5. Не используйте универсальные репозитории.

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

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

 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, так как мой aggregateroot-это продукт (а не категория продукта), но я хочу добавить категорию продукта, когда я создаю или обновляю продукт в сервисном слое:

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

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