Единица работы для нетривиальных операций CRUD для нескольких репозиториев

StackOverflow https://stackoverflow.com/questions/1822179

  •  10-07-2019
  •  | 
  •  

Вопрос

Я видел шаблон единицы работы, реализованный с помощью чего-то вроде следующего кода:

    private HashSet<object> _newEntities = new HashSet<object>();
    private HashSet<object> _updatedEntities = new HashSet<object>();
    private HashSet<object> _deletedEntities = new HashSet<object>();

а затем есть методы для добавления сущностей в каждый из этих HashSets.

При фиксации UnitOfWork создает несколько экземпляров Mapper для каждой сущности и вызывает методы Insert, Update, Delete из некоторого воображаемого Mapper.

Проблема с этим подходом для меня заключается в том, что имена методов Insert, Update, Delete жестко запрограммированы, поэтому кажется, что такой UnitOfWork способен выполнять только простые операции CRUD. Но что, если мне понадобится следующее использование:

UnitOfWork ouw = new UnitOfWork();
uow.Start();

ARepository arep = new ARepository();
BRepository brep = new BRepository(); 

arep.DoSomeNonSimpleUpdateHere();
brep.DoSomeNonSimpleDeleteHere();

uow.Commit();

Теперь подход с тремя HashSet терпит неудачу, потому что тогда я мог зарегистрировать сущности A и B только для операций вставки, обновления, удаления, но теперь мне нужны эти пользовательские операции.

Похоже, я не могу всегда составлять операции репозитория и затем выполнять их все с помощью UnitOfWork.Commit ();

Как решить эту проблему? Первая идея - я мог бы хранить адреса методов

arep.DoSomeNonSimpleUpdateHere();
brep.DoSomeNonSimpleDeleteHere(); 

в экземпляре UoW и выполните их в uow.Commit () , но затем я должен также сохранить все параметры метода. Это звучит сложно.

Другая идея состоит в том, чтобы сделать репозитории полностью UoW-осведомленными: в DoSomeNonSimpleUpdateHere я могу обнаружить, что UoW работает, и поэтому я не выполняю DoSomeNonSimpleUpdateHere , но сохраняю параметры операции и состояние ожидания в некотором стеке экземпляра Repository (очевидно, я не могу сохранить все в UoW, потому что UoW не должен зависеть от конкретных реализаций Repository). А затем я регистрирую соответствующий репозиторий в экземпляре UoW. Когда UoW вызывает Commit , он открывает транзакцию и вызывает что-то вроде Flush () для каждого ожидающего хранилища. Теперь каждый метод репозитория нуждается в некотором материале для обнаружения UoW и откладывания операции для последующей Commit () .

Итак, короткий вопрос - как проще всего зарегистрировать все ожидающие изменения в нескольких репозиториях в UoW, а затем Commit () их все в одной транзакции?

Это было полезно?

Решение

Казалось бы, даже сложные обновления можно разбить на серию модификаций одного или нескольких объектов DomainObject. Вызов DoSomeNonSimpleUpdateHere () может изменить несколько различных объектов DomainObject, что вызовет соответствующие вызовы UnitOfWork.registerDirty (DomainObject) для каждого объекта. В приведенном ниже примере кода я заменил вызов DoSomeNonSimpleUpdateHere кодом, который удаляет неактивных пользователей из системы.

UnitOfWork uow = GetSession().GetUnitOfWork();
uow.Start();

UserRepository repository = new UserRespository();
UserList users = repository.GetAllUsers();

foreach (User user in users)
{
  if (!user.IsActive())
    users.Remove( user );
}

uow.Commit();

Если вас беспокоит необходимость перебора всех пользователей, вот альтернативный подход, в котором используется объект Criteria для ограничения числа пользователей, извлекаемых из базы данных.

UnitOfWork uow = GetSession().GetUnitOfWork();
uow.Start();

Repository repository = new UserRespository();
Criteria inactiveUsersCriteria = new Criteria();
inactiveUsersCriteria.equal( User.ACTIVATED, 0 );
UserList inactiveUsers = repository.GetMatching( inactiveUsersCriteria );
inactiveUsers.RemoveAll();

uow.Commit();

Методы UserList.Remove и UserList.RemoveAll уведомят UnitOfWork о каждом удаленном пользователе. Когда вызывается UnitOfWork.Commit (), он удаляет каждого пользователя, найденного в его _deletedEntities. Этот подход позволяет создавать произвольно сложный код без необходимости писать SQL-запросы для каждого особого случая. Использование пакетных обновлений будет здесь полезно, поскольку UnitOfWork должен будет выполнить несколько операторов удаления вместо одного оператора для всех неактивных пользователей.

Другие советы

Тот факт, что у вас есть эта проблема, говорит о том, что вы используете не шаблон Repository как таковой, а нечто большее, чем шлюзы с несколькими таблицами данных. Как правило, хранилище предназначено для загрузки и сохранения совокупного корня. Таким образом, когда вы сохраняете сущность, ваш уровень сохраняемости сохраняет все изменения в графе объектов этого агрегатного корневого экземпляра сущности.

Если в вашем коде имеется примерно одно " хранилище " на одну таблицу (или сущность) вы, вероятно, фактически используете шлюз данных таблицы или объект передачи данных. В этом случае вам, вероятно, потребуется средство передачи ссылки на активную транзакцию (или единицу работы) в каждом методе Save ().

В книге DDD Эванса он рекомендует передать управление транзакциями клиенту репозитория, и я согласен, что это не очень хорошая практика, хотя ее может быть сложнее избежать, если вы на самом деле используете шаблон шлюза табличных данных.

Я наконец нашел это:

http://www.goeleven.com/Blog/82

Автор решает проблему, используя три Списка для обновления / вставки / удаления, но он не сохраняет там сущности. Вместо этого хранятся делегаты репозитория и их параметры. Поэтому при коммите автор вызывает каждого зарегистрированного делегата. При таком подходе я мог бы зарегистрировать даже некоторые сложные методы хранилища и поэтому избегать использования отдельного TableDataGateway.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top