UnitOfWork 模式应该如何实际处理

How should the UnitOfWork pattern be actually handled

UoW 实际应该处理什么?

在我看来,UoW 不应该处理提交和回滚。它应该只是提供这样做的方法,并且应该只负责跟踪即将提交的对象的更改,这样如果它们处于某种不一致的状态或发生了某些更改,事务应该失败?

所以我看到 UoW 的方式是这样的。

public interface IUnitOfWork : IDisposable
{
    IAtomicTransaction BeginTransaction();

    Task<object> SaveAsync<TEntity>(TEntity entity);

    Task UpdateAsync<TEntity>(TEntity entity);

    Task RemoveAsync<TEntity>(TEntity entity);
}

提交应该是这样的

interface IAtomicTransaction : IDisposable 
{
    void Commit();

    void Rolleback();
}

以这个 post 为例(不仅如此,还有很多类似的),

http://jasonwatmore.com/post/2015/01/28/unit-of-work-repository-pattern-in-mvc5-and-web-api-2-with-fluent-nhibernate-and-ninject

如果您查看,您会发现它在存储库中使用了 ISession,我发现这是一个错误,因为它将直接 link 存储库(您的业务)到 NHibernate 的 ISession。 UoW 不应该承担这个责任吗?您是否应该因为更改框架而开始更改业务实施? UoW 不应该充当适配器,类似于反腐败层吗?

UoW 是 than just a transaction. Following is the quote from Martin Fowler:

A Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you're done, it figures out everything that needs to be done to alter the database as a result of your work.

应该:

  1. 为您的存储库提供上下文
    要么允许从这个 UoW 实例创建存储库,要么允许在存储库中注入这个 UoW 实例。我更喜欢第一种方法。所有具有 internal 构造函数和 UoW 中类似工厂方法的存储库都会创建存储库。
  2. 跟踪此上下文中的更改
    这是一个复杂的话题,但为了简单起见,让我们将其限制在数据库事务中。许多 ORM 以更好的方式处理这个问题。可以通过多种方式跟踪实体对象的状态。维护更改列表,维护实体的状态(脏或不脏)或维护实体的原始副本并在最后与最终副本进行比较等
  3. Flush/Don'-刷新在此上下文中所做的更改
    这与上一点有关。根据跟踪,决定哪些更改需要刷新到存储中。一些 UoW 实现还支持自动刷新,其中 UoW 自动决定 何时 刷新更改。如果一切正常,更改将被刷新;如果有问题,不会冲洗。同样,为简单起见,让我们将其限制为数据库事务。对于详细的实现,使用一些好的 ORM 是更好的解决方案。
  4. 创建和清理资源
    UoW 实例应创建(自动或手动)执行操作所需的资源,并在不再需要时清理它们。

interface IUnitOfWork : IDisposable
{
    IDbConnection Connection { get; }
    IDbTransaction Transaction { get; }
    void Begin();
    void Commit();
    void Rollback();

    IRepository CreateRepository(....);
}

方法CreateRepository 在此UoW 下创建存储库实例。这样,您可以跨多个存储库共享相同的 UoW。这样,一个数据库事务可以分布在多个存储库中。另一种方法是在存储库中注入 UoW,如图 here.

这种方法的问题在于,它不会强制 UoW。何时(或是否)开始交易由调用方决定。


UoW 的其他迷你版本(强制 UoW)我可以想象如下:

public sealed class UoWSession
{
    public UoWSession()
    {
        //Open connection here
        //Begin transaction here
    }

    IRepository CreateRepository(....)
    {
        //Create and return the requested repository instance here
    }

    void Commit()
    {
        transaction.Commit();
    }

    void Dispose()
    {
        //If transaction is not commited, rollback it here.
        //Cleanup resources here.
    }
}

如果不使用 ORM,您必须公开某些东西,告诉您一切都很好。以上实现使用 Commit 方法。
可能有一个简单的 属性 让我们说 IsAllWell 默认情况下是 false 并且调用代码明确设置它。然后,您的 Dispose 方法基于 属性 提交或回滚事务。在这种情况下,您不需要公开 Commit 方法,因为您在标志上内部处理它。

As now you have edited your question, I have to change the way I answer; hence the second answer. My is still valuable (hopefully ;)).

UoW 应该自动找出需要刷新的更改(如果有)。

IAtomicTransaction BeginTransaction();
void Commit();
void Rolleback();

以上方法可能是也可能不是 UoW 的一部分。 UoW 的许多实现都公开了这些。暴露那些的缺点是,事务处理成为调用者的责任;不是你的 class。另外一点是,调用者可以更好地控制流程。

如果您想绕过这个缺点,请参阅我的第一个答案中的备选方案。

Task<object> SaveAsync<TEntity>(TEntity entity);
Task UpdateAsync<TEntity>(TEntity entity);
Task RemoveAsync<TEntity>(TEntity entity);

以上方法是存储库的一部分。那些不能是 UoW 的一部分。 UoW 应该根据更改跟踪自动确定要做什么。如果我们只讨论数据库事务,那么 DbTransaction 可以正确处理。更详细的处理变更跟踪的方法,请参考我的第一个回答。

以下是基于 NHibernate 的实现。请注意 这是 。如果你正在使用完整的 ORM,你应该避免这种类型的实现,因为它对你的设计没有什么价值。 如果您只是将 ISession 替换为 IDbConnection,这也可以用 ADO.NET 实现

public interface IUnitOfWork : IDisposable
{
    void Flush();
}

public sealed class UnitOfWork : IUnitOfWork
{
    public UnitOfWork()
    {
        session = SessionFactroyHelper.CreateSession();
        transaction = session.BeginTransaction();
    }

    ISession session = null;
    ITransaction transaction = null;

    void IUoWSession.Flush()
    {
        transaction.Commit();
    }

    void IDisposable.Dispose()
    {
        transaction.Dispose();
        transaction = null;
        session.Dispose();
        session.Dispose();
    }
}

顺便说一句,这个主题本身是基于意见的。如何实施 UoW 和存储库是个人设计决策。如果您真的热衷于实现 correct(?) UoW,请考虑在代码中直接使用一些高级 ORM 绕过 UoW 包装器。