226  
查询码:00000336
ABP框架SoftDelete过滤器应用--RunGo.Application.Shared重构
作者: 潘帅 于 2020年04月21日 发布在分类 / 人防组 / 人防后端 下,并于 2020年04月21日 编辑
ABP

ABP框架提供了数据过滤器的功能,比较常用的就是SoftDelete过滤器。当实体实现ISoftDelete接口后,生成数据库

表时会自动添加一个IsDeleted字段,用于标识该条记录是否已删除。其实就是数据的逻辑删除而非物理删除。在代码层面,ABP框架是默认启用SoftDelete过滤器的,也就是所有IsDeleted=true的数据都会被过滤掉。

然而在实际使用过程中,并不总是那么美好的。在实际项目中并不是所有实体类都是实现了ISoftDelete接口的。全局开启SoftDelete过滤器时,框架会自动为查询条件加上IsDeleted==false,不包含IsDelete属性的实体查询就会报错。全局关闭SoftDelete过滤器,已经逻辑删除的数据又会显示出来,业务逻辑又不能满足的。

好在ABP框架支持在工作单元(Unit of Work)级别启用或禁用过滤器,所以利用这个特性对目前的应用层公共方法进行修改。由于使用最频繁的是查询方法,在查询这一关把数据都过滤掉,新增、修改、删除都不用去考虑这个问题,所以最关键的改动在于查询方法的重构。


1.分页查询方法重构

在之前的版本中,应用层公共类库提供了仅包含查询的应用服务基类、包含CURD的应用服务基类、包含批量CURD的应用服务基类、包含完整审计的应用服务基类,每个基类中的分页查询方法都是单独实现的,改起来比较耗时。所以首先将分页查询的方法单独以静态泛型方法的形式抽离出来。

        /// <summary>
        /// 内部分页静态方法
        /// </summary>
        /// <typeparam name="T">实体类</typeparam>
        /// <typeparam name="R">仓储接口</typeparam>
        /// <typeparam name="P">查询对象类</typeparam>
        /// <typeparam name="K">主键</typeparam>
        /// <param name="input">查询对象</param>
        /// <param name="repository">仓储</param>
        /// <returns></returns>
        public static PagedResultDto<T> InternalPage<T, R, P, K>(P input, R repository)
            where T : class, IEntity<K>
            where R : IRepository<T, K>
            where P : class, IPagedQueryDto
        {
            PagedResultDto<T> result = new PagedResultDto<T>();
            IQueryable<T> query = repository.GetAll();
            var sourceExpression = query.Expression;

            //where表达式
            if (input.Filters != null)
            {
                foreach (var filter in input.Filters)
                {
                    if (string.IsNullOrWhiteSpace(filter.Name) || string.IsNullOrWhiteSpace(filter.Value))
                    {
                        continue;
                    }
                    var whereLambdaExtenstion = GetLambdaExtention<T>(filter);
                    sourceExpression = Expression.Call(typeof(Queryable), "Where", new Type[1] { typeof(T) }, sourceExpression, Expression.Quote(whereLambdaExtenstion.GetLambda()));
                }
            }

            if (input.Sorts != null)
            {
                ParameterExpression parameter = Expression.Parameter(typeof(T), "x");
                string methodAsc = "OrderBy";
                string methodDesc = "OrderByDescending";
                foreach (var sort in input.Sorts)
                {
                    if (string.IsNullOrWhiteSpace(sort.SortName))
                    {
                        continue;
                    }
                    MemberExpression body = Expression.PropertyOrField(parameter, sort.SortName);
                    sourceExpression = Expression.Call(typeof(Queryable), sort.SortType == OrderByType.Asc ? methodAsc : methodDesc, new Type[] { typeof(T), body.Type }, sourceExpression, Expression.Quote(Expression.Lambda(body, parameter)));
                    methodAsc = "ThenBy";
                    methodDesc = "ThenByDescending";
                }
            }

            query = query.Provider.Execute<IEnumerable<T>>(sourceExpression).AsQueryable();

            result.TotalCount = query.Count();
            if (input.PageSize != -1)
            {
                result.Items = query.Skip((input.PageIndex - 1) * input.PageSize).Take(input.PageSize).ToList();
            }
            else
            {
                result.Items = query.ToList();
            }

            return result;
        }

在泛型方法中,对泛型进行了相应的约束。


2.添加属性用于是否启用过滤器


在应用服务基类中添加IsEnableDeleteFilter属性,当值为true时启用SoftDelete过滤器,当值为false时关闭SoftDelete过滤器。在分页方法中会根据IsEnableDeleteFilter的值在工作单元中分别执行内部分页方法。

        /// <summary>
        /// 分页查询
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public virtual async Task<PagedResultDto<TEntityDto>> Page(TPagedInput input)
        {
            if (input == null)
            {
                return new PagedResultDto<TEntityDto>();
            }
            PagedResultDto<TEntity> entitis = new PagedResultDto<TEntity>();
            if (IsEnableDeleteFilter)
            {
                using (CurrentUnitOfWork.EnableFilter(AbpDataFilters.SoftDelete))
                {
                    entitis = PageHelper.InternalPage<TEntity, IRepository<TEntity, TPrimaryKey>, TPagedInput, TPrimaryKey>(input, Repository);
                }
            }
            else
            {
                using (CurrentUnitOfWork.DisableFilter(AbpDataFilters.SoftDelete))
                {
                    entitis = PageHelper.InternalPage<TEntity, IRepository<TEntity, TPrimaryKey>, TPagedInput, TPrimaryKey>(input, Repository);
                }
            }

            List<TEntityDto> dtos = entitis.Items.MapTo<List<TEntityDto>>();
            PagedResultDto<TEntityDto> result = new PagedResultDto<TEntityDto>(entitis.TotalCount, dtos);

            return await Task.FromResult(result);
        }

同时为应用服务基类添加了一个构造函数,以便于派生类对IsEnableDeleteFilter属性进行赋值。

        protected RunGoAppServiceBase(IRepository<TEntity, TPrimaryKey> repository, ICacheManager cacheManager,bool isEnableDeleteFilter)
        {
            Repository = repository;
            CacheManager = cacheManager;
            LocalizationSourceName = RunGoConsts.LocalizationSourceName;
            IsEnableDeleteFilter = isEnableDeleteFilter;
        }

注意:IsEnableDeleteFilter默认值是false,也就是不开启。


3.如何在派生类中使用

目前此项功能仅在RunGo.Application.Shared 1.2.0以上版本提供。为应用层安装此版本的NuGet包后即可使用。


附:分页查询泛型方法的使用

在应用层中使用最多的方法应该是查询了,不过基类中提供的查询方法在输入输出上均有严格的限制,一些自定义的分页查询就比较麻烦了,因此基类中添加了用于分页查询的泛型方法PageBase。其核心仍然是第一部分中的内部查询的静态泛型方法,当然也包含了SoftDelete过滤器的控制。

        /// <summary>
        /// 分页查询泛型方法
        /// </summary>
        /// <typeparam name="T">实体类</typeparam>
        /// <typeparam name="R">仓储接口</typeparam>
        /// <typeparam name="P">查询对象类</typeparam>
        /// <param name="input">查询对象</param>
        /// <param name="repository">仓储</param>
        /// <param name="isEnableDeleteFilter">是否启用Deleted过滤,默认不启用</param>
        /// <returns></returns>
        protected virtual async Task<PagedResultDto<T>> PageBase<T, R, P>(P input, R repository, bool isEnableDeleteFilter = false)
            where T : class, IEntity<string>
            where R : IRepository<T, string>
            where P : class, IPagedQueryDto
        {
            if (input == null)
            {
                return new PagedResultDto<T>();
            }

            PagedResultDto<T> result = new PagedResultDto<T>();
            if (isEnableDeleteFilter)
            {
                using (CurrentUnitOfWork.EnableFilter(AbpDataFilters.SoftDelete))
                {
                    result = PageHelper.InternalPage<T, R, P, string>(input, repository);
                }
            }
            else
            {
                using (CurrentUnitOfWork.DisableFilter(AbpDataFilters.SoftDelete))
                {
                    result = PageHelper.InternalPage<T, R, P, string>(input, repository);
                }
            }
            return await Task.FromResult(result);
        }


在派生类中可以按照下列示例使用。

注意:1.PageBase方法的返回值有class和IEntity接口的约束,一般就是Entity实体。

         2.分页查询结果对象PagedResultDto<T>不能使用AutoMapper进行映射。






 最新评论
当前评论数1  查看更多评论


 推荐知识

 历史版本

修改日期 修改人 备注
2020-04-21 15:55:08[当前版本] 潘帅 1.0

 附件

附件类型

PNGPNG

知识分享平台 -V 4.8.7 -wcp