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) 。