Общий репозиторий для обновления всего агрегата
Я использую шаблон репозитория, чтобы обеспечить доступ и сохранение моих агрегатов.
Проблема заключается в обновлении агрегатов, которые состоят из отношения сущностей.
Например, возьмите отношения 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();
}
}
надеюсь, что это поможет