预定义的过滤

ISoftDelete

软删除过滤用来在查询数据库时,自动过滤(从结果中抽取)已删除的实体。如果一个实体可以被软删除,它必须实现ISoftDelete接口,该接口只定义了一个IsDeleted属性,例如:

public class Person : Entity, ISoftDelete
{
public virtual string Name { get; set; } public virtual bool IsDeleted { get; set; }
}

不会真实删除数据

不会从数据库里真实删除一个Person实体,当需要删除它时,只是把它的IsDeleted属性设置为true(DbContext.SaveChanges时自动执行)。

namespace Mt.EntityFramework
{
/// <summary>
/// Base class for all DbContext classes in the application.
/// </summary>
public abstract class AbpDbContext : DbContext, ITransientDependency, IShouldInitialize
{
protected virtual void CancelDeletionForSoftDelete(DbEntityEntry entry)
{
if (!(entry.Entity is ISoftDelete))
{
return;
} var softDeleteEntry = entry.Cast<ISoftDelete>();
softDeleteEntry.Reload();
softDeleteEntry.State = EntityState.Modified;
softDeleteEntry.Entity.IsDeleted = true;
}
}
}

查找数据时软删除数据不会被获取

实现ISoftDelete之后,当你从数据库获取Person列表,软删除的人员不会被获取,此处有一个示例类,使用一个person仓储获取所有人员:

public class MyService
{
private readonly IRepository<Person> _personRepository; public MyService(IRepository<Person> personRepository)
{
_personRepository = personRepository;
} public List<Person> GetPeople()
{
return _personRepository.GetAllList();
}
}

GetPeople方法仅获取全部IsDeleted=false(不是delete)的Person。所有的仓储方法和导航属性都工作正常。我们可以添加一些其实where条件、连接等,它会自动添加IsDeleted=false条件到生成的Sql查询。

namespace Mt.EntityFramework
{
/// <summary>
/// Base class for all DbContext classes in the application.
/// </summary>
public abstract class AbpDbContext : DbContext, ITransientDependency, IShouldInitialize
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Filter(AbpDataFilters.SoftDelete, (ISoftDelete d) => d.IsDeleted, false);
//While "(int?)t.TenantId == null" seems wrong, it's needed. See https://github.com/jcachat/EntityFramework.DynamicFilters/issues/62#issuecomment-208198058
modelBuilder.Filter(AbpDataFilters.MustHaveTenant, (IMustHaveTenant t, int tenantId) => t.TenantId == tenantId || (int?)t.TenantId == null, 0);
modelBuilder.Filter(AbpDataFilters.MayHaveTenant, (IMayHaveTenant t, int? tenantId) => t.TenantId == tenantId, 0);
}
}
}

ISoftDelete过滤一直可用,除非你显式禁用它。

Autdit

public interface IHasDeletionTime : ISoftDelete
{
/// <summary>
/// Deletion time of this entity.
/// </summary>
DateTime? DeletionTime { get; set; }
}
public interface IDeletionAudited : IHasDeletionTime
{
/// <summary>
/// Which user deleted this entity?
/// </summary>
long? DeleterUserId { get; set; }
}

所以FullAuditedEntity是软删除, 参阅 ABP框架 - 实体

IMustHaveTenant

如果你正在创建多租户应用并存储所有租户数据在一个数据库里,你明确地不想一个租户的数据意外地被另一个租户看到,这种情况下你可用IMustHaveTenant。例如:

public class Product : Entity, IMustHaveTenant
{
public int TenantId { get; set; } public string Name { get; set; }
}

IMustHaveTenant定义了TenantId,区别不同的租户实体。ABP默认情况下使用IAbpSeesion获取当前TenantId,并自动为当前租户过滤查询。

IMustHaveTenant默认可用。

如果当前用户尚未登录系统或当前是个宿主用户(宿主用户是一个更高级别的用户,它管理租户和租户数据),ABP自动禁用IMustHaveTenant过滤,因此,可以获取所有租户的所有数据。注意:这与安全性无关,你应当一直授权敏感数据。

IMayHaveTenant

如果一个实体类被租户和宿主共享(也就是说一个实体对象可被租户或宿主拥有),你可以使用IMayHaveTenant过滤。IMayHaveTenant接口定义了TenantId,但它是可空的。

public class Role : Entity, IMayHaveTenant
{
public int? TenantId { get; set; } public string RoleName { get; set; }
}

一个null值表示这是个宿主实体,一个非null值表示这个实体被Id为TenantId的租户拥有。默认情况下,ABP使用IAbpSeesion获取当前TenantId。IMayHaveTenant过滤不像IMustHaveTenant那么通用,但在实体类型通用宿主和租户时,需要它。

IMayHaveTenant一直可用,除非你显式禁用它。

禁用过滤

调用DisableFilter方法可以禁用每个工作单元的一个过滤,如下:

var people1 = _personRepository.GetAllList();

using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.SoftDelete))
{
var people2 = _personRepository.GetAllList();
} var people3 = _personRepository.GetAllList();

DisableFilter接受一个或多个过滤名称组成的字符串,AbpDataFilters.SoftDelete是个字符串常量,它代表ABP的软删除过滤。

people2将包含被软删除的people,people1和people3只包含未被软删除的people。使用using声明,你可以在using域内禁用一个过滤。如果不使用using声明,过滤会被禁用,直到当前工作单元结束或是你显示启用这个过滤。

你可以注入IUnitOfWorkManager,然后像上例那样使用,如果你的类继承自特殊的基类(如应用服务,AbpController,AbpApiController...),你也可以直接使用CurrentUnitOfWork属性。

关于using声明

如果一个过滤是启用的,当你使用using声明,调用DisableFilter方法,这个过滤会被禁用,然后在using声明之后,自动地被启用。但是如果这个过滤是在使用using声明前,就是禁用的,那么DisableFilter什么也不做,在using声明之后,它仍然是禁用的。

关于多租户

你可以禁用多租户过滤来查询所有租户的数据,但这只对一个数据库有效。如果你为每个租户使用分离的数据库,禁用过滤就无法帮助你获取所有租户的数据,因为数据在不同的数据库甚至是不同的服务器,更多信息查看多租户文档。

启用过滤

在一个工作单元里,你可以使用EnableFilter方法启用一个过滤。相似于(也相反于)DisableFilter。EnableFilter也在使用using声明时,返回可释放对象,用来在有需要的情况下重新禁用过滤。

设置过滤参数

一个过滤可以参数化,IMusthaveTenant过滤就是一个例子,因为当前租户的Id在运行时才能检测到。对于此类过滤,如果有需要,我们可以修改过滤值,如:

CurrentUnitOfWork.SetFilterParameter(AbpDataFilters.MusthaveTenant, AbpDataFilters.Parameters.TenantId, 42);

另一个例子:为IMayHaveTenant过滤,设置租户Id:

CurrentUnitOfWork.SetFilterParameter(AbpDataFilters.MayHaveTenant, AbpDataFilters.Parameters.TenantId, 42);

SetFilterParameter方法也返回一个IDisposeble,所以使用一个using声明,让它自动在声明之后恢复原值。

public class EfDynamicFiltersUnitOfWorkFilterExecuter : IEfUnitOfWorkFilterExecuter
{
public void ApplyDisableFilter(IUnitOfWork unitOfWork, string filterName)
{
foreach (var activeDbContext in unitOfWork.As<EfUnitOfWork>().GetAllActiveDbContexts())
{
activeDbContext.DisableFilter(filterName);
}
} public void ApplyEnableFilter(IUnitOfWork unitOfWork, string filterName)
{
foreach (var activeDbContext in unitOfWork.As<EfUnitOfWork>().GetAllActiveDbContexts())
{
activeDbContext.EnableFilter(filterName);
}
} public void ApplyFilterParameterValue(IUnitOfWork unitOfWork, string filterName, string parameterName, object value)
{
foreach (var activeDbContext in unitOfWork.As<EfUnitOfWork>().GetAllActiveDbContexts())
{
if (TypeHelper.IsFunc<object>(value))
{
activeDbContext.SetFilterScopedParameterValue(filterName, parameterName, (Func<object>)value);
}
else
{
activeDbContext.SetFilterScopedParameterValue(filterName, parameterName, value);
}
}
} public void ApplyCurrentFilters(IUnitOfWork unitOfWork, DbContext dbContext)
{
foreach (var filter in unitOfWork.Filters)
{
if (filter.IsEnabled)
{
dbContext.EnableFilter(filter.FilterName);
}
else
{
dbContext.DisableFilter(filter.FilterName);
} foreach (var filterParameter in filter.FilterParameters)
{
if (TypeHelper.IsFunc<object>(filterParameter.Value))
{
dbContext.SetFilterScopedParameterValue(filter.FilterName, filterParameter.Key, (Func<object>)filterParameter.Value);
}
else
{
dbContext.SetFilterScopedParameterValue(filter.FilterName, filterParameter.Key, filterParameter.Value);
}
}
}
}
}

SetTenantId 方法

虽然你可以使用SetFilterParameter方法,为MayHaveTenant和MusthaveTenant修改过滤值,但修改租户过滤有一个更好的方式:SetTenantId()。SetTenantId为这两个过滤修改参数值,并且单数据库或每个租户一个数据库都有效。所以,总是推荐用SetTenantId修改租户过滤的参数值。查看多租户文档获取更多信息。

自定义过滤

定义一个接口

为自定义过滤并整合到ABP,首先,定义一个接口,它将被使用这个过滤的实体实现。假设我们要通过PersonId自动过滤实体,接口示例:

public interface IHasPerson
{
int PersonId { get; set; }
}

实现这个接口

public class Phone : Entity, IHasPerson
{
[ForeignKey("PersonId")]
public virtual Person Person { get; set; }
public virtual int PersonId { get; set; } public virtual string Number { get; set; }
}

定义过滤

因为ABP使用EntityFramework.DynamicFilters,我们使用它的规则来定义这个过滤,在我们的DbContext类里,我们重写OnModelCreating,如下所示:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder); modelBuilder.Filter("PersonFilter", (IHasPerson entity, int personId) => entity.PersonId == personId, 0);
}

“PersonFilter”是这个过滤的唯一的名称,第二个参数表明过滤的接口和过滤参数PersonId(如果过滤不可参数化,可不用),最后一个参数是personId的默认值。

注册这个过滤

最后在我们模块的PreInitialize方法里,注册这个过滤到ABP工作单元系统:

Configuration.UnitOfWork.RegisterFilter("PersonFilter", false);

第一个参数就是我们之前定义的名称,第二个参数指明默认情况下是否启用。

使用

using (CurrentUnitOfWork.EnableFilter("PersonFilter"))
{
using(CurrentUnitOfWork.SetFilterParameter("PersonFilter", "personId", 42))
{
var phones = _phoneRepository.GetAllList();
//...
}
}

我们应当从其它地方获取personId代替硬编码。上面是个可参数化过滤的例子,一个过滤可能有0或多个参数,如果没有参数,就没有必要设置过滤的参数值,同样,如果默认过滤是启用的,就不必再手动启用它(当然,我们可以禁用它)。

EntityFramework.DynamicFilters 文档

获取更多有关动态数据过滤信息,请参阅github页上的文档: https://github.com/jcachat/EntityFramework.DynamicFilters

我们可以为security, active/passive等实体自定义过滤。

其它 ORM

ABP数据过滤是为EntityFramework和NHibernate实现的,其它ORM上不可以用(包含EntityFramework Core)。但实质上,你可以在大多数情况上模仿它,只要你也是用仓储来获取数据的,你可以自定义一个仓储,然后重写GetAll和其它所需的数据获取方法。

ABP文档笔记 - 数据过滤的更多相关文章

  1. ABP文档笔记系列

    ABP文档笔记 - 模块系统 及 配置中心 ABP文档笔记 - 事件BUS ABP文档笔记 - 数据过滤 ABP文档笔记 - 规约 ABP文档笔记 - 配置.设置.版本.功能.权限 ABP文档笔记 - ...

  2. ABP文档笔记 - 通知

    基础概念 两种通知发送方式 直接发送给目标用户 用户订阅某类通知,发送这类通知时直接分发给它们. 两种通知类型 一般通知:任意的通知类型 "如果一个用户发送一个好友请求,那么通知我" ...

  3. ABP文档笔记 - 事件BUS

    文档: ABP框架 - 领域事件(EventBus) EventBus & Domain Events ABP源码分析二十五:EventBus EventBus(事件总线) EventBus是 ...

  4. ABP文档笔记 - 配置、设置、版本、功能、权限

    配置 全局仅一个单例,保存一组配置信息,一般直接在模块的预启动事件中赋值or修改.没有Scope划分,无论租户还是房东亦或者用户读取的值都不会有差异.每个模块都可以扩展这个配置. 设置 它没有层级关系 ...

  5. ABP文档笔记 - 规约

    ABP框架 - 规约 简介 规约模式是一个特别的软件设计模式,业务逻辑可以使用boolean逻辑重新链接业务逻辑(维基百科). 实践中的大部分情况,它是为实体或其它业务对象,定义可复用的过滤器. 理解 ...

  6. ABP文档笔记 - 模块系统 及 配置中心

    ABP框架 - 模块系统 ABP框架 - 启动配置 Module System Startup Configuration ABP源码分析三:ABP Module ABP源码分析四:Configura ...

  7. 如何用Transformer+从PDF文档编辑数据

    ABBYY PDF Transformer+是一款可创建.编辑.添加注释及将PDF文件转换为其他可编辑格式的通用工具,可使用该软件从PDF文档编辑机密信息,然后再发布它们,文本和图像均可编辑,本文将为 ...

  8. GIS专业书籍、文档、数据、网站、工具等干货

    整理.分享一些个人整理的GIS专业书籍.文档.数据.网站.工具等.也希望大家将自己的心得也分享出来,一起交流,共同进步. 如果下载链接失效,请到这里去:地信网 一.原理应用类 GIS基础类 01.地理 ...

  9. MongoDB实战读书笔记(二):面向文档的数据

    1 schema设计原则 1.1 关系型数据库的三大设计范式 第一范式(1NF)无重复的列 第二范式(2NF)属性完全依赖于主键 [ 消除部分子函数依赖 ] 第三范式(3NF)属性不依赖于其它非主属性 ...

随机推荐

  1. Codeforces Round #436 (Div. 2) D. Make a Permutation!

    http://codeforces.com/contest/864/problem/D 题意: 给出n和n个数(ai <= n),要求改变其中某些数,使得这n个数为1到n的一个排列,首先保证修改 ...

  2. Struts(二十五):自定义验证器

    编程验证 Struts2提供了一个Validateable接口,可以使用Action类实现这个接口以提供编程验证: ActionSupport类已经实现了Validateable接口. public ...

  3. Struts(二十一):类型转换与复杂属性、集合属性配合使用

    背景: 本章节主要以复杂属性.集合属性类型转化为例,来学习这两种情况下怎么使用. 复杂对象属性转换场景: 1.新建struts_04 web.xml <?xml version="1. ...

  4. python 函数“四剑客”的使用和介绍

    python函数四剑客:lambda.map.filter和reduce. 一.lambda(匿名函数) 1. 学习lambda要注意一下几点: lambda语句被用来创建新的函数对象,并且在运行的时 ...

  5. 列表(list)之二 -运用篇 -快速生成规律性列表

    生成列表[1*2,3*4,5*6,7*8,9*10,11*12] 方法一:list1=[]for i in range(1,13,2): list1.append(i*(i+1))print(list ...

  6. python系列之 - 并发编程(进程池,线程池,协程)

    需要注意一下不能无限的开进程,不能无限的开线程最常用的就是开进程池,开线程池.其中回调函数非常重要回调函数其实可以作为一种编程思想,谁好了谁就去掉 只要你用并发,就会有锁的问题,但是你不能一直去自己加 ...

  7. [HNOI 2002]彩票

    Description 某地发行一套彩票.彩票上写有1到M这M个自然数.彩民可以在这M个数中任意选取N个不同的数打圈.每个彩民只能买一张彩票,不同的彩民的彩票上的选择不同. 每次抽奖将抽出两个自然数X ...

  8. [HNOI2007]最小矩形覆盖

    题目描述 给定一些点的坐标,要求求能够覆盖所有点的最小面积的矩形,输出所求矩形的面积和四个顶点坐标 输入输出格式 输入格式: 第一行为一个整数n(3<=n<=50000),从第2至第n+1 ...

  9. [HNOI2011]数学作业

    题目描述 小 C 数学成绩优异,于是老师给小 C 留了一道非常难的数学作业题: 给定正整数 N 和 M,要求计算 Concatenate (1 .. N) Mod M 的值,其中 Concatenat ...

  10. bzoj 2555: SubString

    Description 懒得写背景了,给你一个字符串init,要求你支持两个操作 (1):在当前字符串的后面插入一个字符串 (2):询问字符串s在当前字符串中出现了几次?(作为连续子串) 你必须在线支 ...