Introduction
It's common to use the soft-delete pattern which is used to not delete an entity from database but only mark it as 'deleted'. So, if an entity is soft-deleted, it should not be accidently retrieved into the application. To provide that, we would add a SQL where condition like 'IsDeleted = false' in every query we select entities. This is a tedious but more importantly a forgettable task. So, there should be an automatic way of it.
ASP.NET Boilerplate provides data filters those can be used to automatically filter queries based on some rules. There are some pre-defined filters, but also you can create your own filters.
使用软删除模式是很常见的,它用于从数据库中删除实体,但只将其标记为“已删除”。所以,如果一个实体是软删除,它不应该被无意中检索到的应用。
我们将添加一个SQL WHERE条件如“isDeleted =false在每个查询我们选择实体。这是一个乏味的但更重要的是一个被遗忘的任务。所以,应该有一个自动的方法。
ASP.NET提供的模板可用于自动过滤基于一定规则的查询数据过滤器。有一些预定义的过滤器,但也可以创建自己的过滤器。
Pre-Defined Filters(预定义的过滤器)
ISoftDelete
Soft-delete filter is used to automatically filter (extract from results) deleted entities while querying database. If an entity should be soft-deleted, it must implement ISoftDelete interface which defines only IsDeleted property. Example:
软删除过滤器用于在查询数据库时自动过滤(从结果中提取)被删除的实体。如果一个实体应该是软删除,必须实现isoftdelete接口只定义isDeleted属性。例子:
public class Person : Entity, ISoftDelete { public virtual string Name { get; set; } public virtual bool IsDeleted { get; set; } }
A Person entity is not actually deleted from database, instead IsDeleted property is set to true when need to delete it. This is done automatically by ASP.NET Boilerplate when you use IRepository.Delete method (you can manually set IsDeleted to true, but Delete method is more natural and preffered way).
person的实体不是从数据库中删除,而是isDeleted属性设置为true时,需要删除它。这是ASP.NET Boilerplate的当你使用IRepository自动完成。删除方法(你可以手动设置isDeleted为真,但删除方法更加自然和择优的方式)。
After implementing ISoftDelete, when you get list of People from database, deleted people are not retrieved. Here, an example class that uses a person repository to get all people:
在实现ISoftDelete后,当你从数据库查询perople列表时,删除的不会检索到。
public class MyService { private readonly IRepository<Person> _personRepository; public MyService(IRepository<Person> personRepository) { _personRepository = personRepository; } public List<Person> GetPeople() { return _personRepository.GetAllList(); } }
GetPeople method only gets Person entities which has IsDeleted = false (not deleted). All repository methods and also navigation properties properly works. We could add some other Where conditions, joins.. etc. It will automatically add IsDeleted = false condition properly to the generated SQL query.
GetPeople方法只会查询 IsDeleted = false的数据。所有存储库方法和导航属性都正常工作。我们可以添加一些其他条件,加入…它会自动添加isDeleted =假条件正确生成的SQL查询。
When Enabled?
ISoftDelete filter is always enabled unless you explicitly disable it.
A side note: If you implement IDeletionAudited (which extends ISoftDelete) then deletion time and deleter user id are also automatically set by ASP.NET Boilerplate.
注:如果你实现ideletionaudited(延伸isoftdelete)然后删除时间和删除用户ID也由ASP.NET样板自动设置。
IMustHaveTenant
If you are building multi-tenant applications and store all tenant data in single database, you definitely do not want a tenant accidently see other's data. You can implement IMustHaveTenant in that case. Example:
如果你正在建设多租户应用程序和存储在单一的数据库中的所有用户数据,你绝对不希望看到其他房客意外的数据。你可以在这种情况下,实现imusthavetenant。例子:
public class Product : Entity, IMustHaveTenant { public int TenantId { get; set; } public string Name { get; set; } }
IMustHaveTenant defines TenantId to distinguish different tenant entities. ASP.NET Boilerplate uses IAbpSession to get current TenantId by default and automatically filter query for the current tenant.
imusthavetenant定义tenantid区分不同租户的实体。ASP.NET iabpsession样板使用默认的自动过滤查询当前租户获取当前tenantid。
When Enabled?
IMustHaveTenant is enabled by default.
If current user is not logged in to the system or current user is a host user (Host user is an upper level user that can manage tenants and tenant data), ASP.NET Boilerplate automatically disables IMustHaveTenant filter. Thus, all data of all tenant's can be retrieved to the application. Notice that this is not about security, you should always authorize sensitive data.
如果当前用户没有登录到系统中或当前用户是主机用户(主机的用户是一个上层的用户可以管理租户,租户数据),ASP.NET样板自动禁用imusthavetenant滤波器。因此,所有租户的所有数据都可以检索到应用程序。注意,这与安全无关,您应该始终授权敏感数据。
IMayHaveTenant
If an entity class shared by tenants and the host (that means an entity object may be owned by a tenant or the host), you can use IMayHaveTenant filter. IMayHaveTenant interface defines TenantId but it's nullable.
如果一个实体类共享的租户和主机(即一个实体对象可以由承租人或主机拥有),你可以使用imayhavetenant滤波器。imayhavetenant接口定义了tenantid。
public class Role : Entity, IMayHaveTenant { public int? TenantId { get; set; } public string RoleName { get; set; } }
A null value means this is a host entity, a non-null value means this entity owned by a tenant which's Id is the TenantId. ASP.NET Boilerplate uses IAbpSession to get current TenantId by default. IMayHaveTenant filter is not common as much as IMustHaveTenant. But you may need it for common entitiy types used by host and tenants.
空值意味着这是一个主机,一个非空的值意味着这个实体的租客的身份拥有的是tenantid。ASP.NET iabpsession样板使用默认获取当前tenantid。imayhavetenant滤波器不常见的尽imusthavetenant。但是你可能需要它的主人和租户使用常见的实体类型。
When Enabled?
IMayHaveTenant is always enabled unless you explicitly disable it.
Disable Filters
You can disable a filter per unit of work by calling DisableFilter method as shown below:
您可以禁用每个工作单元过滤器通过调用DisableFilter方法如下图所示:
var people1 = _personRepository.GetAllList(); using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.SoftDelete)) { var people2 = _personRepository.GetAllList(); } var people3 = _personRepository.GetAllList();
DisableFilter method gets one or more filter names as strings. AbpDataFilters.SoftDelete is a constant string that contains name of the standard soft delete filter of ASP.NET Boilerplate.
DisableFilter方法获取一个或多个过滤名称的字符串。abpdatafilters.softdelete是常量字符串,包含标准的软删除筛选器名称ASP.NET样板。
people2 will also include deleted people while people1 and people3 will be only non-deleted people. With using statement, you can disable a filter in a scope. If you don't use using stamement, filter will be disabled until end of the current unit of work or you enable it again explicitly.
people2包括删除的people,people1和people3没有删除的people。使用语句,可以在范围内禁用筛选器。如果你不使用stamement使用不,过滤器将被禁用,直到结束目前的工作单位或你使它再次明确。
You can inject IUnitOfWorkManager and use as in the example. Also, you can use CurrentUnitOfWork property as a shortcut if you class inherits some special base classes (like ApplicationService, AbpController, AbpApiController...).
About using Statement(关于使用说明)
If a filter is enabled when you call the DisableFilter method with a using statement, the filter is disabled, then automatically re-enabled after using statement. But if the filter was already disabled before the using statement, DisableFilter actually does nothing and the filter remains disabled even after the using statement.
如果过滤器被启用时,你叫DisableFilter方法与使用声明,过滤器被禁用,然后再启用后自动使用声明。但如果过滤器在使用声明已经禁用,DisableFilter其实没有和过滤器保持残疾甚至使用语句之后。
About Multi Tenancy(关于多租户)
You can disable tenancy filters to query all tenant data. But remember that, this works only for single database approach. If you have seperated databases for each tenants, disabling filter does not help to query all data of all tenants, since they are in different databases, can be even in different servers. See multi tenancy document for more information.
可以禁用租户筛选器来查询所有租户数据。但是请记住,这只适用于单个数据库方法。如果您为每个租户分离数据库,禁用筛选器并不能帮助查询所有租户的所有数据,因为它们位于不同的数据库中,甚至可以在不同的服务器中。更多信息参见多租户文档。
Disable Filters Globally
If you need, you can disable pre-defined filters globally. For example, to disable soft delete filter globally, add this code to PreInitialize method of your module:
Configuration.UnitOfWork.OverrideFilter(AbpDataFilters.SoftDelete, false);
Enable Filters
You can enable a filter in a unit of work using EnableFilter method, as similar to (and opposite of) DisableFilter. EnableFilter also returns disposable to be used in a using statement to automatically re-disable the filter if needed.
Setting Filter Parameters
A filter can be parametric. IMustHaveTenant filter is an example of these types of filters since current tenant's Id is determined on runtime. For such filters, we can change filter value if needed. Example:
过滤器可以是参数的。imusthavetenant滤波器是这些类型的过滤器,因为当前租客的身份被确定在运行时实例。对于这种过滤器,如果需要,我们可以改变滤波器的值。例子:
CurrentUnitOfWork.SetFilterParameter("PersonFilter", "personId", 42);
Another example: To set the tenantId value for the IMayHaveTenant filter:
CurrentUnitOfWork.SetFilterParameter(AbpDataFilters.MayHaveTenant, AbpDataFilters.Parameters.TenantId, 42);
SetFilterParameter method also returns an IDisposable. So, we can use it in a using statement to automatically restore the old value after using statement.
setfilterparameter方法也会返回一个IDisposable。因此,我们可以在使用语句中使用它在使用语句后自动恢复旧值。
SetTenantId Method
While you can use SetFilterParameter method to change filter value for MayHaveTenant and MustHaveTenant filters, there is a better way to change tenant filter: SetTenantId(). SetTenantId changes parameter value for both filters, and also works for single database and database per tenant approaches. So, it's always suggested to use SetTenantId to change tenancy filter parameter values. See multi tenancy document for more information.
虽然您可以使用setfilterparameter方法改变mayhavetenant和musthavetenant滤波器的滤波值,有一个更好的方式来改变滤波器:settenantid()房客。settenantid改变参数值为过滤,也适用于单数据库和每个租户的方法数据库。因此,它总是建议使用settenantid改变租赁滤波器参数值。更多信息参见多租户文档。
ORM Integrations
Data filtering for pre-defined filters works for NHibernate, Entity Framework 6.x and Entity Framework Core. Currently, you can only define custom filters for Entity Framework 6.x.
Entity Framework
For Entity Framework integration, automatic data filtering is implemented using EntityFramework.DynamicFilters library.
To create a custom filter for Entity Framework and integrate to ASP.NET Boilerplate, first we should define an interface that will be implemented by entities which use this filter. Assume that we want to automatically filter entities by PersonId. Example interface:
创建一个自定义过滤器和实体框架整合到ASP.NET Boilerplate,首先要定义一个接口,将由实体使用该滤波器实现。假设我们想自动过滤实体的事。接口实例:
public interface IHasPerson { int PersonId { get; set; } }
Then we can implement this interface for needed entities. Example entity:
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; } }
We use it's rules to define the filter. In our DbContext class, we override OnModelCreating and define filter as shown below:
我们用它的规则来定义过滤器。在我们的DbContext类,我们重写OnModelCreating定义滤波器如下图所示:
protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Filter("PersonFilter", (IHasPerson entity, int personId) => entity.PersonId == personId, 0); }
"PersonFilter" is the unique name of the filter here. Second parameter defines filter interface and personId filter parameter (not needed if filter is not parametric), last parameter is the default value of the personId.
“personfilter”是这里独特的过滤器的名称。二参数定义的过滤器接口,PersonId滤波器参数(如果过滤器不参数不是必须的),最后一个参数是默认值的事。
As the last thing, we must register this filter to ASP.NET Boilerplate's unit of work system in PreInitialize method of our module:
Configuration.UnitOfWork.RegisterFilter("PersonFilter", false);
First parameter is same unique name we defined before. Second parameter indicates whether this filter is enabled or disabled by default. After declaring such a parametric filter, we can use it by supplying it's value on runtime.
第一个参数是我们前面定义的相同的唯一名称。第二个参数表示默认情况下启用或禁用此筛选器。在声明了这样一个参数过滤器之后,我们可以通过在运行时提供它的值来使用它。
using (CurrentUnitOfWork.EnableFilter("PersonFilter")) { using(CurrentUnitOfWork.SetFilterParameter("PersonFilter", "personId", 42)) { var phones = _phoneRepository.GetAllList(); //... } }
We could get the personId from some source instead of statically coded. The example above was for parametric filters. A filter can have zero or more parameters. If it has no parameter, it's not needed to set the filter parameter value. Also, if it's enabled by default, no need to enable it manually (surely, we can disable it).
我们可以从一些光源代替PersonId静态编码。上面的例子是参数滤波器。过滤器可以有零个或多个参数。如果没有参数,则不需要设置筛选器参数值。另外,如果它在默认情况下启用,则无需手动启用(当然,我们可以禁用它)。
Documentation for EntityFramework.DynamicFilters
For more information on dynamic data filters, see documentation on it's github page: https://github.com/jcachat/EntityFramework.DynamicFilters
We can create custom filters for security, active/passive entities and so on.
Other ORMs
For Entity Framework Core and NHibernate, data filtering is implemented in the repository level. That means it only filters when you query over repositories. If you directly use DbContext (for EF Core) or query via custom SQL, you should handle filtering yourself.