事务

目前的事务实现,我们推荐还是在Services层来定义事务限界,以下的示例代码均是Services层下的实现。

常规事务 UnitOfWork

在使用仓储类来做事务管控的情况下,也就是使用Repository来做增删改查而不是IFreesql的情况下我们推荐使用UnitOfWork事务方式,在之后的版本会引入自动绑定的快速事务。

工作单元绑定

使用仓储类事务UnitOfWork的情况下就必不可免的涉及到工作单元绑定。

using (var uow = _freeSql.CreateUnitOfWork())
{
  _ipVisitRepository.UnitOfWork = uow; //手工绑定工作单元
  _userRepository.UnitOfWork = uow; //手工绑定工作单元

    try
    {
        //绑定后的仓储类数据库操作就是参与事务的。
      _ipVisitRepository.InsertNewIpVisit(...);
      _userRepository.Update(...);

      uow.Commit();
    }
    catch(Exception ex)
    {
        _logger.Error(ex,"...事务发生回退");
        uow.Rollback();
    }
}

临时获取事务仓储实例

如果不想绑定,也可以获取临时的事务用的仓储类。

using (var uow = _freeSql.CreateUnitOfWork())
{
    var ipVisitRepository = uow.GetRepository<IpVisitEntity>(); //临时获取事务内仓储
    var userRepository = uow.GetRepository<UserEntity>(); //临时获取事务内仓储

    try
    {
        //绑定后的仓储类数据库操作就是参与事务的。
      ipVisitRepository.InsertNewIpVisit(...);
      userRepository.Update(...);

      uow.Commit();
    }
    catch(Exception ex)
    {
        _logger.Error(ex,"...事务发生回退");
        uow.Rollback();
    }
}

事务中使用IFreesql实例

如果想在事务里获取IFreesql实例来实现更多动态功能的情况下,请使用仓储类里的Orm,而不是外部定义的_freeSql。外部定义的_freeSql并不包含在事务里。

错误示范

private readonly IFreeSql<HisDb> _freeSql;

public void InsertTestUser()
{
    using (var uow = _freeSql.CreateUnitOfWork())
    {
        _freeSql.Insert(new UserEntity()).ExecuteAffrows();
    }
    ...
}

正确示范

private readonly IFreeSql<HisDb> _freeSql;
private readonly IpVisitRepository _ipVisitRepository;

public void InsertTestUser()
{
    using (var uow = _freeSql.CreateUnitOfWork())
    {
        _ipVisitRepository.UnitOfWork = uow; //绑定
        var userRepository = uow.GetRepository<UserEntity>(); //临时获取事务内仓储、

        _ipVisitRepository.Orm.Insert(new IpVisitEnity()).ExecuteAffrows(); //正常
        userRepository.Orm.Insert(new UserEntity()).ExecuteAffrows(); //正常
        uow.Orm.Insert(new UserEntity()).ExecuteAffrows(); //正常

    }
    ...
}

Repository的注意事项

强烈建议在Repository层下单表的增删改查都使用仓储类本身(也就是this)

//当前表直接使用当前仓储
public async Task<PatientInfoEntity> GetPatientInfo(string seq)
{
    bool canParse = long.TryParse(seq, out long id);

    return await this
        .WhereIf(canParse, x => x.PatientSeq == id || x.OldPatientSeq == seq)
        .WhereIf(!canParse,x => x.OldPatientSeq == seq)
        .WithLock(SqlServerLock.NoLock)
        .FirstAsync();
}

只有多表或者复杂查询才使用IFreesql实例。但是更推荐使用当前仓储类的Orm而不是直接使用注入进来的IFreesql,这样在使用外部事务的时候能正常处理。(下述代码是对应仓储类的代码)

//多表联查使用IFreesql实例
decimal total = await this.Orm.Select<IpVisitPrePayEntity, IpVisitEntity>().WithLock(SqlServerLock.NoLock)
         .LeftJoin((a, b) => a.IpVisitSeq.Equals(b.VisitSeq))
         .Where((a, b) => b.PMINo.Equals(ipVisit.PMINo) && a.PrePayStatus == PrePayStatusEnum.正常
                && a.SettledFlag == false)
         .WhereIf(item.PrePayStatus > 0, (a, b) => a.PrePayStatus.Equals(item.PrePayStatus))
         .SumAsync((a, b) => a.PrePayAmount);

传统事务 DbTransaction

常规情况下还是更建议使用UnitOfWork事务,在之后扩展为分布式事务会更方便,传统的Transaction事务的数据库表与表之间的耦合性极高。

同时传统事务不推荐使用异步事务BeginTransactionAsync,异步事务不能确保事务的及时性,代码里存在大量的NoLock,异步事务执行的同时执行查询极其容易出现幻读等数据读取错误的常见问题。


public async Task CreateAsync(CreateGroupDto inputDto)
{
    using Object<DbConnection> conn = _freeSql.Ado.MasterPool.Get();
    using DbTransaction transaction = conn.Value.BeginTransaction();
    try
    {
        await fsql.Update<xxx>()
        .WithTransaction(transaction)
        .Set(a => a.Clicks + 1)
        .ExecuteAffrowsAsync();

        transaction.Commit();
    }
    catch (Exception ex)
    {
        transaction.Rollback();
        ...
    } 
}

分布式事务 TCC/Saga

待补充...

results matching ""

    No results matching ""