本文首先介紹了ABP內建的軟刪除過濾器(ISoftDelete)和多租戶過濾器(IMultiTenant),然後介紹瞭如何實現一個自定義過濾器,最後介紹了在軟體開發過程中遇到的實際問題,同時給出瞭解決問題的一個未必最優的思路。
ABP中的資料過濾器原始碼在Volo.Abp.Data[2]包中,官方定義了2個開箱即用的過濾器,分別是軟刪除過濾器(ISoftDelete)和多租戶過濾器(IMultiTenant),想必大家對這2個內建的過濾器已經比較熟悉了。下面重點說下通過IDataFilter實現區域性過濾,和通過AbpDataFilterOptions實現全域性過濾。
主要的思路就是通過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的記錄查詢出來。
主要是通過選項(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的自定義過濾器。
首先定義一個過濾器介面,然後實現該介面:
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
}
然後就是重寫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