【ASP.NET CORE】3.ORM事务管理

介绍

本次完善ORM仓储层的事务管理,链式调用开启/关闭事务 支持配置数据库隔离级别。

实现

1.异步流隔离

private static readonly AsyncLocal<bool> _withoutTransaction = new AsyncLocal<bool>();
private static readonly AsyncLocal<IsolationLevel?> _isolationLevel = new AsyncLocal<IsolationLevel?>();

AsyncLocal 是线程安全的异步上下文存储,确保每个异步流的事务配置独立,不会相互污染

2.流式API设计

// 链式调用
await _repository.WithoutTransaction().GetListAsync();  // 无事务查询
await _repository.WithIsolationLevel(IsolationLevel.Serializable).AddAsync(entity); // 指定隔离级别

3.统一执行入口

private async Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> operation)
{
    // 读取当前流的配置
    bool withoutTran = _withoutTransaction.Value;
    IsolationLevel? level = _isolationLevel.Value;
    ResetTransactionFlags(); // 用完立即重置,防止污染下一次操作
    
    if (withoutTran)
    {
        return await operation(); // 裸执行,无事务
    }
    
    // 自动开启事务(默认 ReadCommitted)
    await _db.Ado.BeginTranAsync(level ?? IsolationLevel.ReadCommitted);
    try {
        var result = await operation();
        await _db.Ado.CommitTranAsync();
        return result;
    }
    catch {
        await _db.Ado.RollbackTranAsync();
        throw;
    }
}

特点

特性说明
零侵入默认自动开启事务,业务代码无感知
灵活性通过 WithoutTransaction() 跳过事务(适合纯查询)
隔离性AsyncLocal 保证并发安全,每个请求独立配置
防御性ResetTransactionFlags() 防止配置残留导致意外行为
防御编程回滚时用 try-catch 包裹,避免回滚失败时丢失原始异常

使用示例

public class UserService 
{
    private readonly BaseRepository<User, string> _userRepo;
    
    public async Task CreateUserWithOrder(User user, Order order) 
    {
        // 仓储层自动管理事务,Service层专注业务
        await _userRepo.AddAsync(user);
        // 如果这里抛出异常,事务自动回滚
    }
    
    public async Task<List<User>> GetHotUsers() 
    {
        // 纯查询,显式声明不需要事务,性能更好
        return await _userRepo.WithoutTransaction()
                              .GetListAsync();
    }
}

完整代码

using System.Linq.Expressions;
using System.Data;
using SqlSugar;

namespace HQ.Common.ORM.SQLSugar;

public class BaseRepository<T, TKey> : IBaseRepository<T, TKey> where T : BaseEntity<TKey>, new()
{
    protected ISqlSugarClient _db;
    
    // 异步流隔离 防止并发事务混乱
    private static readonly AsyncLocal<bool> _withoutTransaction = new AsyncLocal<bool>();
    private static readonly AsyncLocal<IsolationLevel?> _isolationLevel = new AsyncLocal<IsolationLevel?>();
    
    public Func<Task> PreOperationHandler { get; set; }

    public BaseRepository(ISqlSugarClient db)
    {
        _db = db;
    }

    public BaseRepository<T, TKey> WithoutTransaction()
    {
        _withoutTransaction.Value = true;
        return this;
    }

    public BaseRepository<T, TKey> WithIsolationLevel(IsolationLevel level)
    {
        _isolationLevel.Value = level;
        return this;
    }

    private void ResetTransactionFlags()
    {
        _withoutTransaction.Value = false;
        _isolationLevel.Value = null;
    }

    private async Task ExecutePreHandlerAsync()
    {
        if (PreOperationHandler != null)
        {
            await PreOperationHandler.Invoke();
        }
    }

    private async Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> operation)
    {
        bool withoutTran = _withoutTransaction.Value;
        IsolationLevel? level = _isolationLevel.Value;
        ResetTransactionFlags();
        
        try
        {
            await ExecutePreHandlerAsync();

            if (withoutTran)
            {
                return await operation();
            }

            var isolationLevel = level ?? IsolationLevel.ReadCommitted;
            bool isTranStarted = false;
            
            try
            {
                await _db.Ado.BeginTranAsync(isolationLevel);
                isTranStarted = true;
                
                var result = await operation();
                await _db.Ado.CommitTranAsync();
                return result;
            }
            catch (Exception)
            {
                if (isTranStarted)
                {
                    try { await _db.Ado.RollbackTranAsync(); } 
                    catch { }
                }
                throw;
            }
        }
        catch
        {
            throw;
        }
    }

    private async Task ExecuteAsync(Func<Task> operation)
    {
        await ExecuteAsync(async () =>
        {
            await operation();
            return true;
        });
    }
    
    public async Task<List<T>> GetListAsync()
    {
        return await ExecuteAsync(async () => await _db.Queryable<T>().ToListAsync());
    }
    
    public ISugarQueryable<T> GetAll()
    {
        ResetTransactionFlags();
        return GetQuery();
    }
    
    public async Task<bool> DeleteAsync(Expression<Func<T, bool>> whereExpression)
    {
        return await ExecuteAsync(async () => 
            await _db.Deleteable<T>().Where(whereExpression).ExecuteCommandAsync() > 0);
    }
    
    public ISugarQueryable<T> GetQuery()
    {
        ResetTransactionFlags();
        return _db.Queryable<T>();
    }

    public async Task<T> GetByIdAsync(TKey id)
    {
        return await ExecuteAsync(async () => 
            await _db.Queryable<T>().FirstAsync(x => x.Id.Equals(id)));
    }

    public async Task<bool> AddAsync(T entity)
    {
        return await ExecuteAsync(async () =>
        {
            if (typeof(TKey) == typeof(string))
            {
                var currentId = entity.Id as string;
                if (string.IsNullOrWhiteSpace(currentId))
                    entity.Id = (TKey)(object)Guid.NewGuid().ToString("N");
            }
            return await _db.Insertable(entity).ExecuteCommandAsync() > 0;
        });
    }

    public async Task<bool> AddRangeAsync(List<T> entities)
    {
        return await ExecuteAsync(async () => 
            await _db.Insertable(entities).ExecuteCommandAsync() > 0);
    }

    public async Task<bool> UpdateAsync(T entity)
    {
        return await ExecuteAsync(async () => 
            await _db.Updateable(entity).ExecuteCommandAsync() > 0);
    }

    public async Task<bool> DeleteAsync(TKey id)
    {
        return await ExecuteAsync(async () => 
            await _db.Deleteable<T>().Where(x => x.Id.Equals(id)).ExecuteCommandAsync() > 0);
    }

    public async Task<bool> DeleteAsync(T entity)
    {
        return await ExecuteAsync(async () => 
            await _db.Deleteable(entity).ExecuteCommandAsync() > 0);
    }

    public async Task<int> CountAsync()
    {
        return await ExecuteAsync(async () => await _db.Queryable<T>().CountAsync());
    }

    public async Task<bool> ExistsAsync(TKey id)
    {
        return await ExecuteAsync(async () => 
            await _db.Queryable<T>().AnyAsync(x => x.Id.Equals(id)));
    }
}
© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容