前言
簡單整理一下工作單元模式。
正文
工作單元模式有3個特性,也算是其功能:
- 使用同一上下文
- 跟蹤實體的狀态
- 保障事務一緻性
工作單元模式 主要關注事務,是以重點在事務上。
在共享層的基礎建設類庫中加入:
/// <summary>
/// 工作單元接口
/// </summary>
public interface IUnitOfWork : IDisposable
{
/// <summary>
/// 儲存變更
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns>傳回受影響的資料條數</returns>
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
/// <summary>
/// 儲存變更
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns>傳回儲存是否成功</returns>
Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default);
}
SaveChangesAsync 事務第一個影響多少條數
SaveEntitiesAsync 事務是否成功
同樣加入事務接口:
interface ITransaction
{
IDbContextTransaction GetCurrentTransaction();
bool HasActiveTransaction { get; }
Task<IDbContextTransaction> BeginTransactionAsync();
Task CommitTransactionAsync(IDbContextTransaction transaction);
void RollbackTransaction();
}
然後EFContext 實作它們:
/// <summary>
/// EF上下文
/// 注:在處理事務的邏輯部分,需要嵌入CAP的代碼,構造函數參數 ICapPublisher
/// </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
/// <summary>
/// 儲存實體變更
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default)
{
var result = await base.SaveChangesAsync(cancellationToken);
// 執行發送領域事件
await _mediator.DispatchDomainEventsAsync(this);
return true;
}
///// <summary>
///// IUniOfWork中該方法的定義與DbContext中的SaveChangesAsync一緻,是以此處無需再進行實作
///// </summary>
///// <param name="cancellationToken"></param>
///// <returns></returns>
//public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
//{
// return base.SaveChangesAsync();
//}
#endregion
#region ITransaction
/// <summary>
/// 目前事務
/// </summary>
private IDbContextTransaction _currentTransaction;
/// <summary>
/// 公開方法,傳回目前私有事務對象
/// </summary>
/// <returns></returns>
public IDbContextTransaction GetCurrentTransaction() => _currentTransaction;
/// <summary>
/// 目前事務是否開啟
/// </summary>
public bool HasActiveTransaction => _currentTransaction == null;
/// <summary>
/// 開啟事務
/// </summary>
/// <returns></returns>
public Task<IDbContextTransaction> BeginTransactionAsync()
{
if (_currentTransaction != null)
{
return null;
}
// 該擴充方法是由CAP元件提供
// 建立事務時,也要把 ICapPublisher 傳入
// 核心作用是将我們要發送事件邏輯與我們業務的存儲都放在同一個事務内部,進而保證事件與業務邏輯的存取都是一緻的
_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
{
// 送出事務之前,安全起見還是要 SaveChanges 一下,儲存變更到資料庫
await SaveChangesAsync();
transaction.Commit();
}
catch (Exception ex)
{
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
}
前面這兩個實作了工作單元模式的事務的功能,那麼還有一個問題,如何實作管理我們的事務。
/// <summary>
/// 注入事務管理過程
/// </summary>
/// <typeparam name="TDbContext"></typeparam>
/// <typeparam name="TRequest"></typeparam>
/// <typeparam name="TResponse"></typeparam>
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();
_capBus = capBus ?? throw new ArgumentNullException(nameof(capBus));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>
/// 事務執行
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <param name="next"></param>
/// <returns></returns>
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();
_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;
}
}
}
這裡可能會有點疑問,這裡沒有rollback啊。
using (var transaction = await _dbContext.BeginTransactionAsync())
這一句是托管了,如果中間發生異常,那麼會自動調用rollback,using原理前面在c# 基礎篇中介紹了,本質就是try catch finnaly這樣的模式,這裡不詳細介紹了。
結
下一節倉儲層的具體實作。