乐趣区

关于程序员:ABP中的数据过滤器

  本文首先介绍了 ABP 内置的软删除过滤器 (ISoftDelete) 和多租户过滤器(IMultiTenant),而后介绍了如何实现一个自定义过滤器,最初介绍了在软件开发过程中遇到的理论问题,同时给出了解决问题的一个未必最优的思路。

一. 预约义过滤器

  ABP 中的数据过滤器源码在 Volo.Abp.Data[2]包中,官网定义了 2 个开箱即用的过滤器,别离是软删除过滤器 (ISoftDelete) 和多租户过滤器(IMultiTenant),想必大家对这 2 个内置的过滤器曾经比拟相熟了。上面重点说下通过 IDataFilter 实现部分过滤,和通过 AbpDataFilterOptions 实现全局过滤。

1.IDataFilter 部分过滤

  次要的思路就是通过 IDataFilter 依赖注入,而后通过 _dataFilter.Disable<XXX>() 长期的启用或者禁用过滤器:

namespace Acme.BookStore
{
    public class MyBookService : ITransientDependency
    {
        private readonly IDataFilter _dataFilter;
        private readonly IRepository<Book, Guid> _bookRepository;

        public MyBookService(IDataFilter dataFilter, IRepository<Book, Guid> bookRepository)
        {
            _dataFilter = dataFilter;
            _bookRepository = bookRepository;
        }

        public async Task<List<Book>> GetAllBooksIncludingDeletedAsync()
        {
            // 长期禁用 ISoftDelete 过滤器
            using (_dataFilter.Disable<ISoftDelete>())
            {return await _bookRepository.GetListAsync();
            }
        }
    }
}

这样就会部分地把 IsDeleted= 1 的记录查找进去。

2.AbpDataFilterOptions 全局过滤

次要是通过选项 (Options) 的形式来配置全局过滤:

Configure<AbpDataFilterOptions>(options =>
{options.DefaultStates[typeof(ISoftDelete)] = new DataFilterState(isEnabled: false);
});

这样就会全局地把 IsDeleted= 1 的记录查找进去。其中的一个问题是,这段代码写到哪里呢?本人是写到 XXX.Host->XXXHostModule->ConfigureServices 中,比方 Business.Host->BusinessHostModule->ConfigureServices。

二. 自定义过滤器

  自定义过滤器是比较简单的,基本上都是八股文格局了,对于 EFCore 来说,就是重写 DbContext 中的 ShouldFilterEntity 和 CreateFilterExpression 办法。因为临时用不到 MongoDB,所以不做介绍,有趣味能够参考[1],也不是很难。上面通过一个例子来介绍下 EF Core 的自定义过滤器。

1. 定义过滤器接口

首先定义一个过滤器接口,而后实现该接口:

public interface IIsActive
{bool IsActive { get;}
}

public class Book : AggregateRoot<Guid>, IIsActive
{public string Name { get; set;}
    public bool IsActive {get; set;} //Defined by IIsActive
}

2. 重写 DbContext 中的办法

而后就是重写 DbContext 中的 ShouldFilterEntity 和 CreateFilterExpression 办法:

protected bool IsActiveFilterEnabled => DataFilter?.IsEnabled<IIsActive>() ?? false;

protected override bool ShouldFilterEntity<TEntity>(IMutableEntityType entityType)
{if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
    {return true;}
    return base.ShouldFilterEntity<TEntity>(entityType);
}

protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>()
{var expression = base.CreateFilterExpression<TEntity>();

    if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
    {Expression<Func<TEntity, bool>> isActiveFilter = e => !IsActiveFilterEnabled || EF.Property<bool>(e, "IsActive");
        expression = expression == null ? isActiveFilter : CombineExpressions(expression, isActiveFilter);
    }
    return expression;
}

  忽然看上去感觉这个自定义过滤器好简单,起初想想那 ABP 内置的软删除过滤器 (ISoftDelete) 和多租户过滤器 (IMultiTenant) 是如何实现的呢?而后就找到了源码ABP/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs

看了源码实现后会发现格局截然不同,所以自定义过滤器应用起来没有这么简单。

三. 遇到的理论问题

  如果在 SaaS 零碎中,有一个主核心和分中心的概念,什么意思呢?就是在主核心中能够看到所有分中心的 User 数据,同时主核心能够把一些通用的材料 (比方,科普文章) 共享给分中心。在 ABP 群里问了下,有人倡议宿主就是宿主,用来做租户治理的,不能把它当成一个租户,这是一个父子租户的问题。有人倡议搞一个仿租户 ID 过滤器,这样既能曲线解决问题,又不背离宿主和租户的准则。父子租户第一次据说,所以暂不思考。因为零碎曾经开发了一部分,如果每个实体都继承仿租户 ID 过滤器接口,那么也感觉麻烦。
  最终抉择把主核心当成是宿主用户,分中心当成是租户。对于一些通用的材料(比方,科普文章),在增删改查中间接 IDataFilter 部分过滤。比方查找实现如下:

public async Task<PagedResultDto<ArticleDto>> GetAll(GetArticleInputDto input)
{
    // 长期禁用掉 IMultiTenant 过滤器
    using (_dataFilter.Disable<IMultiTenant>())
    {var query = (await _repository.GetQueryableAsync()).WhereIf(!string.IsNullOrWhiteSpace(input.Filter), a => a.Title.Contains(input.Filter));

        var totalCount = await query.CountAsync();
        var items = await query.OrderBy(input.Sorting ?? "Id").Skip(input.SkipCount).Take(input.MaxResultCount).ToListAsync();

        var dto = ObjectMapper.Map<List<Article>, List<ArticleDto>>(items);
        return new PagedResultDto<ArticleDto>(totalCount, dto);
    }
}

  对于 ” 主核心中能够看到所有分中心的 User 数据 ” 这个问题,因为只是波及到查看,不做增删改,所以又新建了一个 User 查找接口,在该接口中间接 IDataFilter 部分过滤。这样新建的 User 查找接口就能够看到所有分中心的数据,原来的 User 查找接口仅能看到宿主或者租户的 User 数据。总之,适宜本人需要的架构就是最好的,如果架构满足不了需要了,那么就迭代架构。

参考文献:
[1]数据过滤:https://docs.abp.io/zh-Hans/a…
[2]Volo.Abp.Data:https://github.com/abpframewo…
[3]EntityFramework.DynamicFilters:https://github.com/zzzproject…
[4]ABP 文档笔记 – 数据过滤:https://www.cnblogs.com/wj033…
[5]ABP 畛域层 – 数据过滤器:https://www.kancloud.cn/gaota…
[6]Mastering-ABP-Framework:https://github.com/PacktPubli…
[7]ABP 多租户:https://docs.abp.io/zh-Hans/a…
[8]ASP.NET Boilerplate 中文文档:https://www.kancloud.cn/gaota…
[9]详解 ABP 框架中数据过滤器与数据传输对象应用:https://wenku.baidu.com/view/…
[10]ASP.NET Boilerplate 官网文档:https://aspnetboilerplate.com…
[11]How to create a custom data filter with EF Core:https://support.aspnetzero.co…

本文由 mdnice 多平台公布

退出移动版