C#中的工作单元(Unit Of Work)

什么是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    {        ...    }