ABP文檔筆記 - 數據過濾

ABP文檔筆記 - 數據過濾

  1. 預定義的過濾
    1. ISoftDelete
      1. 不會真實刪除數據
      2. 查找數據時軟刪除數據不會被獲取
      3. Autdit
    2. IMustHaveTenant
    3. IMayHaveTenant
    4. 禁用過濾
    5. 啓用過濾
    6. 設置過濾參數
    7. SetTenantId 方法
  2. 自定義過濾
    1. 定義一個接口
    2. 實現這個接口
    3. 定義過濾
    4. 註冊這個過濾
    5. 使用
    6. EntityFramework.DynamicFilters 文檔
  3. 其它 ORM
  4. 目 錄

1、 預定義的過濾

1.1、 ISoftDelete

軟刪除過濾用來在查詢數據庫時,自動過濾(從結果中抽取)已刪除的實體。如果一個實體可以被軟刪除,它必須實現ISoftDelete接口,該接口只定義了一個IsDeleted屬性,例如:

public class Person : Entity, ISoftDelete
{
    public virtual string Name { get; set; }

    public virtual bool IsDeleted { get; set; }
}

1.1.1、 不會真實刪除數據

不會從數據庫裏真實刪除一個Person實體,當需要刪除它時,只是把它的IsDeleted屬性設置爲true(DbContext.SaveChanges時自動執行)。

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;
        }
    }
}

1.1.2、 查找數據時軟刪除數據不會被獲取

實現ISoftDelete之後,當你從數據庫獲取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(不是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過濾一直可用,除非你顯式禁用它。

1.1.3、 Autdit

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框架 - 實體

1.2、 IMustHaveTenant

如果你正在創建多租戶應用並存儲所有租戶數據在一個數據庫裏,你明確地不想一個租戶的數據意外地被另一個租戶看到,這種情況下你可用IMustHaveTenant。例如:

public class Product : Entity, IMustHaveTenant
{
    public int TenantId { get; set; }

    public string Name { get; set; }
}

IMustHaveTenant定義了TenantId,區別不同的租戶實體。ABP默認情況下使用IAbpSeesion獲取當前TenantId,並自動爲當前租戶過濾查詢。

IMustHaveTenant默認可用。

如果當前用戶尚未登錄系統或當前是個宿主用戶(宿主用戶是一個更高級別的用戶,它管理租戶和租戶數據),ABP自動禁用IMustHaveTenant過濾,因此,可以獲取所有租戶的所有數據。注意:這與安全性無關,你應當一直授權敏感數據。

1.3、 IMayHaveTenant

如果一個實體類被租戶和宿主共享(也就是說一個實體對象可被租戶或宿主擁有),你可以使用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一直可用,除非你顯式禁用它。

1.4、 禁用過濾

調用DisableFilter方法可以禁用每個工作單元的一個過濾,如下:

// 賬號轉賬領域服務類

public class AccountService

{

    private readonly IAccountRepository _productRepository;

    private readonly IUnitOfWork _unitOfWorkManager; //注入使用IUnitOfWork

 

    public AccountService(IAccountRepository productRepository, IUnitOfWork unitOfWork)

    {

        _productRepository = productRepository;

        _unitOfWorkManager= unitOfWork;           

    }

     

    public void Transfer(Account from, Account to, decimal amount)

    {

        if (from.Balance >= amount)

        {

            from.Balance -= amount;

            to.Balance += amount;

 

            _productRepository.Save(from);

            _productRepository.Save(to);

            _unitOfWork.Commit();

        }

    }

}

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聲明之後,它仍然是禁用的。

關於多租戶

你可以禁用多租戶過濾來查詢所有租戶的數據,但這隻對一個數據庫有效。如果你爲每個租戶使用分離的數據庫,禁用過濾就無法幫助你獲取所有租戶的數據,因爲數據在不同的數據庫甚至是不同的服務器,更多信息查看多租戶文檔。

1.5、 啓用過濾

在一個工作單元裏,你可以使用EnableFilter方法啓用一個過濾。相似於(也相反於)DisableFilter。EnableFilter也在使用using聲明時,返回可釋放對象,用來在有需要的情況下重新禁用過濾。

1.6、 設置過濾參數

一個過濾可以參數化,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);
                }
            }
        }
    }
}

1.7、 SetTenantId 方法

雖然你可以使用SetFilterParameter方法,爲MayHaveTenant和MusthaveTenant修改過濾值,但修改租戶過濾有一個更好的方式:SetTenantId()。SetTenantId爲這兩個過濾修改參數值,並且單數據庫或每個租戶一個數據庫都有效。所以,總是推薦用SetTenantId修改租戶過濾的參數值。查看多租戶文檔獲取更多信息。

2、 自定義過濾

2.1、 定義一個接口

爲自定義過濾並整合到ABP,首先,定義一個接口,它將被使用這個過濾的實體實現。假設我們要通過PersonId自動過濾實體,接口示例:

public interface IHasPerson
{
    int PersonId { get; set; }
}

2.2、 實現這個接口

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; }
}

2.3、 定義過濾

因爲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的默認值。

2.4、 註冊這個過濾

最後在我們模塊的PreInitialize方法裏,註冊這個過濾到ABP工作單元系統:

Configuration.UnitOfWork.RegisterFilter("PersonFilter", false);

第一個參數就是我們之前定義的名稱,第二個參數指明默認情況下是否啓用。

2.5、 使用

using (CurrentUnitOfWork.EnableFilter("PersonFilter"))
{
    using(CurrentUnitOfWork.SetFilterParameter("PersonFilter", "personId", 42))
    {
        var phones = _phoneRepository.GetAllList();
        //...
    }
}

我們應當從其它地方獲取personId代替硬編碼。上面是個可參數化過濾的例子,一個過濾可能有0或多個參數,如果沒有參數,就沒有必要設置過濾的參數值,同樣,如果默認過濾是啓用的,就不必再手動啓用它(當然,我們可以禁用它)。

2.6、 EntityFramework.DynamicFilters 文檔

獲取更多有關動態數據過濾信息,請參閱github頁上的文檔: https://github.com/jcachat/EntityFramework.DynamicFilters

我們可以爲security, active/passive等實體自定義過濾。

3、 其它 ORM

ABP數據過濾是爲EntityFramework和NHibernate實現的,其它ORM上不可以用(包含EntityFramework Core)。但實質上,你可以在大多數情況上模仿它,只要你也是用倉儲來獲取數據的,你可以自定義一個倉儲,然後重寫GetAll和其它所需的數據獲取方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章