我們在數據庫開發中,一般會運用
軟刪除 (soft delete)模式 ,即不直接從數據庫刪除數據 ,而是標記這筆數據為已刪除。因此 ,如果實體被軟刪除了,那么它就應該不會在應用程序中被檢索到。要達到這種效果 ,我們需要在每次檢索實體的查詢語句上添加 SQL的 Where條件 IsDeleted = false 。這是個乏味的工作 。但它是個容易被忘掉的事情。因此 ,我們應該要有個自動的機制來處理這些問題 。
ABP 提供數據過濾器 (Data filters),它使用 自動化的,基於規則的過濾查詢。
ABP已做好的過濾器
ISoftDelete
publicclass Person : Entity, ISoftDelete { publicvirtualstring Name { get; set; } publicvirtualbool IsDeleted { get; set; } }如上面的Person類,實現了ISoftDelete接口,當我們使用IRepository.Delete方法刪除一個Person時,該Person並不真的從數據庫刪除,僅僅是IsDeleted屬性被設置為true。
publicclass MyService { privatereadonly IRepository<Person> _personRepository; publicMyService(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.
如上面代碼,如果某個Person已經被軟刪除,那么使用IRepository獲取所有Person數據時,過濾器會自動過濾掉已經軟刪除的Person,也就是說,GetAll不是獲取實際上數據庫還存有的所用Person數據,而是排除了之前被軟刪除的數據了。
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.
IMustHaveTenant
publicclass Product : IMustHaveTenant { publicvirtualint TenantId { get; set; } publicvirtualstring Name { get; set; } }如果你創建了一個多租戶的應用程序(儲存所有租戶的數據於單一一個數據庫中),你肯定不會希望某個租戶看到其他租戶的資料,此時你可以實現IMustHaveTenant接口。
ABP會使用IABPSession來取得當前TenantId並且自動地替當前租戶進行過濾查詢處理。
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 datas), 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.
IMayHaveTenant
publicclass Product : IMayHaveTenant { publicvirtualint? TenantId { get; set; } publicvirtualstring Name { get; set; } }
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接口定義了TenantId,但是注意它是int?類型,可為空。
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. IMayHaveTenant filter is not common as much as IMustHaveTenant. But you may need it for common structures used by host and tenants.
禁用過濾器
var people1 = _personRepository.GetAllList();
using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.SoftDelete))
{
var people2 = _personRepository.GetAllList();
}
var people3 = _personRepository.GetAllList();
啟用過濾器
也就是使用_unitOfWorkManager.Current.EnableFilter方法
設定過濾器參數
CurrentUnitOfWork.SetFilterParameter("PersonFilter", "personId", 42);
CurrentUnitOfWork.SetFilterParameter(AbpDataFilters.MayHaveTenant, AbpDataFilters.Parameters.TenantId, 42);
自定義過濾器
首先定義一個接口:
publicinterface IHasPerson { int PersonId { get; set; } }
實現接口:
publicclass Phone : Entity, IHasPerson { [ForeignKey("PersonId")] publicvirtual Person Person { get; set; } publicvirtualint PersonId { get; set; } publicvirtualstring Number { get; set; } }
重寫DbContext類中的OnModelCreating 方法(用的EntityFramework.DynamicFilters,參考https://github.com/jcachat/EntityFramework.DynamicFilters):
protectedoverridevoidOnModelCreating(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.
最后,我們要把過濾器注冊到ABP的工作單元系統中,以下代碼需要寫在模塊的Preinitialize方法里:
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")) { CurrentUnitOfWork.SetFilterParameter("PersonFilter", "personId", 42); var phones = _phoneRepository.GetAllList(); //... }