【问题标题】:Proper way of using BeginTransaction with Dapper.IDbConnection将 BeginTransaction 与 Dapper.IDbConnection 一起使用的正确方法
【发布时间】:2014-08-30 07:17:09
【问题描述】:

在 Dapper 中使用 BeginTransaction()IDbConnection 的正确方法是什么?

我创建了一个必须使用BeginTransaction() 的方法。这是代码。

using (IDbConnection cn = DBConnection)
{
    var oTransaction = cn.BeginTransaction();

    try
    {
        // SAVE BASIC CONSULT DETAIL
        var oPara = new DynamicParameters();
        oPara.Add("@PatientID", iPatientID, dbType: DbType.Int32);
        ..........blah......blah............
    }
    catch (Exception ex)
    {
        oTransaction.Rollback();
        return new SaveResponse { Success = false, ResponseString = ex.Message };
    }
}

当我执行上述方法时 - 我遇到了异常 -

无效操作。连接已关闭。

这是因为您无法在连接打开之前开始事务。所以当我添加这一行时:cn.Open();,错误得到解决。但我在某处读到手动打开连接是不好的做法! Dapper 仅在需要时打开连接。

在实体框架中,您可以使用TransactionScope 处理事务。

所以我的问题是在 Dapper 中不添加 cn.Open()... 行的情况下处理交易的好习惯是什么?我想应该有一些适当的方法。

【问题讨论】:

    标签: c# .net orm dapper idbconnection


    【解决方案1】:

    手动打开连接并不是“坏习惯”; dapper 使用打开或关闭的连接为了方便,仅此而已。一个常见的问题是人们的连接处于打开状态、未使用状态、时间过长而从未将它们释放到池中 - 但是,在大多数情况下这不是问题,您当然可以这样做:

    using(var cn = CreateConnection()) {
        cn.Open();
        using(var tran = cn.BeginTransaction()) {
            try {
                // multiple operations involving cn and tran here
    
                tran.Commit();
            } catch {
                tran.Rollback();
                throw;
            }
        }
    }
    

    注意dapper有一个可选参数传入事务,例如:

    cn.Execute(sql, args, transaction: tran);
    

    我实际上很想IDbTransaction 上创建类似的扩展方法,因为a transaction always exposes .Connection;这将允许:

    tran.Execute(sql, args);
    

    但这在今天不存在。

    TransactionScope 是另一种选择,但具有不同的语义:这可能涉及 LTM 或 DTC,这取决于......好吧,主要是运气。围绕IDbTransaction 创建一个不需要try/catch 的包装器也很诱人——更像TransactionScope 的工作方式;类似的东西(这也不存在):

    using(var cn = CreateConnection())
    using(var tran = cn.SimpleTransaction())
    {
        tran.Execute(...);
        tran.Execute(...);
    
        tran.Complete();
    }
    

    【讨论】:

    • FFR:这是建议但作为 PR 被拒绝 :( github.com/StackExchange/dapper-dot-net/pull/429 Marc 也参与了讨论。被拒绝主要是因为同步/异步之间已经存在重复 - 为事务添加扩展方法会导致所有方法被重复 4 次。
    • @marc-gravell - 在回滚的情况下,是否必须显式调用tran.RollBack?事务在 dispose 时不会自动回滚吗?
    【解决方案2】:

    你不应该打电话

    cn.Close();
    

    因为 using 块也会尝试关闭。 对于事务部分,是的,您也可以使用 TransactionScope,因为它不是与实体框架相关的技术。 看看这个 SO 答案:https://stackoverflow.com/a/6874617/566608 它解释了如何在事务范围内登记您的连接。 重要的方面是:连接会自动加入事务 IIF 您在范围内打开连接

    【讨论】:

    • 是的,你是对的,对不起,我忘了删除它。因此,您提供的链接说您可以将 TransactionScope 与 Dapper 一起使用,但您必须编写此代码 - con.Open()。那么这是一个好习惯吗??
    • 当然要先打开连接才能使用
    【解决方案3】:

    看看Tim Schreiber 解决方案,它简单但功能强大,使用存储库模式实现,并牢记Dapper Transactions

    下面代码中的Commit() 显示了它。

    public class UnitOfWork : IUnitOfWork
    {
        private IDbConnection _connection;
        private IDbTransaction _transaction;
        private IBreedRepository _breedRepository;
        private ICatRepository _catRepository;
        private bool _disposed;
    
        public UnitOfWork(string connectionString)
        {
            _connection = new SqlConnection(connectionString);
            _connection.Open();
            _transaction = _connection.BeginTransaction();
        }
    
        public IBreedRepository BreedRepository
        {
            get { return _breedRepository ?? (_breedRepository = new BreedRepository(_transaction)); }
        }
    
        public ICatRepository CatRepository
        {
            get { return _catRepository ?? (_catRepository = new CatRepository(_transaction)); }
        }
    
        public void Commit()
        {
            try
            {
                _transaction.Commit();
            }
            catch
            {
                _transaction.Rollback();
                throw;
            }
            finally
            {
                _transaction.Dispose();
                _transaction = _connection.BeginTransaction();
                resetRepositories();
            }
        }
    
        private void resetRepositories()
        {
            _breedRepository = null;
            _catRepository = null;
        }
    
        public void Dispose()
        {
            dispose(true);
            GC.SuppressFinalize(this);
        }
    
        private void dispose(bool disposing)
        {
            if (!_disposed)
            {
                if(disposing)
                {
                    if (_transaction != null)
                    {
                        _transaction.Dispose();
                        _transaction = null;
                    }
                    if(_connection != null)
                    {
                        _connection.Dispose();
                        _connection = null;
                    }
                }
                _disposed = true;
            }
        }
    
        ~UnitOfWork()
        {
            dispose(false);
        }
    }
    

    【讨论】:

    • 很好。有几个关于解决方案的问题。如果不想使用事务让我们说通常的选择查询怎么办?那么,据我了解,sql 会在 commit() 之后为事务生成代码还是什么?如果我不会在查询中使用它,为什么我需要做 BeginTransaction()?它会影响我不需要事务的查询的性能吗?请不要理解我的错误。在我开始在生产中使用它之前,我只想澄清所有事情。
    • 所以,我认为最好是添加标志(useTransaction = false)。在这种情况下,创建 unitOfWork 的实例,我们可以选择我们需要的策略。我说的对吗?
    • 当您的查询只是SELECT 时,您不需要commit()。所以不用担心性能!您关于添加标志的想法很好,但实际上,没有必要。我以这种方式使用它,它就像一个魅力。
    • 您能解释一下为什么在 catch 块中调用了 _transaction.RollBack() 却在 finally 块中设置了 _transaction 吗?
    • @EfeZaladin finally 块肯定会运行,因此无论哪种方式都需要处理对象。如果try 成功,_transaction 应该被释放,如果出现问题,_transaction 应该被回滚,在这两种情况下,它都会被最终释放。
    【解决方案4】:

    Dapper 有两种使用交易的预期方式。

    1. 将您的 IDbTranasction 传递给您的正常 Dapper 呼叫。

      之前:

      var affectedRows = connection.Execute(sql, new {CustomerName = "Mark"});
      

      之后:

      var affectedRows = connection.Execute(sql, new {CustomerName = "Mark"}, transaction=tx);
      
    2. 使用 Dapper 添加到 IDbTransaction 本身的新 .Execute 扩展方法:

      tx.Execute(sql, new {CustomerName = "Mark"});
      

    注意:变量tx来自IDbTransaction tx = connection.BeginTransaction();

    这就是你应该如何使用 Dapper 的交易方式;它们都不是 TransactionScope。

    阅读奖励

    【讨论】:

      猜你喜欢
      • 2011-10-05
      • 2015-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多