承接上篇文章咱们会在这篇文章详细解说一下 Abp 是如何结合 IPermissionChecker
与 IFeatureChecker
来实现一个完整的多租户系统的权限校验的。html
多租户系统又被称之为 Saas ,好比阿里云就是一个典型的多租户系统,用户自己就是一个租户,能够在上面购买本身的 ECS 实例,而且本身的数据与其余使用者(租户)所隔绝,二者的数据都是不可见的。git
那么 Abp 是如何实现数据隔离的呢?github
若是你的软件系统仅部署一个实例,而且全部租户的数据都是存放在一个数据库里面的,那么能够经过一个 TenantId
(租户 Id) 来进行数据隔离。那么当咱们执行 SELECT 操做的时候就会附加上当前登陆用户租户 Id 做为过滤条件,那么查出来的数据也仅仅是当前租户的数据,而不会查询到其余租户的数据。数据库
Abp 还提供了另一种方式,即为每个租户提供一个单独的数据库,在用户登陆的时候根据用户对应的租户 ID,从一个数据库链接映射表获取到当前租户对应的数据库链接字符串,而且在查询数据与写入数据的时候,不一样租户操做的数据库是不同的。express
从上一篇文章咱们知道了在权限过滤器与权限拦截器当中,最终会使用 IFeatureChecker
与 IPermissionChecker
来进行权限校验,而且它还持久一个用户会话状态 IAbpSession
用于存储识别当前访问网站的用户是谁。缓存
基本作过网站程序开发的同窗都知道用于区分每个用户,咱们须要经过 Session 来保存当前用户的状态,以便进行权限验证或者其余操做。而 Abp 框架则为咱们定义了一个统一的会话状态接口 IAbpSession
,用于标识当前用户的状态。在其接口当中主要定义了三个重要的属性,第一个 UserId
(用户 Id),第二个就是 TenantId
(租户 Id),以及用于肯定当前用户是租户仍是租主的 MultiTenancySides
属性。框架
除此以外,还拥有一个 Use()
方法,用户在某些时候临时替换掉当前用户的 UserId
与 TenantId
的值,这个方法在个人 《Abp + Grpc 如何实现用户会话状态传递》 文章当中有讲到过。async
而针对这个方法的实现又能够扯出一大堆知识,这块咱们放在后面再进行精讲,这里咱们仍是主要通篇讲解一下多租户体系下的数据过滤与权限验证。ide
IAbpSession
当中的值默认是从 JWT 当中取得的,这取决于它的默认实现 ClaimsAbpSession
,它还继承了一个抽象父类 AbpSessionBase
,这个父类主要是实现了 Use()
方法,这里略过。函数
在其默认实现里面,重载了 UserId
与 TenantId
的获取方法。
public override long? UserId { get { // ... 其余代码 var userIdClaim = PrincipalAccessor.Principal?.Claims.FirstOrDefault(c => c.Type == AbpClaimTypes.UserId); // ... 其余代码 long userId; if (!long.TryParse(userIdClaim.Value, out userId)) return null; return userId; } }
能够看到这里是经过 PrincipalAccessor
从当前请求的请求头中获取 Token ,并从 Claims
里面获取 Type 值为 AbpClaimTypes.UserId
的对象,将其转换为 long
类型的 UserId
,这样就拿到了当前用户登陆的 Id 了。
这里的 PrincipalAccessor
是一个 IPrincipalAccessor
接口,在 ASP .NET Core 库当中他的实现名字叫作 AspNetCorePrincipalAccessor
。其实你应该猜获得,在这个类的构造函数当中,注入了 HttpContext
的访问器对象 IHttpContextAccessor
,这样 IAbpSession
就能够垂手可得地得到当前请求上下文当中的具体数据了。
public class AspNetCorePrincipalAccessor : DefaultPrincipalAccessor { public override ClaimsPrincipal Principal => _httpContextAccessor.HttpContext?.User ?? base.Principal; private readonly IHttpContextAccessor _httpContextAccessor; public AspNetCorePrincipalAccessor(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } }
因此,Abp 经过 IAbpSession
能够轻松地知道咱们当前用户的状态,包括用户 Id 与租户 Id,它只须要知道这两个东西,就能够很简单的在 IFeatureChecker
和 IPermissionChecker
当中来查询用户所绑定的权限来进行验证。
首先咱们的思绪回到上一章所讲的 AuthorizationHelper
类,在其 AuthorizeAsync()
方法当中,使用 IFeatureChecker
来检测用户是否拥有某种功能。
public virtual async Task AuthorizeAsync(MethodInfo methodInfo, Type type) { // 检测功能 await CheckFeatures(methodInfo, type); // 检测权限 await CheckPermissions(methodInfo, type); }
而后呢,在 IFeatureChecker.CheckFeatures()
方法的内部,跟 IPermissionChecker
的套路同样,这里仍然是一个扩展方法,遍历方法/类上标记的 [RequiresFeatureAttribute]
特性,调用 IFeatureChecker
的 GetValueAsync()
方法传入功能的名称,而后将其值与 "true"
相比较,为真则是启用了该功能,其余值则说明没有启用。
public static async Task<bool> IsEnabledAsync(this IFeatureChecker featureChecker, string featureName) { // 检查是否启用 return string.Equals(await featureChecker.GetValueAsync(featureName), "true", StringComparison.OrdinalIgnoreCase); }
IFeatureChecker
的定义:
public interface IFeatureChecker { // 传入功能名字,获取真这对于当前租户其默认值 Task<string> GetValueAsync(string name); // 传入租户 Id 与功能名字,获取针对于指定 Id 租户的默认值 Task<string> GetValueAsync(int tenantId, string name); }
到这一步咱们仍然是跟 IFeatureChecker
打交道,那么他的具体实现是怎样的呢?
先来看一下这个 IFeatureChecker
的依赖关系图:
目前看起来仍是比较简单,他拥有一个默认实现 FeatureChecker
,其中 IFeatureValueStore
从名字就能够知道它是用来存储功能列表的,而 IFeatureManager
则是用来管理这些功能的,Feature
则是这些功能的定义。
结合以前在 IsEnabledAsync()
方法的调用,能够看到它先进入的 GetValueAsync(string name)
方法,判断当前用户的租户 Id 是否有值,若是没有值则直接抛出异常,中断权限验证。若是有值得话,传入当前登陆用户的租户 Id ,从 IFeatureManager
当中获取到定义的权限,以后呢从 IFeatureValueStore
当中拿到功能具体的值,由于功能是针对租户而言的,因此一个功能针对于多个租户的值确定是不一样的,因此在这里查询具体值的时候须要传入租户 Id。
public class FeatureChecker : IFeatureChecker, ITransientDependency { public IAbpSession AbpSession { get; set; } public IFeatureValueStore FeatureValueStore { get; set; } private readonly IFeatureManager _featureManager; public FeatureChecker(IFeatureManager featureManager) { _featureManager = featureManager; FeatureValueStore = NullFeatureValueStore.Instance; AbpSession = NullAbpSession.Instance; } public Task<string> GetValueAsync(string name) { // 判断当前登陆的用户是否拥有租户 ID if (!AbpSession.TenantId.HasValue) { throw new AbpException("FeatureChecker can not get a feature value by name. TenantId is not set in the IAbpSession!"); } // 传入当前登陆用户的租户 Id ,获取其值 return GetValueAsync(AbpSession.TenantId.Value, name); } public async Task<string> GetValueAsync(int tenantId, string name) { // 从功能管理器根据名字查询用户定义的功能 var feature = _featureManager.Get(name); // 得到功能的值,若是没有值则返回其默认值 var value = await FeatureValueStore.GetValueOrNullAsync(tenantId, feature); if (value == null) { return feature.DefaultValue; } return value; } }
聪明的你确定猜到功能实际上是用户在代码当中定义的,而功能的值则是存放在数据库当中,每一个租户其值都是不同的。这是否是让你想到了系列文章 《[Abp 源码分析]5、系统设置》 SettingProvider
的实现呢?
So,这里的 IFeatureStore
的默认实现确定是从数据库进行配置咯~
首先功能、权限都是树形结构,他们均可以拥有本身的子节点,这样能够直接实现针对父节点赋值而拥有其子节点的全部权限。这里先来看一下功能的的基本定义:
public class Feature { // 附加数据的一个索引器 public object this[string key] { get => Attributes.GetOrDefault(key); set => Attributes[key] = value; } // 功能的附加数据 public IDictionary<string, object> Attributes { get; private set; } // 父级功能 public Feature Parent { get; private set; } // 功能的名称 public string Name { get; private set; } // 功能的展现名称,这是一个本地化字符串 public ILocalizableString DisplayName { get; set; } // 功能的描述,同样的是一个本地化字符串 public ILocalizableString Description { get; set; } // 功能的输入类型 public IInputType InputType { get; set; } // 功能的默认值 public string DefaultValue { get; set; } // 功能所适用的范围 public FeatureScopes Scope { get; set; } // 若是当前功能的子节点的不可变集合 public IReadOnlyList<Feature> Children => _children.ToImmutableList(); private readonly List<Feature> _children; public Feature(string name, string defaultValue, ILocalizableString displayName = null, ILocalizableString description = null, FeatureScopes scope = FeatureScopes.All, IInputType inputType = null) { Name = name ?? throw new ArgumentNullException("name"); DisplayName = displayName; Description = description; Scope = scope; DefaultValue = defaultValue; InputType = inputType ?? new CheckboxInputType(); _children = new List<Feature>(); Attributes = new Dictionary<string, object>(); } public Feature CreateChildFeature(string name, string defaultValue, ILocalizableString displayName = null, ILocalizableString description = null, FeatureScopes scope = FeatureScopes.All, IInputType inputType = null) { var feature = new Feature(name, defaultValue, displayName, description, scope, inputType) { Parent = this }; _children.Add(feature); return feature; } public override string ToString() { return string.Format("[Feature: {0}]", Name); } }
这玩意儿光看着头仍是有点疼的,其实就是关于功能的基础定义,他为啥附带了一个附加描述字典,由于能够存储一些额外的信息,好比说一个短信功能,他的配额和到期时间,至于他的 Scope
则说明了它的生效范围。
接着看看 GetValueAsync(int tenantId, string name)
方法的第一句:
var feature = _featureManager.Get(name);
emmm,我要从 IFeatureManager
根据权限名称取得一个具体的 Feature
对象,那咱们继续来看一下 IFeatureManager
接口。
public interface IFeatureManager { // 根据名称得到一个具体的功能,这个名称应该是惟一的 Feature Get(string name); // 根据一个名称得到一个具体的功能,若是没找到则返回 NULL Feature GetOrNull(string name); // 得到全部定义的功能 IReadOnlyList<Feature> GetAll(); }
在看具体实现的时候,咱们先不慌,先看一下它实现类所继承的东西。
internal class FeatureManager : FeatureDefinitionContextBase, IFeatureManager, ISingletonDependency
WTF,他又继承了什么奇奇怪怪的东西。咱们又在此来到 FeatureDefinitionContextBase
,通过一番探查总算知道这玩意儿实现自 IFeatureDefinitionContext
,看看他的定义:
// 功能定义上下文,主要功能是提供给 FeatureProvider 来建立功能的 public interface IFeatureDefinitionContext { // 建立一个功能 Feature Create(string name, string defaultValue, ILocalizableString displayName = null, ILocalizableString description = null, FeatureScopes scope = FeatureScopes.All, IInputType inputType = null); // 根据名称得到一个功能 Feature GetOrNull(string name); // 移除一个功能 void Remove(string name); }
因此,你要把这些功能存放在哪些地方呢?
其实看到这个玩意儿 name-value,答案呼之欲出,其实现内部确定是用的一个字典来存储数据的。
接着咱们来到了 FeatureDefinitionContextBase
的默认实现 FeatureDefinitionContextBase
,而后发现里面也是别有洞天,Abp 又把字典再次封装了一遍,此次字典的名字叫作 FeatureDictionary
,你只须要记住他只提供了一个做用,就是将字典内部的全部功能项与其子功能项按照平级关系存放在字典当中。
除了内部封装了一个字典以外,在这个上下文当中,实现了建立,获取,和移除功能的方法,而后就没有了。咱们再次回到功能管理器,
功能管理器集成了这个上下文基类,集合以前 IFeatureManager
所定义的接口,它就具有了随时能够修改功能集的权力。那么这些功能是何时被定义的,而又是何时被初始化到这个字典的呢?
在前面咱们已经说过,Feature 的增长与以前文章所讲的系统设置是同样的,他们都是经过集成一个 Provider ,而后在模块预加载的时候,经过一个 IFeatureConfiguration
的东西被添加到 Abp 系统当中的。因此在 FeatureManager
内部注入了 IFeatureConfiguration
用来拿到用户在模块加载时所配置的功能项集合。
public interface IFeatureConfiguration { /// <summary> /// Used to add/remove <see cref="FeatureProvider"/>s. /// </summary> ITypeList<FeatureProvider> Providers { get; } }
下面给你演示一下如何添加一个功能项:
public class AppFeatureProvider : FeatureProvider { public override void SetFeatures(IFeatureDefinitionContext context) { var sampleBooleanFeature = context.Create("SampleBooleanFeature", defaultValue: "false"); sampleBooleanFeature.CreateChildFeature("SampleNumericFeature", defaultValue: "10"); context.Create("SampleSelectionFeature", defaultValue: "B"); } }
不用猜想 FeatureProvier
的实现了,他就是一个抽象类,定义了一个 SetFeatures
方法好让你实现而已。
以后我又在模块的预加载方法吧 AppFeatureProvider
添加到了IFeatureConfiguration
里面:
public class XXXModule : AbpModule { public override void PreInitialize() { Configuration.Features.Providers.Add<AppFeatureProvider>(); } }
而功能管理器则是在 Abp 核心模块 AbpKernalModule
初始化的时候,跟着权限管理器和系统设置管理器,一块儿被初始化了。
public override void PostInitialize() { RegisterMissingComponents(); // 这里是系统的设置的管理器 IocManager.Resolve<SettingDefinitionManager>().Initialize(); // 功能管理器在这里 IocManager.Resolve<FeatureManager>().Initialize(); // 权限管理器 IocManager.Resolve<PermissionManager>().Initialize(); IocManager.Resolve<LocalizationManager>().Initialize(); IocManager.Resolve<NotificationDefinitionManager>().Initialize(); IocManager.Resolve<NavigationManager>().Initialize(); if (Configuration.BackgroundJobs.IsJobExecutionEnabled) { var workerManager = IocManager.Resolve<IBackgroundWorkerManager>(); workerManager.Start(); workerManager.Add(IocManager.Resolve<IBackgroundJobManager>()); } }
看看功能管理器的定义就知道了:
public void Initialize() { foreach (var providerType in _featureConfiguration.Providers) { using (var provider = CreateProvider(providerType)) { provider.Object.SetFeatures(this); } } Features.AddAllFeatures(); }
波澜不惊的我早已看透一切,能够看到这里他经过遍历注入的 FeatureProvider
集合,传入本身,让他们能够向本身注入定义的功能项。
继续看 IFeatureChecker
的代码,最后从功能管理器拿到了功能项以后,就要根据租户的 Id 取得它具体的值了。值还能存在哪儿,除了数据库最合适放这种东西,其余的你愿意也能够存在 TXT 里面。
public interface IFeatureValueStore { // 很简洁,你传入当前用户的租户 Id 与 当前须要校验的功能项,我给你他的值 Task<string> GetValueOrNullAsync(int tenantId, Feature feature); }
废话很少说,来到 Zero 关于这个功能存储类的定义 AbpFeatureValueStore<TTenant,TUser>
,你先不着急看那两个泛型参数,这两个泛型就是你的用户与租户实体,咱们先看看这玩意儿继承了啥东西:
public class AbpFeatureValueStore<TTenant, TUser> : IAbpZeroFeatureValueStore, ITransientDependency, IEventHandler<EntityChangedEventData<Edition>>, IEventHandler<EntityChangedEventData<EditionFeatureSetting>> where TTenant : AbpTenant<TUser> where TUser : AbpUserBase
能够看到它首先继承了 IAbpZeroFeatureValueStore
接口,这里的 IAbpZeroFeatureValueStore
接口同样的继承的 IFeatureValueStore
,因此在 Abp 底层框架可以直接使用。
其次咱们还看到它监听了两个实体变动事件,也就是 Edition 与 EditFeatureSettings 表产生变化的时候,会进入到本类进行处理,其实这里的处理就是发生改变以后,拿到改变实体的 Id,从缓存清除掉脏数据而已。
而后咱们直奔主题,找到方法的实现:
public virtual Task<string> GetValueOrNullAsync(int tenantId, Feature feature) { return GetValueOrNullAsync(tenantId, feature.Name); }
发现又是一个空壳子,继续跳转:
public virtual async Task<string> GetValueOrNullAsync(int tenantId, string featureName) { // 首先从租户功能值表获取功能的值 var cacheItem = await GetTenantFeatureCacheItemAsync(tenantId); // 得到到值 var value = cacheItem.FeatureValues.GetOrDefault(featureName); // 不等于空,优先获取租户的值而忽略掉版本的值 if (value != null) { return value; } // 若是租户功能值表的缓存说我还有版本 Id,那么就去版本级别的功能值表查找功能的值 if (cacheItem.EditionId.HasValue) { value = await GetEditionValueOrNullAsync(cacheItem.EditionId.Value, featureName); if (value != null) { return value; } } return null; }
这才是真正的获取功能值的地方,其他方法就再也不详细讲述,这两个从缓存获取的方法,都分别有一个工厂方法从数据库拿去数据的,因此你也不用担忧缓存里面不存在值的状况。
总的来讲功能是针对租户的一个权限,Abp 建议一个父母功能通常定义为 布尔功能。只有父母功能可用时,子功能才可用。ABP不强制这样作,可是建议这样作。
在一个基于 Abp 框架的系统功能权限是可选的,具体使用仍是取决于你所开发的业务系统是否有这种需求。
权限的定义与 Feature 同样,都是存放了一些基本信息,好比说权限的惟一标识,权限的展现名称与描述,只不过少了 Feature 的附加属性而已。下面咱们就会加快进度来讲明一下权限相关的知识。
权限相比于功能,权限更加细化到了用户与角色,角色经过与权限关联,角色就是一个权限组的集合,用户再跟角色进行关联。看看权限管理器的定义吧:
public abstract class PermissionChecker<TRole, TUser> : IPermissionChecker, ITransientDependency, IIocManagerAccessor where TRole : AbpRole<TUser>, new() where TUser : AbpUser<TUser>
仍是相对而言比较简单的,在这里你只须要关注两个东西:
public virtual async Task<bool> IsGrantedAsync(string permissionName) { return AbpSession.UserId.HasValue && await _userManager.IsGrantedAsync(AbpSession.UserId.Value, permissionName); } public virtual async Task<bool> IsGrantedAsync(long userId, string permissionName) { return await _userManager.IsGrantedAsync(userId, permissionName); }
这就是权限校验的实现,第一个是传入当前用户的 Id 扔到 _userManager
进行校验,而第二个则扔一个用户制定的 Id 进行校验。
看到这里,咱们又该到下一节了,讲解一下这个 _userManager
是何方神圣。
若是读者接触过 ASP.NET Core MVC 的 Identity 确定对于 UserManager<,>
不会陌生,没错,这里的 _userManager
就是继承自 UserManager<TUser, long>,
实现的 AbpUserManager<TRole, TUser>
。
继续咱们仍是看关键方法 IsGrantedAsync()
。
public virtual async Task<bool> IsGrantedAsync(long userId, string permissionName) { // 传入用户 ID 与须要检测的权限,经过权限管理器得到 Permission 对象 return await IsGrantedAsync( userId, _permissionManager.GetPermission(permissionName) ); }
仍是个空壳子,继续跳转:
public virtual async Task<bool> IsGrantedAsync(long userId, Permission permission) { // 首先检测当前用户是否拥有租户信息 if (!permission.MultiTenancySides.HasFlag(GetCurrentMultiTenancySide())) { return false; } // 而后检测权限依赖的功能,若是功能没有启用,同样的是没权限的 if (permission.FeatureDependency != null && GetCurrentMultiTenancySide() == MultiTenancySides.Tenant) { FeatureDependencyContext.TenantId = GetCurrentTenantId(); if (!await permission.FeatureDependency.IsSatisfiedAsync(FeatureDependencyContext)) { return false; } } // 得到当前用户所拥有的权限,没有权限同样滚蛋 var cacheItem = await GetUserPermissionCacheItemAsync(userId); if (cacheItem == null) { return false; } // 检测当前用户是否被授予了特许权限,没有的话则直接跳过,有的话说明这是个特权用户,拥有这个特殊权限 if (cacheItem.GrantedPermissions.Contains(permission.Name)) { return true; } // 检测禁用权限名单中是否拥有本权限,若是有,同样的不经过 if (cacheItem.ProhibitedPermissions.Contains(permission.Name)) { return false; } // 检测用户角色是否拥有改权限 foreach (var roleId in cacheItem.RoleIds) { if (await RoleManager.IsGrantedAsync(roleId, permission)) { return true; } } return false; }
这里咱们没有讲解权限管理器与权限的注入是由于他们两个简直一毛同样好吧,你能够看看权限的定义:
public class MyAuthorizationProvider : AuthorizationProvider { public override void SetPermissions(IPermissionDefinitionContext context) { var administration = context.CreatePermission("Administration"); var userManagement = administration.CreateChildPermission("Administration.UserManagement"); userManagement.CreateChildPermission("Administration.UserManagement.CreateUser"); var roleManagement = administration.CreateChildPermission("Administration.RoleManagement"); } }
是否是感受跟功能的 Provider 很像...
权限仅仅会与用于和角色挂钩,与租户无关,它和功能的实现大同小异,可是也是值得咱们借鉴学习的。
租户与租户之间是如何进行数据过滤的呢?
这里简单讲一下单部署-单数据库的作法吧,在 EF Core 当中针对每个实体都提供了一个全局过滤的方法 HasQueryFilter
,有了这个东西,在每次 EF Core 进行查询的时候都会将查询表达式附加上你自定义的过滤器一块儿进行查询。
在 Abp 内部定义了一个借口,叫作 IMustHaveTenant
,这玩意儿有一个必须实现的属性 TenantId
,因此只要在你的实体继承了该接口,确定就是会有 TenantId
字段咯,那么 Abp 就能够先判断你当前的实体是否实现了 IMusHaveTenant
接口,若是有的话,就给你建立了一个过滤器拼接到你的查询表达式当中。
protected override void OnModelCreating(ModelBuilder modelBuilder) { // DbContext 模型建立的时候 base.OnModelCreating(modelBuilder); // 遍历全部 DbContext 定义的实体 foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { ConfigureGlobalFiltersMethodInfo .MakeGenericMethod(entityType.ClrType) .Invoke(this, new object[] { modelBuilder, entityType }); } } protected void ConfigureGlobalFilters<TEntity>(ModelBuilder modelBuilder, IMutableEntityType entityType) where TEntity : class { // 判断实体是否实现了租户或者软删除接口,实现了则添加一个过滤器 if (entityType.BaseType == null && ShouldFilterEntity<TEntity>(entityType)) { var filterExpression = CreateFilterExpression<TEntity>(); if (filterExpression != null) { modelBuilder.Entity<TEntity>().HasQueryFilter(filterExpression); } } } // 数据过滤用的查询表达式构建 protected virtual Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>() where TEntity : class { Expression<Func<TEntity, bool>> expression = null; if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity))) { /* This condition should normally be defined as below: * !IsSoftDeleteFilterEnabled || !((ISoftDelete) e).IsDeleted * But this causes a problem with EF Core (see https://github.com/aspnet/EntityFrameworkCore/issues/9502) * So, we made a workaround to make it working. It works same as above. */ Expression<Func<TEntity, bool>> softDeleteFilter = e => !((ISoftDelete)e).IsDeleted || ((ISoftDelete)e).IsDeleted != IsSoftDeleteFilterEnabled; expression = expression == null ? softDeleteFilter : CombineExpressions(expression, softDeleteFilter); } if (typeof(IMayHaveTenant).IsAssignableFrom(typeof(TEntity))) { /* This condition should normally be defined as below: * !IsMayHaveTenantFilterEnabled || ((IMayHaveTenant)e).TenantId == CurrentTenantId * But this causes a problem with EF Core (see https://github.com/aspnet/EntityFrameworkCore/issues/9502) * So, we made a workaround to make it working. It works same as above. */ Expression<Func<TEntity, bool>> mayHaveTenantFilter = e => ((IMayHaveTenant)e).TenantId == CurrentTenantId || (((IMayHaveTenant)e).TenantId == CurrentTenantId) == IsMayHaveTenantFilterEnabled; expression = expression == null ? mayHaveTenantFilter : CombineExpressions(expression, mayHaveTenantFilter); } if (typeof(IMustHaveTenant).IsAssignableFrom(typeof(TEntity))) { /* This condition should normally be defined as below: * !IsMustHaveTenantFilterEnabled || ((IMustHaveTenant)e).TenantId == CurrentTenantId * But this causes a problem with EF Core (see https://github.com/aspnet/EntityFrameworkCore/issues/9502) * So, we made a workaround to make it working. It works same as above. */ Expression<Func<TEntity, bool>> mustHaveTenantFilter = e => ((IMustHaveTenant)e).TenantId == CurrentTenantId || (((IMustHaveTenant)e).TenantId == CurrentTenantId) == IsMustHaveTenantFilterEnabled; expression = expression == null ? mustHaveTenantFilter : CombineExpressions(expression, mustHaveTenantFilter); } return expression; }
上面就是实现了,你每次使用 EF Core 查询某个表的实体都会应用这个过滤表达式。
可是能够看到在建立表达式的时候这里还有一些诸如 IsSoftDeleteFilterEnabled
的东西,这个就是用于你在某些时候须要禁用掉软删除过滤器的时候所须要用到的。
看看是哪儿来的:
protected virtual bool IsSoftDeleteFilterEnabled => CurrentUnitOfWorkProvider?.Current?.IsFilterEnabled(AbpDataFilters.SoftDelete) == true;
能够看到这个玩意儿是使用当前的工做单元来进行控制的,检测当前工做单元的过滤器是否被启用,若是实体被打了软删除接口,而且被启用的话,那么就执行过滤,反之亦然。
这些过滤器都是放在 AbpDataFilters
当中的,如今有如下几种定义:
public static class AbpDataFilters { public const string SoftDelete = "SoftDelete"; public const string MustHaveTenant = "MustHaveTenant"; public const string MayHaveTenant = "MayHaveTenant"; public static class Parameters { public const string TenantId = "tenantId"; } }
而这些过滤器是在 AbpKernelModule
的预加载方法当中被添加到 UOW 的默认配置当中的。
public override void PreInitialize() { // ... 其余代码 AddUnitOfWorkFilters(); // ... 其余代码 } private void AddUnitOfWorkFilters() { Configuration.UnitOfWork.RegisterFilter(AbpDataFilters.SoftDelete, true); Configuration.UnitOfWork.RegisterFilter(AbpDataFilters.MustHaveTenant, true); Configuration.UnitOfWork.RegisterFilter(AbpDataFilters.MayHaveTenant, true); }
这些东西被添加到了 IUnitOfWorkDefaultOptions
以后,每次初始化一个工做单元,其自带的 Filiters 都是从这个 IUnitOfWorkDefaultOptions
拿到的,除非用户显式指定 UowOptions 配置。