如何解决 Dapper - UnitOfWork 事务错误
How to solve Dapper - UnitOfWork Transaction Error
我正在尝试在 ASP.NET Core Web API.
中使用 Dapper 实现工作单元存储库模式
我已经创建了模型、存储库和 UOW。当我尝试执行 GET 请求时出现错误
System.InvalidOperationException: BeginExecuteReader requires the
command to have a transaction when the connection assigned to the
command is in a pending local transaction. The Transaction property
of the command has not been initialized.
这是我的控制器;
public class CityController : ControllerBase
{
private readonly IUnitOfWork unitOfWork;
public CityController(IUnitOfWork unitOfWork)
{
this.unitOfWork = unitOfWork;
}
[HttpGet]
public async Task<IEnumerable<City>> GetAll()
{
return await unitOfWork.Cities.All();
}
CityRepository.cs
internal class CityRepository : GenericRepository<City>, ICityRepository
{
public CityRepository(IDbTransaction transaction)
: base(transaction)
{
}
public async Task<IEnumerable<City>> All()
{
var model = await Connection.QueryAsync<City>("SELECT * FROM DT_Inspection.City");
return model.ToList();
}
}
public IConfiguration configuration;
private IDbTransaction _transaction;
private IDbConnection _connection;
ICityRepository _cityRepository;
private bool _disposed;
public UnitOfWork(IConfiguration _configuration)
{
configuration = _configuration;
_connection = new SqlConnection(_configuration.GetConnectionString("DefaultConnection"));
_connection.Open();
_transaction = _connection.BeginTransaction();
}
public ICityRepository Cities { get { return _cityRepository ?? (_cityRepository = new CityRepository(_transaction)); }
public void Commit()
{
try
{
_transaction.Commit();
}
catch
{
_transaction.Rollback();
throw;
}
finally
{
_transaction.Dispose();
_transaction = _connection.BeginTransaction();
resetRepositories();
}
}
对于初学者来说,这与工作单元完全相反。工作单元意味着你有一个单一的,不可分割的一堆(unit)操作(work)需要作为一个整体提交或丢弃。一旦完成,它就消失了,不能重复使用。这是一个功能。
UoW 通常意味着工作在提交之前不会影响数据源,但是您的代码启动了一个昂贵的长期事务,确实 锁定记录初读。
您使用的 class 创建了一个 全局 长期连接和一个全局隐式事务。这是一个非常糟糕的做法。具体来说,这些行是一个主要错误:
_transaction = _connection.BeginTransaction();
resetRepositories();
您可以通过一些连接设置在任何数据库中实现相同的效果,但是很少很少有人这样做。
数据库连接和事务应该短暂。否则它们会在服务器上积累锁并占用资源,导致不同事务之间发生阻塞甚至死锁。否则,您可能 运行 陷入死锁或长时间延迟,即使有几个并发客户端也是如此。在引入断开连接操作和乐观并发之前,这在 1990 年代是一个巨大的问题。您尝试做的事情让您回到了 1990 年代。
真正的区别是性能差 1000 倍,并且必须使用 10 倍以上的数据库服务器s 来处理相同的流量。
这就是为什么文档、课程和教程(好的)都显示在使用之前创建的连接和事务:
using(var cn=new SqlConnection(...))
{
cn.Open();
using(var tx=cn.BeginTransaction())
{
using (var cmd1=new SqlCommand(sql1,cn,tx))
{
...
}
using (var cmd2=new SqlCommand(sql2,cn,tx))
{
...
}
}
}
如果使用显式数据库事务,则必须将活动事务传递给命令本身。这就是你得到的例外所说的。另一种方法是使用 TransactionScope
并在其中 创建开放连接。在这种情况下,连接隐式注册到事务中:
using(var cn=new SqlConnection(...))
{
using(var scope=new TransactionScope())
{
cn.Open();
using (var cmd=new SqlCommand(sql,cn))
{
...
}
...
}
}
Dapper
是 ADO.NET 上的精简映射器,它不会取代它。这意味着您仍然必须正确使用 ADO.NET、连接和事务。如果要使用显式事务,需要通过transaction
参数传递给Query
或Execute
:
using(var cn=new SqlConnection(...))
{
cn.Open();
using(var tx=cn.BeginTransaction())
{
var results1=cn.QueryAsync<City>(sql1,transaction:tx);
var results2=cn.QueryAsync<City>(sql2,transaction:tx);
}
}
或者您可以使用 TransactionScope
:
using(var scope=new TransactionScope())
{
using(var cn=new SqlConnection(...))
{
cn.Open();
var results1=cn.QueryAsync<City>(sql1);
var results2=cn.QueryAsync<City>(sql2);
}
}
实现已经泄露。 “Repository”(实际上是 Data Access Object,而不是 Repository)需要访问 _transaction
字段的值。或者您可以使用 TransactionScope
而忘记 UoW
。毕竟,访问数据库是 DAO/Repository 的 的工作,而不是 UoW 的。 也许 你可以使用 UoW
作为 TransactionScope
的薄包装,或者让 Repository
创建并初始化 UoW
来自其拥有的连接的显式事务。
假设您使用 TransactionScope
,您的 UoW 应该只是一个包装器:
class UnitOfWork:IDisposable
{
TransactionScope _scope=new TransactionScope();
public void Dispose()
{
_scope.Dispose();
}
}
“存储库”甚至不应该知道 UoW。它 应该 控制连接:
internal class CityRepository
{
string _connString;
public CityRepository(IConfiguration configuration)
{
_connString=configuration.GetConnectionString("DefaultConnection")
}
public async Task<IEnumerable<City>> All()
{
using(var cn=new SqlConnection(_connStr))
{
var model = await Connection.QueryAsync<City>("SELECT * FROM DT_Inspection.City");
return model.ToList();
}
}
}
只有控制器需要创建 UoW,然后,只有在有机会修改数据时才需要。读取不需要事务:
public class CityController : ControllerBase
{
private ICityRepository _cityRepo;
public CityController(ICityRepository cityRepo)
{
_cityRepo=cityRepo;
}
[HttpGet]
public Task<IEnumerable<City>> GetAll()
{
return _cityRepo.All();
}
[HttpPost]
public async Task Post(City[] cities)
{
using(var uow=new UnitOfWork())
{
foreach(var city in cities)
{
_cityRepo.Insert(city);
}
}
}
我正在尝试在 ASP.NET Core Web API.
中使用 Dapper 实现工作单元存储库模式我已经创建了模型、存储库和 UOW。当我尝试执行 GET 请求时出现错误
System.InvalidOperationException: BeginExecuteReader requires the command to have a transaction when the connection assigned to the command is in a pending local transaction. The Transaction property of the command has not been initialized.
这是我的控制器;
public class CityController : ControllerBase
{
private readonly IUnitOfWork unitOfWork;
public CityController(IUnitOfWork unitOfWork)
{
this.unitOfWork = unitOfWork;
}
[HttpGet]
public async Task<IEnumerable<City>> GetAll()
{
return await unitOfWork.Cities.All();
}
CityRepository.cs
internal class CityRepository : GenericRepository<City>, ICityRepository
{
public CityRepository(IDbTransaction transaction)
: base(transaction)
{
}
public async Task<IEnumerable<City>> All()
{
var model = await Connection.QueryAsync<City>("SELECT * FROM DT_Inspection.City");
return model.ToList();
}
}
public IConfiguration configuration;
private IDbTransaction _transaction;
private IDbConnection _connection;
ICityRepository _cityRepository;
private bool _disposed;
public UnitOfWork(IConfiguration _configuration)
{
configuration = _configuration;
_connection = new SqlConnection(_configuration.GetConnectionString("DefaultConnection"));
_connection.Open();
_transaction = _connection.BeginTransaction();
}
public ICityRepository Cities { get { return _cityRepository ?? (_cityRepository = new CityRepository(_transaction)); }
public void Commit()
{
try
{
_transaction.Commit();
}
catch
{
_transaction.Rollback();
throw;
}
finally
{
_transaction.Dispose();
_transaction = _connection.BeginTransaction();
resetRepositories();
}
}
对于初学者来说,这与工作单元完全相反。工作单元意味着你有一个单一的,不可分割的一堆(unit)操作(work)需要作为一个整体提交或丢弃。一旦完成,它就消失了,不能重复使用。这是一个功能。
UoW 通常意味着工作在提交之前不会影响数据源,但是您的代码启动了一个昂贵的长期事务,确实 锁定记录初读。
您使用的 class 创建了一个 全局 长期连接和一个全局隐式事务。这是一个非常糟糕的做法。具体来说,这些行是一个主要错误:
_transaction = _connection.BeginTransaction();
resetRepositories();
您可以通过一些连接设置在任何数据库中实现相同的效果,但是很少很少有人这样做。
数据库连接和事务应该短暂。否则它们会在服务器上积累锁并占用资源,导致不同事务之间发生阻塞甚至死锁。否则,您可能 运行 陷入死锁或长时间延迟,即使有几个并发客户端也是如此。在引入断开连接操作和乐观并发之前,这在 1990 年代是一个巨大的问题。您尝试做的事情让您回到了 1990 年代。
真正的区别是性能差 1000 倍,并且必须使用 10 倍以上的数据库服务器s 来处理相同的流量。
这就是为什么文档、课程和教程(好的)都显示在使用之前创建的连接和事务:
using(var cn=new SqlConnection(...))
{
cn.Open();
using(var tx=cn.BeginTransaction())
{
using (var cmd1=new SqlCommand(sql1,cn,tx))
{
...
}
using (var cmd2=new SqlCommand(sql2,cn,tx))
{
...
}
}
}
如果使用显式数据库事务,则必须将活动事务传递给命令本身。这就是你得到的例外所说的。另一种方法是使用 TransactionScope
并在其中 创建开放连接。在这种情况下,连接隐式注册到事务中:
using(var cn=new SqlConnection(...))
{
using(var scope=new TransactionScope())
{
cn.Open();
using (var cmd=new SqlCommand(sql,cn))
{
...
}
...
}
}
Dapper
是 ADO.NET 上的精简映射器,它不会取代它。这意味着您仍然必须正确使用 ADO.NET、连接和事务。如果要使用显式事务,需要通过transaction
参数传递给Query
或Execute
:
using(var cn=new SqlConnection(...))
{
cn.Open();
using(var tx=cn.BeginTransaction())
{
var results1=cn.QueryAsync<City>(sql1,transaction:tx);
var results2=cn.QueryAsync<City>(sql2,transaction:tx);
}
}
或者您可以使用 TransactionScope
:
using(var scope=new TransactionScope())
{
using(var cn=new SqlConnection(...))
{
cn.Open();
var results1=cn.QueryAsync<City>(sql1);
var results2=cn.QueryAsync<City>(sql2);
}
}
实现已经泄露。 “Repository”(实际上是 Data Access Object,而不是 Repository)需要访问 _transaction
字段的值。或者您可以使用 TransactionScope
而忘记 UoW
。毕竟,访问数据库是 DAO/Repository 的 的工作,而不是 UoW 的。 也许 你可以使用 UoW
作为 TransactionScope
的薄包装,或者让 Repository
创建并初始化 UoW
来自其拥有的连接的显式事务。
假设您使用 TransactionScope
,您的 UoW 应该只是一个包装器:
class UnitOfWork:IDisposable
{
TransactionScope _scope=new TransactionScope();
public void Dispose()
{
_scope.Dispose();
}
}
“存储库”甚至不应该知道 UoW。它 应该 控制连接:
internal class CityRepository
{
string _connString;
public CityRepository(IConfiguration configuration)
{
_connString=configuration.GetConnectionString("DefaultConnection")
}
public async Task<IEnumerable<City>> All()
{
using(var cn=new SqlConnection(_connStr))
{
var model = await Connection.QueryAsync<City>("SELECT * FROM DT_Inspection.City");
return model.ToList();
}
}
}
只有控制器需要创建 UoW,然后,只有在有机会修改数据时才需要。读取不需要事务:
public class CityController : ControllerBase
{
private ICityRepository _cityRepo;
public CityController(ICityRepository cityRepo)
{
_cityRepo=cityRepo;
}
[HttpGet]
public Task<IEnumerable<City>> GetAll()
{
return _cityRepo.All();
}
[HttpPost]
public async Task Post(City[] cities)
{
using(var uow=new UnitOfWork())
{
foreach(var city in cities)
{
_cityRepo.Insert(city);
}
}
}