軟刪除過濾用來在查詢數據庫時,自動過濾(從結果中抽取)已刪除的實體。若是一個實體能夠被軟刪除,它必須實現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和其它所需的數據獲取方法。