用于多个存储库的重要 CRUD 操作的工作单元

发布于 2024-08-13 04:34:27 字数 1544 浏览 6 评论 0原文

我已经看到工作单元模式是用如下代码实现的:

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

然后有一些方法可以将实体添加到每个哈希集中。

在提交时,UnitOfWork 为每个实体创建一些 Mapper 实例,并从一些虚构的 Mapper 中调用 Insert、Update、Delete 方法。

对我来说,这种方法的问题是: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() 之类的操作。现在,Repository 的每个方法都需要一些用于 UoW 检测和操作推迟到稍后的 Commit() 的内容。

因此,简单的问题是 - 在 UoW 的多个存储库中注册所有待处理更改,然后在单个事务中将它们全部提交()的最简单方法是什么?

I have seen unit of work pattern implemented with something like a following code:

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

and then there are methods for adding entities to each of these HashSets.

On Commit UnitOfWork creates some Mapper instance for each entity and calls Insert, Update, Delete methods from some imaginary Mapper.

The problem for me with this approach is: the names of Insert, Update, Delete methods are hard-coded, so it seems such a UnitOfWork is capable only of doing simple CRUD operations. But what if I need the following usage:

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

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

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

uow.Commit();

Now the three-HashSet approach fails because I then I could register A and B entities only for Insert, Update, Delete operations but I need those custom operations now.

So it seems that I cannot always stack the Repository operations and then perform them all with UnitOfWork.Commit();

How to solve this problem? The first idea is - I could store addresses of methods

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

in UoW instance and execute them on uow.Commit() but then I have also to store all the method parameters. That sounds complicated.

The other idea is to make Repositories completely UoW-aware: In DoSomeNonSimpleUpdateHere I can detect that there is a UoW running and so I do not perform DoSomeNonSimpleUpdateHere but save the operation parameters and 'pending' status in some stack of the Repository instance (obviously I cannot save everything in UoW because UoW shouldn't depend on concrete Repository implementations). And then I register the involved Repository in the UoW instance. When UoW calls Commit, it opens a transaction, and calls some thing like Flush() for each pending Repository. Now every method of Repository needs some stuff for UoW detection and operation postponing for later Commit().

So the short question is - what is the easiest way to register all the pending changes in multiple repositories in UoW and then Commit() them all in a single transaction?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

原来是傀儡 2024-08-20 04:34:27

看起来即使是复杂的更新也可以分解为对一个或多个域对象的一系列修改。调用 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 必须为所有非活动用户执行多个删除语句,而不是只执行一个语句。

It would seem that even complicated updates can be broken down into a series of modifications to one or more DomainObjects. Calling DoSomeNonSimpleUpdateHere() may modify several different DomainObjects, which would trigger corresponding calls to UnitOfWork.registerDirty(DomainObject) for each object. In the sample code below, I have replaced the call to DoSomeNonSimpleUpdateHere with code that removes inactive users from the system.

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

If you are concerned about having to iterate over all users, here is an alternative approach that uses a Criteria object to limit the number of users pulled from the database.

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

The UserList.Remove and UserList.RemoveAll methods will notify the UnitOfWork of each removed User. When UnitOfWork.Commit() is called, it will delete each User found in its _deletedEntities. This approach allows you to create arbitrarily complex code without having to write SQL queries for each special case. Using batched updates will be useful here, since the UnitOfWork will have to execute multiple delete statements instead of only one statement for all inactive users.

百变从容 2024-08-20 04:34:27

您遇到此问题的事实表明您没有使用存储库模式,而是更像多个表数据网关。通常,存储库用于加载和保存聚合根。因此,当您保存实体时,持久层会保存该聚合根实体实例的对象图中的所有更改。

如果在您的代码中,每个表(或实体)大约有一个“存储库”,那么您实际上可能正在使用表数据网关或数据传输对象。在这种情况下,您可能需要有一种方法在每个 Save() 方法中传递对活动事务(或工作单元)的引用。

在 Evans DDD 书中,他建议将事务控制留给存储库的客户端,我同意这不是一个好的实践,尽管如果您实际上使用表数据网关模式,这可能很难避免。

The fact that you have this problem suggests that you aren't using the Repository pattern as such, but something more like multiple table data gateways. Generally, a repository is for loading and saving an aggregate root. As such, when you save an entity, your persistence layer saves all the changes in that aggregate root entity instance's object graph.

If, in your code, you have roughly one "repository" per one table (or Entity), you're probably actually using a table data gateway or a data transfer object. In that case, you probably need to have a means of passing in a reference to the active transaction (or the Unit of Work) in each Save() method.

In Evans DDD book, he recommends leaving transaction control to the client of a repository, and I would agree that it's not a good practice, though it may be harder to avoid if you're actually using a table data gateway pattern.

固执像三岁 2024-08-20 04:34:27

我终于找到了这个:

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

作者使用三个列表进行更新/插入/删除来解决问题,但他不在那里存储实体。相反,存储库委托及其参数。因此,在提交时,作者调用每个注册的委托。通过这种方法,我甚至可以注册一些复杂的存储库方法,从而避免使用单独的 TableDataGateway。

I finally found this one:

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

The author solves the problem using three Lists for update/insert/delete, but he does not store entities there. Instead repository delegates and their parameters are stored. So on Commit the author calls each registered delegate. With this approach I could register even some complex repository methods and so avoid using a separate TableDataGateway.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文