1. 从一次深夜报错说起:Transaction属性未初始化的“幽灵”
那天晚上十一点,我正赶着上线一个用户积分更新的功能。逻辑很简单:用户完成一个任务,系统要同时更新他的积分总额,并在积分明细表里插入一条记录。为了保证数据一致性,我自然想到了用事务。代码写得飞快,用的是咱们.NET圈里轻量又高效的Dapper。核心代码大概长这样:
using (var connection = new SqlConnection(connStr))
{
connection.Open();
var transaction = connection.BeginTransaction();
try
{
// 更新用户总积分
connection.Execute("UPDATE Users SET Points = Points + @AddedPoints WHERE UserId = @UserId",
new { UserId = userId, AddedPoints = points }, transaction);
// 插入积分明细记录
connection.Execute("INSERT INTO PointDetails (UserId, Points, Reason) VALUES (@UserId, @Points, @Reason)",
new { UserId = userId, Points = points, Reason = "完成任务奖励" });
transaction.Commit();
Console.WriteLine("操作成功!");
}
catch (Exception ex)
{
transaction.Rollback();
Console.WriteLine($"操作失败,已回滚: {ex.Message}");
}
}
看起来没什么毛病对吧?我信心满满地跑起来,结果在第二句 Execute 那里,程序“啪”地一下抛出了一个异常,就是我标题里提到的那个:“如果分配给命令的连接位于本地挂起事务中,ExecuteNonQuery 要求命令拥有事务。命令的 Transaction 属性尚未初始化。”
当时我就懵了,明明开启了事务,也传给了第一个操作,怎么第二个就“属性未初始化”了?这错误信息对新手来说确实有点绕,它其实是说:你的数据库连接(Connection)已经处在一个本地挂起的事务(Local Pending Transaction)里了,但你现在要执行的这个命令(Command),它的 Transaction 属性是空的(null),系统不知道它该属于哪个事务,所以拒绝执行。
简单翻译成人话就是:你拉着连接进了一个事务的“房间”,然后让这个连接去干活(执行SQL),却没告诉它这次干活要遵守这个“房间”(事务)的规则。Dapper(或者说底层的ADO.NET)很严格,它不允许这种模糊状态,必须明确指定。这个错误在批量操作、循环插入或者像我这样连续执行多条不同SQL时特别容易踩坑。很多刚接触Dapper事务的朋友,包括当年的我,都在这儿栽过跟头。下面,我们就来把这个坑彻底填平。
2. 刨根问底:为什么Transaction会“丢失”?
要避免错误,首先得理解它为什么发生。我们得深入到Dapper和ADO.NET的协作层面去看。当你调用 connection.BeginTransaction() 时,本质上是在这个特定的数据库连接上启动了一个事务。这个事务对象(IDbTransaction)和这个连接是绑定的。
关键点在于 Dapper的 Execute 方法重载。Dapper提供了非常灵活的方法签名,其中与事务相关的典型重载是:Execute(string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)。
注意那个 IDbTransaction transaction = null 参数,它有一个默认值 null。这就是问题的根源!
场景还原:
- 你通过
BeginTransaction()拿到了一个事务对象trans。 - 第一次调用
Execute(sql1, param, trans),你明确传入了trans。Dapper在内部创建命令对象时,会把这个trans赋值给命令的Transaction属性。命令知道:“哦,我要在trans这个事务里执行。” 执行成功。 - 第二次调用
Execute(sql2, param),你省略</


375

被折叠的 条评论
为什么被折叠?



