什么是Unit Of Work
Unit of Work: Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems. —— Martin Fowler
按照Martin Fowler的说法,Unit Of Work实际也就是其字面意思,工作单元
。在业务上,需要一个工作单元的稳定性,完整性。类似于数据库中的事务,以防在业务操作单元中出了意外,可以回滚。
更为直白的意思,就是在一个业务操作的方法中,可能对数据库的多个实体对象进行了删除,修改,新增等操作;那么我们希望它们的改动是统一,一致的。不能在在改一部分的情况下,另一部分没有被改到。类似数据库事务的经典场景:一个人去银行转钱的问题,不能钱在对方账户到账了,而自己的账户余额还没有减少。这样就造成了数据的不一致,也就可能造成了不可预期的后果。
C#中Unit Of Work的实现(基于EF)
UnitOfWorkAttribute(特性的定义)
代码语言:javascript复制 public sealed class UnitOfWorkAttribute : Attribute
{
public UnitOfWorkAttribute()
{
}
public UnitOfWorkAttribute(bool ensureTransaction)
{
EnsureTransaction = ensureTransaction;
}
/// <summary>
/// 确保事务可用
/// <para>此方法为了解决静态类方式操作数据库的问题</para>
/// </summary>
public bool EnsureTransaction { get; set; } = false;
}
UnitOfWorkFilter(AOP Filter的定义)
代码语言:javascript复制在这里使用UnitOfWorkFilter的目的是为了使用AOP,面向切面编程的思想。在具体的业务逻辑中,不直接在逻辑中使用数据库的事务代码,而在业务的入口使用Filter将逻辑进行包裹,以达到Uinit Of Work的目的。
public sealed class UnitOfWorkFilter : IAsyncActionFilter
{
private readonly DbContext? _dbContext;
public UnitOfWorkFilter(DbContext? dbContext)
{
_dbContext = dbContext;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// 获取动作方法描述器
var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
var method = actionDescriptor?.MethodInfo;
// 判断是否贴有工作单元特性
if (method == null || !method.IsDefined(typeof(UnitOfWorkAttribute), true))
{
// 调用方法
var resultContext = await next();
}
else
{
// 获取工作单元特性
var unitOfWorkAttribute = method.GetCustomAttribute<UnitOfWorkAttribute>();
// 判断,以决定是否使用数据库事务。
if (unitOfWorkAttribute != null && unitOfWorkAttribute.EnsureTransaction)
{
if (_dbContext == null)
{
throw new Exception($"{nameof(DbContext)} is null.");
}
using (var tran = _dbContext.Database.BeginTransaction())
{
try
{
// 调用方法
var resultContext = await next();
await tran.CommitAsync();
}
catch (Exception ex)
{
await tran.RollbackAsync();
throw new Exception(ex.Message);
}
}
}
}
}
}
最后,UnitOfWorkFilter
的使用方式同其它的Attribute一样,可以全局注册,也可以在相应的Action或者Controller上使用。
// services中注册
services.AddScoped<UnitOfWorkFilter>();
// Controller上使用
[ServiceFilter(typeof(UnitOfWorkFilter))]
public class TestControllerBase : ControllerBase
{
...
}
【小结】
任何一门编程语言,需要在实践中千锤百炼,方能真正掌握。