Единица работы для нетривиальных операций CRUD для нескольких репозиториев - PullRequest
2 голосов
/ 30 ноября 2009

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

    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, но сохраняю параметры операции и статус 'ожидание' в некотором стеке экземпляра репозитория (очевидно, я не могу сохранить все в UoW, потому что UoW не должен зависеть от конкретных реализаций репозитория). А затем я регистрирую соответствующий репозиторий в экземпляре UoW. Когда UoW вызывает Commit, он открывает транзакцию и вызывает что-то вроде Flush () для каждого ожидающего хранилища. Теперь каждый метод репозитория нуждается в некотором материале для обнаружения UoW и отсрочки работы на более поздний срок Commit().

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

Ответы [ 3 ]

3 голосов
/ 01 декабря 2009

Может показаться, что даже сложные обновления можно разбить на серию модификаций одного или нескольких объектов 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 придется выполнять несколько операторов удаления вместо одного оператора для всех неактивных пользователей.

1 голос
/ 30 ноября 2009

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

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

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

0 голосов
/ 01 декабря 2009

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

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

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

...