软删除过滤用来在查询数据库时,自动过滤(从结果中抽取)已删除的实体。若是一个实体能够被软删除,它必须实现ISoftDelete接口,该接口只定义了一个IsDeleted属性,例如:html
public class Person : Entity, ISoftDelete { public virtual string Name { get; set; } public virtual bool IsDeleted { get; set; } }
不会从数据库里真实删除一个Person实体,当须要删除它时,只是把它的IsDeleted属性设置为true(DbContext.SaveChanges时自动执行)。git
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仓储获取全部人员:github
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过滤一直可用,除非你显式禁用它。安全
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。例如:app
public class Product : Entity, IMustHaveTenant { public int TenantId { get; set; } public string Name { get; set; } }
IMustHaveTenant定义了TenantId,区别不一样的租户实体。ABP默认状况下使用IAbpSeesion获取当前TenantId,并自动为当前租户过滤查询。框架
IMustHaveTenant默承认用。ide
若是当前用户还没有登陆系统或当前是个宿主用户(宿主用户是一个更高级别的用户,它管理租户和租户数据),ABP自动禁用IMustHaveTenant过滤,所以,能够获取全部租户的全部数据。注意:这与安全性无关,你应当一直受权敏感数据。ui
若是一个实体类被租户和宿主共享(也就是说一个实体对象可被租户或宿主拥有),你可使用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); } } } } }
虽然你可使用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或多个参数,若是没有参数,就没有必要设置过滤的参数值,一样,若是默认过滤是启用的,就没必要再手动启用它(固然,咱们能够禁用它)。
获取更多有关动态数据过滤信息,请参阅github页上的文档: https://github.com/jcachat/EntityFramework.DynamicFilters
咱们能够为security, active/passive等实体自定义过滤。
ABP数据过滤是为EntityFramework和NHibernate实现的,其它ORM上不能够用(包含EntityFramework Core)。但实质上,你能够在大多数状况上模仿它,只要你也是用仓储来获取数据的,你能够自定义一个仓储,而后重写GetAll和其它所需的数据获取方法。