【问题标题】:C# UnitOWork implementation without EF with database transaction or TransactionScope没有带有数据库事务或 TransactionScope 的 EF 的 C# UnitOWork 实现
【发布时间】:2014-01-31 13:10:47
【问题描述】:

我正在开发一个 Web API 项目(ninject、原始 sql 查询、存储库模式、UoW 模式)我几乎在所有地方都检查过一篇文章,该文章描述了使用不使用实体框架的简单数据库事务实现 UoW(普通的 SQL 请求和 SqlConnection 等...),但找不到任何东西。

我遇到的问题如下。我有一个 Web API,它的控制器与存储库一起使用,而存储库又通过通过 UoW 注入到其中的 DBManager 类与 DB 一起使用。

假设我在存储库中有 2 个方法,每个方法都更新数据库中的数据:

方法 1 - 更新工单(添加客户的帖子)。 方法 2 - 更新工单的状态(仅当发布成功时)。

这些方法可以一个接一个地调用,也可以单独调用,例如 Method2 可以在票据关闭后从其他方法调用。

Method1,在更新DB之前通过DBManager创建一个事务。然后它更新 DB 并调用 Method2 来做他的事情。 Method2,因为它也可以作为一个独立的方法调用,所以也在更新数据库之前启动事务。执行查询时,它会提交事务并返回到 Method1。在这个阶段,方法 1 也提交事务,因为没有异常,并且它希望保留它对数据库所做的更改。但是,它不能,因为 Method2 已经提交了更改。

所以动作图类似于下图:

Method1()
  DBManager.BeginTransaction() - begins new transaction

  update DB - adds post to the ticket

  Method2() - calls method 2 to update ticket status
    DBManager.BeginTransaction() - returns transaction started by Method1()

    update DB - updates ticket status

    DBManager.CommitTransaction() - commits transaction

    return

  DBManager.CommitTransaction() - commits transaction to save ALL changes but can't since transaction was already committed.

如果我需要在工单状态更新后调用其他方法,则该方法将使用全新的数据集,因为更改已通过 Method2() 提交到数据库中。

我开始考虑如何解决这个问题,但找不到任何东西。我已阅读有关 TransactionScope 的信息,并认为我可以这样做:

public class UnitOfWork : IUnitOfWork, IDisposable
{
    /// <summary>
    /// DB context.
    /// </summary>
    private IDBManager _dbManager;

    /// <summary>
    /// Repository provider class which can create repositories on demand.
    /// </summary>
    private IRepositoryProvider _repositoryProvider;

    private TransactionScope _transaction;

    public UnitOfWork(IDBManager dbManager, IRepositoryProvider repoProvider)
    {
        _dbManager = dbManager;
        _repositoryProvider = repoProvider;
    }

    public T GetRepository<T>()
    {
        if (_transaction == null)
            _transaction = new TransactionScope();

        return _repositoryProvider.Create<T>(_dbManager);
    }

    public void Save()
    {
        _transaction.Complete();
    }

    public void Dispose()
    {
        _transaction.Dispose();
    }
}

在这种情况下,TransactionScope 将在我创建我的第一个存储库时开始,然后我可以在我的控制器中调用 save,如下所示:

    public TicketPost AddTicketPost(int tid, TicketPost update)
    {
        TicketPost post = Uow.GetRepository<ITicketsRepository>().AddPost(tid, update);
        Uow.Save();

        return post;
    }

但是,这意味着将为任何操作创建 TransactionScope - 选择/更新/删除,并且它会从创建第一个存储库的那一刻(即使我可能不需要它)持续到事务的那一刻已处置或已完成。

另一种解决方案是使用 DBManager 的事务并从控制器调用 BeginTransaction,并在需要时提交或回滚。像这样:

Uow.BeginTransaction();
try
{
    TicketPost post = Uow.GetRepository<ITicketsRepository>().AddPost(tid, update);
}
catch (Exception e)
{
    Uow.RollbacTransaction();
}
Uow.CommitTransaction();

但我不太喜欢这种方法。在第一种情况下,我需要捕获异常,这些异常会冒泡到我的 ExceptionsHandler ,这会为客户端创建响应消息。另外,我认为控制器是一个中间人,它收到请求并说“嘿,存储库,这是数据,我已经检查过了,做你的事情,然后给我回电话。”。当它从存储库收到“回电”时,它可能会做一些与数据无关的事情,比如发送电子邮件。我喜欢控制器不需要在同一个存储库中一一调用方法并考虑完成工作需要做的事情,例如:

  1. 更新工单
  2. 设置状态
  3. 用票做别的事
  4. 发送电子邮件

而不是这个,控制器要求存储库处理票证更新并等待它可以发送电子邮件:

  1. 告诉控制器他需要做的任何事情来更新票证。
  2. 等待并发送电子邮件。

我对控制器和存储库的处理方式可能是错误的。如果我错了,请纠正我。

我希望有人可以向我指出一个资源,或者可能有人有类似的设置并且已经找到了针对这种情况的解决方案(交易问题)。

任何帮助将不胜感激。非常感谢您。

【问题讨论】:

    标签: c# entity-framework transactions asp.net-web-api transactionscope


    【解决方案1】:

    如果我的理解正确,您想要一种方法来避免创建不需要的事务?

    为此,我建议您创建一种 BaseUnitOfWork 类型和另外两种类型:ReadOnlyUnitOfWork 和 ReadWriteUnitOfWork。

    然后,在选择内容时只使用 ReadOnly,在需要同时使用 ReadWrite 时。

    C# 中的骨架类似于。

    public class BaseUnitOfWork // YOUR INTERFACES HERE
    {
        /// <summary>
        /// DB context.
        /// </summary>
        private IDBManager _dbManager;
    
        /// <summary>
        /// Repository provider class which can create repositories on demand.
        /// </summary>
        private IRepositoryProvider _repositoryProvider;
    
        public BaseUnitOfWork(IDBManager dbManager, IRepositoryProvider repoProvider)
        {
            _dbManager = dbManager;
            _repositoryProvider = repoProvider;
        }
    
        public T GetRepository<T>()
        {
            return _repositoryProvider.Create<T>(_dbManager);
        }
    }
    
    public class ReadOnlyUnitOfWork : BaseUnitOfWork
    {
        public ReadOnlyUnitOfWork(IDBManager dbManager, IRepositoryProvider repoProvider) : base(dbManager,repoProvider)
        {
            _dbManager = dbManager;
            _repositoryProvider = repoProvider;
        }
    }
    
    public class ReadWriteUnitOfWork : BaseUnitOfWork// YOUR INTERFACES HERE
    {
        private TransactionScope _transaction;
    
        public ReadWriteUnitOfWork(IDBManager dbManager, IRepositoryProvider repoProvider) : base(dbManager,repoProvider)
        {
            if (_transaction == null)
                _transaction = new TransactionScope();
        }
    
        public void Save()
        {
            _transaction.Complete();
        }
    
        public void Dispose()
        {
            _transaction.Dispose();
        }
    }
    

    我已经在几个项目中成功地使用了这个策略。

    此策略的好处在于您遵守 SOLID (http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)) 中的 S 设计:单一责任。

    一个类负责处理带有事务的数据库操作,而另一个只处理无事务操作。

    此外,您必须了解工作单元块应该快速(执行中)并且(如果可能)代码量小(作为最佳实践)。

    因此,您可以像这样使用它:

    using( IReadWriteUnitOfWork uow = InjectionFramework.ResolveDependencyOfType<IReadWriteUnitOfWork>() )
    {
        //do your database stuff here, try to keep it simple.
        //after doing everything, you **commit** the transaction (in your case, you save)
        uow.Save();
    }
    

    using命令的好处在于,在你完成这段代码后,它会自动调用你的dispose方法。

    【讨论】:

    • 哇,太好了。非常感谢你的想法。这个想法是以某种方式强制所有更新/删除操作在我认为您的方法允许的同一事务保护下工作。我还想问是否可以按照我的方式在 UoW 中创建一个 TransactionScope,然后让它一直在那里,打开,在我使用 DB 和执行其他活动的整个时间。或者我可能在这里遗漏了什么?当打开 TransactionScope 时,某些操作可能会有所不同,而不仅仅是 DB 请求?
    • 我正在想办法使用您建议的方法。正如我所提到的,我使用 Ninject。它将 UoW 注入到我的控制器中,然后我使用它来创建存储库。现在,我需要注入某种 UoW 工厂而不是 UoW,通过它我将创建所需的 UoW 类型对象。我猜
    • 嗯,关于您的第一条评论,您必须了解工作单元块应该尽可能快(并且小)。否则,您将开始遇到数据库性能问题。因此,在使用 uow 时,您应该尽量保持它非常小。我将编辑答案,以举例说明。
    • 然后,对于您的第二条评论,您可以像我展示的那样使用它:InjectionContainer.Resolve.... 您可以在注入容器配置块中定义将 IReadWrite 解析为 ReadWrite 并将 IReadOnly 解析为只读。 (您只需要从 ReadWrite 和 ReadOnly 实现类中提取接口。但不要忘记对基类也使用接口)
    • 另外,如果这能解决您的问题,请不要忘记“接受”它作为答案。 :) 我正在运行以在堆栈溢出附近获得一些声誉。 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-03
    • 2013-01-11
    相关资源
    最近更新 更多