将 BeginTransaction 与 Dapper.IDbConnection 一起使用的正确方法

Posted

技术标签:

【中文标题】将 BeginTransaction 与 Dapper.IDbConnection 一起使用的正确方法【英文标题】:Proper way of using BeginTransaction with 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()... 行的情况下处理交易的好习惯是什么?我想应该有一些适当的方法。

【问题讨论】:

【参考方案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://***.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 有两种使用交易的预期方式。

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

    之前:

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

    之后:

    var affectedRows = connection.Execute(sql, new CustomerName = "Mark", transaction=tx);
    

    使用 Dapper 添加到 IDbTransaction 本身的新 .Execute 扩展方法:

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

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

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

阅读奖励

https://***.com/a/67474832/12597

【讨论】:

以上是关于将 BeginTransaction 与 Dapper.IDbConnection 一起使用的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

处理以太坊 DApp 中的用户资料

DAPP与钱包不得不说的事情

DAPP系统开发,场外币币交易所系统开发

如何理解太极链DAPP

Web3与智能合约:开发一个简单的DApp并部署到以太坊测试网(Solidity+Hardhat+React)① 环境搭建

记 通过ganache与以太坊Dapp实现交互