實體框架高級應用之動態過濾 EntityFramework DynamicFilters


實體框架高級應用之動態過濾 EntityFramework DynamicFilters

我們開門見山,直奔主題。

 

一、EntityFramework DynamicFilters 是什么,它能做什么?

  EntityFramework DynamicFilters是一個開源項目。你可以到這里去下載它的源碼。顧名思義,它為我們做的事,就是幫我們動態過濾數據。為了照顧初學者,我們從頭道來。

  1、何為數據過濾?

     數據過濾說簡單點,就是去掉我們不想要的數據。SQL語句中的where從句,Linq中的where從句,還有擴展方法Where,就是完成這件光榮任務的。

 

  2、何為動態?

    動態的意思就是不死板地應用我們所寫的條件,比如,我們在一個地方寫了where從句,它只能用於這次查詢,下次遇到相似的情況時,我們還得老老實實的寫 where xxx=xxx。很長的一段時間,我們一直這樣,很和諧地使用着這種方法。突然有一天,抓了抓頭:如果類似的情況,能自動加上相應的過慮條件,或是應用相應的規則,該有多好?於是就有動態。當然這里的動態,只是我們面對問題的一個方面。

 

  3、廢話半天,它到底能做什么,具體點,好不?

    它可以為我們創建全局的,針對實體框架查詢的過慮器,這些過濾器會自動應用於每一個查詢。能被用於支持多租戶,軟刪除,等等。過濾器能通過返回布爾類型的Linq表達式來創建,同時還支持Contains()操作符(方法)。目前支持的數據庫有MS SQL Server(包含 Azure),MySql,Oracle。

 

二、 沒有它時,我們是怎么做的?

  我們以軟刪除(不是真正意義上的刪除數據,只是在相應的記錄上作一個刪除標識)為例。正因為數據沒有被真正地刪除,只是被我們用一個標識給標記起來了,那么,我們就得在每一個查詢的地方加上一個條件(過濾掉標記為刪除的數據),代碼可能長成這樣:

1  var blogs = context.BlogEntries.Where(b => b.IsDeleted == false).ToList();

上面的代碼就不用多解釋了,相信你能看明白。 如果是sql 語句,你可能會說,這有什么難的,我找一個地方,把所有的查詢拼接上這個條件不就OK。 確實如此,但,這里只是拿這個簡單的場景來作為示例,復雜的場景呢?其次,Linq表達式拼接條件 ,不是像字符串那樣隨心所欲,至少很大一部分人是這樣,當然也包含我。每一個查詢都手工加上這樣的條件,不光是工作量增加了,可維護性降低了,還分散了我們的核心業務邏輯的注意力。

 

三、EntityFramework DynamicFilters給我們帶來了改變

  當然,它只是眾多解決方案之一,只是作者無私的分享出來了,沒把它當成寶供在自己的電腦里。 我只需要在上下文DbContext的OnModelCreating 方法中添加過濾器。代碼如下:

 1     protected override void OnModelCreating(DbModelBuilder modelBuilder)
 2         {
 3             base.OnModelCreating(modelBuilder);
 4 
 5             //限制所有針對BlogEntry查詢的過慮(只獲取未刪除的)
 6             //這里的全局過慮,使用了委托,以便在每次需要計算值
 7             //重要:如果值使用的是一個委托,請確保它在你的應用中是全局的,
 8             modelBuilder.Filter("BlogEntryFilter", (BlogEntry b, bool isDeleted) => (b.IsDeleted == isDeleted),
 9                                                 () => false);
10 
11         }

就這樣,它就會在我們每一個關於Blog實體的查詢中添加上條件(b => b.IsDeleted == false)。我們無需關心它如何添加這個條件,使用的地方,完全透明,就像沒有它一樣。示例代碼如下:

 1          /// <summary>
 2         /// 查詢
 3         /// </summary>
 4         /// <param name="context"></param>
 5         /// <param name="userName"></param>
 6         private static void Query(ExampleContext context, string userName)
 7         {
 8             var account = context.Accounts
 9                 .Include(a => a.BlogEntries).FirstOrDefault(a => a.UserName == userName);
13 Console.WriteLine("賬號{0}的博客有:",userName); 14 if (account == null) return; 15 foreach (var blog in account.BlogEntries) 16 { 17 Console.WriteLine("{0}",blog.Id); 18 } 19 }

但需要注意的是,如果在同一個上下文DbContext實例中,運用過慮器之前,過慮器有被禁用過,而數據被緩存時,過濾器就不會起任何效果,所有使用時,你一定要避免在同一個上下文中因更改過濾器而影響結果的情況。

如果你在某種情況下不想使用過慮器時,你可以使用如下代碼將其禁用:

1             //禁用過濾器
2             context.DisableFilter("BlogEntryFilter");    

 

注意:禁用只對當前上下文DbContext實例有效,不影響別的上下文實例。如果你想對所有的上下文實例有效時,可以在 OnModelCreating方法中使用全局禁用函數:

1 //全局禁用過濾器
2 modelBuilder.DisableFilterGlobally("BlogEntryFilter");

 

啟用的代碼類似,這里就不多少了,直接看代碼:

1             //啟用過濾器
2             context.EnableFilter("BlogEntryFilter");
3             context.EnableAllFilters();

 

說了這么多,我們來看看運用過濾器的效果吧,代碼如下:

 1  class Program {
 2         static void Main(string[] args) {
 3 
 4             // 過濾器默認啟用
 5             var context = new ExampleContext();
 6 
 7             Console.WriteLine(" 使用過濾器BlogEntryFilter進行查詢");
 8             Query(context, "homer");
 9 
10             //禁用過濾器
11             context.DisableFilter("BlogEntryFilter");
12             
13             Console.WriteLine(" 禁用過濾器BlogEntryFilter進行查詢");
14             Query(context, "homer");
15 
16             Console.ReadLine();
17         }
18 
19         /// <summary>
20         /// 查詢
21         /// </summary>
22         /// <param name="context"></param>
23         /// <param name="userName"></param>
24         private static void Query(ExampleContext context, string userName)
25         {
26             var account = context.Accounts
27                 .Include(a => a.BlogEntries).FirstOrDefault(a => a.UserName == userName);
28 
29             Console.WriteLine("賬號{0}的博客有:",userName);
30             if (account == null) return;
31             foreach (var blog in account.BlogEntries)
32             {
33                 Console.WriteLine("{0}",blog.Id);
34             }
35         }
36     }

 

代碼輸出如下:

 

四、EntityFramework DynamicFilters原理概述

  它是通過在對象 DbModelBuilder 上添加擴展方法Filter實現的,核心代碼如下:

 1   private static void Filter<TEntity>(DbModelBuilder modelBuilder, string filterName, LambdaExpression predicate, params object[] valueList)
 2         {
 3             InitializeDynamicFilters(null);
 4 
 5             filterName = ScrubFilterName(filterName);
 6 
 7             modelBuilder.Conventions.Add(new DynamicFilterConvention(filterName, typeof(TEntity), predicate));
 8 
 9             //  Always add the filter to _GlobalParameterValues - need it to be able to disable it
10             _GlobalParameterValues.TryAdd(filterName, new DynamicFilterParameters());
11 
12             int numParams = predicate.Parameters == null ? 0 : predicate.Parameters.Count;
13             int numValues = valueList == null ? 0 : valueList.Length;
14             for (int i = 1; i < numParams; i++)
15             {
16                 object value = ((i - 1) < numValues) ? valueList[i - 1] : null;
17                 SetFilterGlobalParameterValue(null, filterName, predicate.Parameters[i].Name, value);
18             }
19         }

 

整個項目的源代碼不多,如果你有興趣,請閱讀源代碼。文中使用的代碼,請於結尾處下載。

最后,我要說明的是,文中並沒有把 EntityFramework DynamicFilters的方方面面說完,只是說了一些常見的場景。更多的細節,說閱讀源碼,或者和大家一起實踐、交流。

 

文中示例源代碼下載地址:http://files.cnblogs.com/files/VolcanoCloud/EFDynamicFilterDemo.rar

 

實體框架交流QQ群:  458326058,歡迎有興趣的朋友加入一起交流

謝謝大家的持續關注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM