7

ABP中的数据过滤器 - 阿升1990

 2 years ago
source link: https://www.cnblogs.com/shengshengwang/p/16564270.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

  本文首先介绍了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/abp/6.0/Data-Filtering
[2]Volo.Abp.Data:https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.Data
[3]EntityFramework.DynamicFilters:https://github.com/zzzprojects/EntityFramework.DynamicFilters
[4]ABP文档笔记 - 数据过滤:https://www.cnblogs.com/wj033/p/6494879.html
[5]ABP领域层 - 数据过滤器:https://www.kancloud.cn/gaotang/abp/225839
[6]Mastering-ABP-Framework:https://github.com/PacktPublishing/Mastering-ABP-Framework
[7]ABP多租户:https://docs.abp.io/zh-Hans/abp/6.0/Multi-Tenancy
[8]ASP.NET Boilerplate中文文档:https://www.kancloud.cn/gaotang/abp/225819
[9]详解ABP框架中数据过滤器与数据传输对象使用:https://wenku.baidu.com/view/ec237e90b3717fd5360cba1aa8114431b80d8e5e
[10]ASP.NET Boilerplate官方文档:https://aspnetboilerplate.com/Pages/Documents/Introduction
[11]How to create a custom data filter with EF Core:https://support.aspnetzero.com/QA/Questions/4752/How-to-create-a-custom-data-filter-with-EF-Core


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK