本篇目錄
介紹###
軟刪除模式通常用於不會真正從數據庫刪除一個實體而是僅僅將它標記為"已刪除的"。這樣,如果一個實體是軟刪除的,那么它不應該在應用中檢索到。為了實現這個目的,我們應該在每一個select實體查詢操作中添加一個SQL where條件,如“IsDeleted=false”。這是乏味但是很重要的一項容易忘記的任務。因此,這項工作應該自動完成。
ABP提供了數據過濾器,它們可以基於某些規則自動過濾查詢。有很多預定義的過濾器,但你也可以創建自己的過濾器。
預定義過濾器###
ISoftDelete
軟刪除過濾器用於當查詢數據庫時自動過濾(從結果中提取)已經刪除的實體。如果實體應該是軟刪除的,那么它必須實現只定義了IsDelete屬性的 ISoftDelete接口,例如:
public class Person : Entity, ISoftDelete
{
public virtual string Name { get; set; }
public virtual bool IsDeleted { get; set; }
}
實際上,Person實體並沒有從數據庫中刪除,只是當要刪除它時將它的 IsDelete屬性設置成了true。當使用 IRepository.Delete方法時,ABP會自動處理(你可以手動設置IsDelete為true,但是Delete方法更自然且更受人歡迎)。
實現了ISoftDelete之后,當從數據庫獲取Person的列表時,已經軟刪除的person是不會檢索到的。這里有一個使用了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(沒有刪除)的Person實體。所有的倉儲方法和導航屬性都會正確工作。我們也可以添加一些其他的Where條件,連接等等。它會自動將IsDeleted=false添加到生成的Sql查詢中。
何時開啟ISoftDelete呢?
ISoftDelete過濾器始終是開啟的,除非你顯式關閉了它。
額外注意:如果實現了IDeletionAudited(它繼承了ISoftDelete),那么ABP會自動設置刪除時間和刪除者的id。
IMustHaveTenant
如果你生成的是多租戶應用(在一個數據庫中存儲所有租戶的數據),那么你肯定不想一個租戶意外地看到了其他租戶的數據。這種情況你可以實現IMustHaveTenant。例如:
public class Product : Entity, IMustHaveTenant
{
public int TenantId { get; set; }
public string Name { get; set; }
}
IMustHaveTenant定義了 TenantId來區分不同的租戶實體。ABP使用了 IAbpSession來獲得當前的TenantId,而且自動過濾當前租戶的查詢。
何時開啟IMustHaveTenant呢?
IMustHaveTenant默認是開啟的。
如果當前的用戶沒有登錄到系統或者當前的用戶是一個租主用戶(租主用戶是可以管理租戶和租戶數據的更高級用戶),ABP會自動關閉IMustHaveTenant過濾器。因此,所有租戶的所有數據都可以被檢索到。注意這是沒有涉及到安全的情況,你應該總是要對敏感的數據進行授權。
IMayHaveTenant
如果一個實體類是租戶和租主共享的(這意味着一個實體對象可能被一個租戶或者租主擁有),那么你可以使用IMayHaveTenant過濾器。IMayHaveTenant接口定義了ITenantId但是它是 nullable。
public class Role : Entity, IMayHaveTenant
{
public int? TenantId { get; set; }
public string RoleName { get; set; }
}
如果TenantId的值是null,就意味着這是一個 租主實體;如果值不為null,就意味着該實體被一個 租戶擁有,該租戶的Id就是該TenantId。ABP使用了IAbpSession來獲得當前的TenantId。IMayHaveTenant過濾器不像IMustHaveTenant過濾器那樣常用,但是,對於租戶和租戶公用的結構,你可能需要它。
何時開啟IMayHaveTenant呢?
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可以獲得已經刪除的person實體,然而people1和people3只會獲得沒有刪除的實體。使用 using語句,你可以在一個 作用域(scope)中關閉過濾器。如果你沒有使用using語句,那么過濾器在當前工作單元結束前都是關閉的,除非你顯式再次開啟它。
你可以像上面的例子那樣注入IUnitOfWorkManager使用。此外,你也可以在應用服務(它派生自ApplicationService類)中使用CurrentUnitOfWork屬性作為快捷方式。
關於using語句
如果過濾器是開啟的,當你在using語句中調用DisableFilter方法時,那么過濾器會關閉,然后,當using語句結束時,它會自動再次開啟。但是如果在使用using語句之前過濾器已經關閉了,那么DisableFilter實際上什么都不會做,而且在using語句結束后仍然是關閉的。
開啟過濾器###
你可以在工作單元中使用EnableFilter方法來開啟一個過濾器,和DisableFilter很相似。EnableFilter也返回disable來自動再次關閉該過濾器。
設置過濾器參數###
過濾器是可以帶參數的。IMustHaveTenant過濾器就是這些過濾器類型的一個例子,因為當前的租戶Id要在運行時確定。對於這些過濾器,如果需要的話,我們可以改變過濾器的值。例如:
CurrentUnitOfWork.SetFilterParameter("PersonFilter", "personId", 42);
另一個例子:為IMayHaveTenant過濾器設置tenantId值:
CurrentUnitOfWork.SetFilterParameter(AbpDataFilters.MayHaveTenant, AbpDataFilters.Parameters.TenantId, 42);
SetFilterParameter方法也返回一個IDisposable。因此,我們可以在一個using語句中使用它,在using語句結束時自動還原舊值。
定義自定義過濾器###
要創建一個自定義過濾器並集成到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"))
{
CurrentUnitOfWork.SetFilterParameter("PersonFilter", "personId", 42);
var phones = _phoneRepository.GetAllList();
//...
}
我們可以從一些源中獲得personId而不是靜態代碼中。上面的例子是對於參數化的過濾器來說的。過濾器可以有零個或更多的參數。如果它沒有參數,就不需要設置過濾器的值了。此外,如果它默認是開啟的,它就不需要手動開啟了(當然,我們還可以關閉它)。
EntityFramework.DynamicFilters文檔
關於動態數據過濾器的更多信息,請看github上的文檔。
我們也可以為安全,激活/未激活的實體等創建自定義的過濾器。
其他ORM###
ABP已經實現了EF和NH的數據過濾。對於其他的ORM還不可用。但是實際上,只要你使用倉儲獲得數據,你就可以為絕大多數情況模擬數據過濾。對於這種情況,如果需要的話,你可以創建自定義的倉儲,然后重寫 GetAll和其他的數據檢索方法。