EntityFramework Core 2.0全局過濾(HasQueryFilter)

前言

EntityFramework Core每一次版本的迭代和更新都會帶給咱們驚喜,每次都會盡可能知足大部分使用者的需求。在EF Core 2.0版本中出現了全局過濾新特性即HasQueryFilter,它出現的意義在哪裏?可以解決什麼問題呢?這是咱們須要思考的問題。經過HasQueryFilter方法來建立過濾器可以容許咱們對訪問特定數據庫表的全部查詢額外添加如出一轍的過濾器。它主要用於軟刪除(soft-delete)場景,即用戶並不想返回那些被標記爲已刪除可是還沒有從數據庫中作物理刪除的數據行。數據庫

HasQueryFilter全局過濾器

在文章開頭咱們講述了HasQueryFilter特性所解決的問題,下面咱們利用實際例子來說述一下。在許多場景中咱們並不會從物理上刪除數據,而只是僅僅改變數據的狀態。這個時候就凸顯了HasQueryFilter特性的做用。好比咱們建立一個博客實體。express

    public class Blog
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Url { get; set; }
        public bool IsDeleted { get; set; }
        public DateTime CreatedTime { get; set; }
        public DateTime ModifiedTime { get; set; }
        public ICollection<Post> Posts { get; set; }
    }

是否是由於咱們本節講述全局過濾因此纔會加IsDeleted字段呢?顯然不是,這個是有其應用場景,當咱們管理本身的博客時,可能瞌睡沒睡好(固然也有其餘緣由,沒必要糾結於此)致使一個不當心刪除了某一篇文章即便在博客後臺有友好提示【是否刪除該博客】,可是你依然手滑刪除了,這個時候咱們找到博客園運營反應,而後分分鐘就還原了咱們所刪除的博客,就像將文件扔到了回收站再還原同樣。接下來咱們在OnModelCreating方法或者單獨創建的映射類中配置過濾未被刪除的博客,以下:ui

     modelBuilder.Entity<Blog>(e => 
     {
           e.HasQueryFilter(f => !f.IsDeleted);
      });
         

    或者

    public class BlogConfiguration : IEntityTypeConfiguration<Blog>
    {
        public void Configure(EntityTypeBuilder<Blog> builder)
        {
            builder.HasQueryFilter(f => !f.IsDeleted);
            ......
        }
    }

要是咱們對於特殊場景須要禁用查詢過濾或者說忽略查詢過濾又該如何呢?經過對應的APi(IgnoreQueryFilters)便可,以下:spa

            using (var context = new EFCoreDbContext())
            {
                var blogs = context.Blogs
                   .Include(d => d.Posts)
                   .IgnoreQueryFilters()
                  .AsNoTracking()
                  .ToList();
            }

使用HasQueryFilter進行查詢過濾是否是就是如此簡單呢?是否是問題到此就這樣結束了呢?看過Jeff博客的童鞋知道,Jeff常常問這樣的問題且自問自答,確定不止於此,咱們繼續往下探索。上述咱們只是應用一個博客實體,咱們還存在發表實體且兩者存在關聯關係,咱們同上在Post中定義IsDeleted字段,此時咱們在對Blog進行過濾的同時也對Post進行過濾,以下:翻譯

builder.HasQueryFilter(f => !f.IsDeleted && f.Posts.All(w => !w.IsDeleted));

由上得知使用HasQueryFilter進行過濾篩選的侷限性之一。3d

侷限性一:HasQueryFilter方法過濾篩選沒法應用於導航屬性。code

接下來咱們再來看一個例子,首先咱們定義以下繼承關係:blog

    public abstract class Payment
    {
        public int PaymentId { get; set; }
        public decimal Amount { get; set; }
        public string Name { get; set; }
        public bool IsDeleted { get; set; }
    }

    public class CashPayment : Payment
    {
    }

    public class CreditcardPayment : Payment
    {
        public string CreditcardNumber { get; set; }
    }

上述咱們定義支付基類Payment,而後派生出現金支付CashPayment和信用卡支付CreditcardPayment子類,接下來咱們配置以下查詢過濾:繼承

builder.HasQueryFilter(f => !(f is CreditcardPayment && f.IsDeleted && ((CreditcardPayment)f).IsDeleted));

接下來咱們進行以下查詢:接口

            using (var context = new EFCoreDbContext())
            {
                var payments = context.Payments.ToList();
            }

沒毛病,接下來咱們查詢子類CreditcardPayment,以下:

            using (var context = new EFCoreDbContext())
            {
                var payments = context.Payments.OfType<CashPayment>().ToList();
            }

由上得知使用HasQueryFilter進行過濾篩選的侷限性之二。

侷限性二:HasQueryFilter方法過濾篩選只能定義在基類中,沒法對子類進行過濾。

HasQueryFilter通用解決方案

不知咱們是否思考過一個問題,如有不少實體都有其軟刪除場景,那麼咱們就都須要加上IsDeleted字段,同時還須要配置全局過濾器,如此重複性動做咱們是否以爲厭煩,搬磚的咱們的單從程序角度來看,搬磚的本源就是爲了解放勞動生產率,提升工做效率,說的更通俗一點則是解決重複性勞動。此時爲了解決上述所延伸出的問題,咱們徹底可抽象出一個軟刪除接口,以下:

    public interface ISoftDleteBaseEntity
    {
        bool IsDeleted { get; set; }
    }

接下來讓全部須要進行軟刪除的實體繼承該接口,好比博客實體,以下:

    public class Blog : ISoftDleteBaseEntity
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Url { get; set; }
        public bool IsDeleted { get; set; }
        ......
    }

爲了解決重複性配置勞動,咱們讓全部繼承自上述接口一次性在OnModelCreating方法中配置全局過濾,如此一來則免去了在每一個對應實體映射類中進行配置,以下:

            Expression<Func<ISoftDleteBaseEntity, bool>> expression = e => !e.IsDeleted;
            foreach (var entityType in modelBuilder.Model.GetEntityTypes()
                .Where(e => typeof(ISoftDleteBaseEntity).IsAssignableFrom(e.ClrType)))
            {
                modelBuilder.Entity(entityType.ClrType).HasQueryFilter(expression);
            }

願望是美好的,結果尼瑪拋出異常不支持該操做。看到上述lambda表達式都沒有翻譯過來,此時咱們轉到定義看看。

public virtual EntityTypeBuilder HasQueryFilter([CanBeNullAttribute] LambdaExpression filter);

竟然參數類型不是以表達式樹的形式給出,若是是以下形式則確定能夠。

public virtual EntityTypeBuilder HasQueryFilter([CanBeNullAttribute] Expression<Func<TEntity, TProperty>> filter);

因此問題出在沒法翻譯lambda表達式,那麼咱們就來自動構建lambda表達式參數和主體,以下:

             foreach (var entityType in modelBuilder.Model.GetEntityTypes()
                .Where(e => typeof(ISoftDleteBaseEntity).IsAssignableFrom(e.ClrType)))
            {
                modelBuilder.Entity(entityType.ClrType).Property<Boolean>("IsDeleted");
                var parameter = Expression.Parameter(entityType.ClrType, "e");
                var body = Expression.Equal(
                    Expression.Call(typeof(EF), nameof(EF.Property), new[] { typeof(bool) }, parameter, Expression.Constant("IsDeleted")),
                Expression.Constant(false));
                modelBuilder.Entity(entityType.ClrType).HasQueryFilter(Expression.Lambda(body, parameter));
            }

至此美好願望得意實現,算是給出了一種通用解決方案。

總結

本節咱們講述了EntityFramework Core 2.0中的新特性HasQueryFilter,使用此特性仍然存在侷限性沒法對導航屬性屬性進行過濾,對實體爲繼承次模型,那麼過濾篩選只能定義在基類中,可是最後咱們給出了通用解決方案解決重複定義查詢過濾問題,但願給閱讀的您一點幫助。精簡的內容,簡單的講解,但願對閱讀的您有所幫助,咱們明天再會。

相關文章
相關標籤/搜索