什么是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(特性的定义)
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的定义)
在这里使用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{...}