表时会自动添加一个IsDeleted字段,用于标识该条记录是否已删除。其实就是数据的逻辑删除而非物理删除。在代码层面,ABP框架是默认启用SoftDelete过滤器的,也就是所有IsDeleted=true的数据都会被过滤掉。
然而在实际使用过程中,并不总是那么美好的。在实际项目中并不是所有实体类都是实现了ISoftDelete接口的。全局开启SoftDelete过滤器时,框架会自动为查询条件加上IsDeleted==false,不包含IsDelete属性的实体查询就会报错。全局关闭SoftDelete过滤器,已经逻辑删除的数据又会显示出来,业务逻辑又不能满足的。
好在ABP框架支持在工作单元(Unit of Work)级别启用或禁用过滤器,所以利用这个特性对目前的应用层公共方法进行修改。由于使用最频繁的是查询方法,在查询这一关把数据都过滤掉,新增、修改、删除都不用去考虑这个问题,所以最关键的改动在于查询方法的重构。
在之前的版本中,应用层公共类库提供了仅包含查询的应用服务基类、包含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; }
在泛型方法中,对泛型进行了相应的约束。
在应用服务基类中添加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,也就是不开启。
目前此项功能仅在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进行映射。