将 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 一起使用的正确方法的主要内容,如果未能解决你的问题,请参考以下文章
Web3与智能合约:开发一个简单的DApp并部署到以太坊测试网(Solidity+Hardhat+React)① 环境搭建