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

2023-05-26 09:30:22 浏览数 (1)

什么是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的定义)

在这里使用UnitOfWorkFilter的目的是为了使用AOP,面向切面编程的思想。在具体的业务逻辑中,不直接在逻辑中使用数据库的事务代码,而在业务的入口使用Filter将逻辑进行包裹,以达到Uinit Of Work的目的。

代码语言:javascript复制
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上使用。

代码语言:javascript复制
// services中注册
    services.AddScoped<UnitOfWorkFilter>();
    // Controller上使用
    [ServiceFilter(typeof(UnitOfWorkFilter))]
    public class TestControllerBase : ControllerBase
    {
        ...
    }

【小结】

任何一门编程语言,需要在实践中千锤百炼,方能真正掌握。

0 人点赞