首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何解决Dapper - UnitOfWork事务错误

如何解决Dapper - UnitOfWork事务错误
EN

Stack Overflow用户
提问于 2021-08-09 09:33:38
回答 1查看 1.6K关注 0票数 0

我试图在ASP.NET Core中使用Dapper实现工作存储库模式。

我已经创建了模型、存储库和UOW。当我试图获得请求时,我得到了一个错误

System.InvalidOperationException:当分配给命令的连接位于挂起的本地事务中时,BeginExecuteReader要求命令具有事务。命令的事务属性尚未初始化。

这是我的控制器;

代码语言:javascript
运行
复制
 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

代码语言:javascript
运行
复制
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();

        }
}
代码语言:javascript
运行
复制
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();
            }
        }
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-08-09 10:22:20

首先,这与工作单位正好相反。工作单位意味着你有一个单一的,不可分割的一组操作(工作),需要作为一个整体提交或丢弃。一旦它完成,它就消失了,不能被重用。这是个特征。

UoW通常意味着工作在提交之前不会影响数据源,但是您的代码会启动一个昂贵的长时间事务,该事务会从第一次读取开始就锁定记录。

不过,您使用的类创建了一个全局长寿命连接和一个全局隐式事务。这是个很糟糕的练习。具体而言,这些行是一个主要的错误:

代码语言:javascript
运行
复制
_transaction = _connection.BeginTransaction();
resetRepositories();

您可以通过某些连接设置在任何数据库中实现相同的效果,但是很少有人这样做。

数据库连接和事务应该是短暂的.否则,它们会累积锁并将服务器上的资源捆绑起来,从而导致阻塞,甚至导致不同事务之间的死锁。否则,即使有几个并发客户端,您也可能遇到死锁或长时间延迟。在上世纪90年代引入中断操作和乐观并发之前,这是一个巨大的问题。你想要做的事情让你回到了90年代。

不同之处在于性能下降了1000倍,必须使用10x+更多的数据库服务器来处理相同数量的通信量。

这就是为什么文档、课程和教程(好的)都显示在使用之前创建的连接和事务:

代码语言:javascript
运行
复制
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并在其中创建打开的连接。在这种情况下,连接隐式地注册到事务中:

代码语言:javascript
运行
复制
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参数传递给QueryExecute

代码语言:javascript
运行
复制
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

代码语言:javascript
运行
复制
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“(实际上是数据访问对象,而不是Repository)需要访问_transaction字段的值。或者你可以使用一个TransactionScope,然后忘记那个UoW。毕竟,对数据库的访问是DAO/Repository的工作,而不是UoW的工作。也许您可以使用UoW作为TransactionScope的瘦包装器,或者让Repository使用它拥有的连接上的显式事务创建和初始化UoW

假设您使用的是TransactionScope,那么您的UoW应该只是一个包装器:

代码语言:javascript
运行
复制
class UnitOfWork:IDisposable
{
    TransactionScope _scope=new TransactionScope();

    public void Dispose()
    {
        _scope.Dispose();
    }
}

“存储库”甚至不应该知道UoW。但是,它应该控制连接:

代码语言:javascript
运行
复制
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,然后,只有在有机会修改数据的情况下才需要。读取不需要事务:

代码语言:javascript
运行
复制
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);
            }
        }
    }
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68709825

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档