.NET Core開發實戰(第28課:工作單元模式(UnitOfWork):管理好你的事務)--學習筆記


28 | 工作單元模式(UnitOfWork):管理好你的事務

工作單元模式有如下幾個特性:

1、使用同一上下文

2、跟蹤實體的狀態

3、保障事務一致性

我們對實體的操作,最終的狀態都是應該如實保存到我們的存儲中,進行持久化

接下來看一下代碼

為了實現工作單元模式,這里定義了一個工作單元的接口

public interface IUnitOfWork : IDisposable
{
    Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
    Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default);
}

這兩個方法的區別是:一個是返回的 int 是指我們影響的數據條數,另外一個返回 bool 表示我們保存是否成功,本質上這兩個方法達到的效果是相同的

另外還定義了一個事務管理的接口

public interface ITransaction
{
    // 獲取當前事務
    IDbContextTransaction GetCurrentTransaction();

    // 判斷當前事務是否開啟
    bool HasActiveTransaction { get; }

    // 開啟事務
    Task<IDbContextTransaction> BeginTransactionAsync();

    // 提交事務
    Task CommitTransactionAsync(IDbContextTransaction transaction);

    // 事務回滾
    void RollbackTransaction();
}

在實現上我們是借助 EF 來實現工作單元模式的

看一下 EFContext 的定義

/// <summary>
/// DbContext 是 EF 的基類,然后實現了 UnitOfWork 的接口和事務的接口
/// </summary>
public class EFContext : DbContext, IUnitOfWork, ITransaction
{
    protected IMediator _mediator;
    ICapPublisher _capBus;

    // 后面的章節會詳細講到這兩個參數
    public EFContext(DbContextOptions options, IMediator mediator, ICapPublisher capBus) : base(options)
    {
        _mediator = mediator;
        _capBus = capBus;
    }

    #region IUnitOfWork

    public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default)
    {
        var result = await base.SaveChangesAsync(cancellationToken);
        //await _mediator.DispatchDomainEventsAsync(this);
        return true;
    }

    //// 可以看到這個方法實際上與上面的方法是相同的,所以這個方法可以不實現
    //public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
    //{
    //    return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
    //}

    #endregion

    #region ITransaction

    private IDbContextTransaction _currentTransaction;// 把當前的事務用一個字段存儲

    public IDbContextTransaction GetCurrentTransaction() => _currentTransaction;// 獲取當前的事務就是返回存儲的私有對象

    public bool HasActiveTransaction => _currentTransaction != null;// 事務是否開啟是判斷當前這個事務是否為空
    
    /// <summary>
    /// 開啟事務
    /// </summary>
    /// <returns></returns>
    public Task<IDbContextTransaction> BeginTransactionAsync()
    {
        if (_currentTransaction != null) return null;
        _currentTransaction = Database.BeginTransaction(_capBus, autoCommit: false);
        return Task.FromResult(_currentTransaction);
    }

    /// <summary>
    /// 提交事務
    /// </summary>
    /// <param name="transaction">當前事務</param>
    /// <returns></returns>
    public async Task CommitTransactionAsync(IDbContextTransaction transaction)
    {
        if (transaction == null) throw new ArgumentNullException(nameof(transaction));
        if (transaction != _currentTransaction) throw new InvalidOperationException($"Transaction {transaction.TransactionId} is not current");

        try
        {
            await SaveChangesAsync();// 將當前所有的變更都保存到數據庫
            transaction.Commit();
        }
        catch
        {
            RollbackTransaction();
            throw;
        }
        finally
        {
            if (_currentTransaction != null)
            {
                // 最終需要把當前事務進行釋放,並且置為空
                // 這樣就可以多次的開啟事務和提交事務
                _currentTransaction.Dispose();
                _currentTransaction = null;
            }
        }
    }

    /// <summary>
    /// 回滾
    /// </summary>
    public void RollbackTransaction()
    {
        try
        {
            _currentTransaction?.Rollback();
        }
        finally
        {
            if (_currentTransaction != null)
            {
                _currentTransaction.Dispose();
                _currentTransaction = null;
            }
        }
    }

    #endregion
}

另外一個我們還是需要關注的一點就是如何管理我們的事務

這里有一個類 TransactionBehavior,這個類是用來注入我們的事務的管理過程的,具體它是怎么工作的在后續的章節會講到,這里先關注它的實現過程

public class TransactionBehavior<TDbContext, TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TDbContext : EFContext
{
    ILogger _logger;
    TDbContext _dbContext;
    ICapPublisher _capBus;
    public TransactionBehavior(TDbContext dbContext, ICapPublisher capBus, ILogger logger)
    {
        _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
        _capBus = capBus ?? throw new ArgumentNullException(nameof(capBus));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }


    public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
    {
        var response = default(TResponse);
        var typeName = request.GetGenericTypeName();

        try
        {
            // 首先判斷當前是否有開啟事務
            if (_dbContext.HasActiveTransaction)
            {
                return await next();
            }

            // 定義了一個數據庫操作執行的策略,比如說可以在里面嵌入一些重試的邏輯,這里創建了一個默認的策略
            var strategy = _dbContext.Database.CreateExecutionStrategy();

            await strategy.ExecuteAsync(async () =>
            {
                Guid transactionId;
                using (var transaction = await _dbContext.BeginTransactionAsync())
                using (_logger.BeginScope("TransactionContext:{TransactionId}", transaction.TransactionId))
                {
                    _logger.LogInformation("----- 開始事務 {TransactionId} ({@Command})", transaction.TransactionId, typeName, request);

                    response = await next();// next 實際上是指我們的后續操作,這里的模式有點像之前講的中間件模式

                    _logger.LogInformation("----- 提交事務 {TransactionId} {CommandName}", transaction.TransactionId, typeName);


                    await _dbContext.CommitTransactionAsync(transaction);

                    transactionId = transaction.TransactionId;
                }
            });

            return response;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "處理事務出錯 {CommandName} ({@Command})", typeName, request);

            throw;
        }
    }
}

回過頭來看一下我們的 EFContext,EFContext 實現 IUnitOfWork,工作單元模式的核心,它實現了事務的管理和工作單元模式,我們就可以借助 EFContext 來實現我們的倉儲層

GitHub源碼鏈接:

https://github.com/witskeeper/geektime

知識共享許可協議

本作品采用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。

歡迎轉載、使用、重新發布,但務必保留文章署名 鄭子銘 (包含鏈接: http://www.cnblogs.com/MingsonZheng/ ),不得用於商業目的,基於本文修改后的作品務必以相同的許可發布。

如有任何疑問,請與我聯系 (MingsonZheng@outlook.com) 。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM