使用Expression實現數據的任意欄位過濾(1)

来源:http://www.cnblogs.com/huwz/archive/2016/12/27/ExpressionFilter1.html
-Advertisement-
Play Games

在項目常常要和數據表格打交道。 現在BS的通常做法都是前端用一個js的Grid控制項, 然後通過ajax的方式從後臺載入數據, 然後將數據和Grid綁定。 數據往往不是一頁可以顯示完的, 所以要加分頁;然後就是根據關鍵欄位做排序, 做篩選過濾。 作為後端人員, 要考慮的是如何優雅的實現分頁、排序、篩選 ...


在項目常常要和數據表格打交道。 現在BS的通常做法都是前端用一個js的Grid控制項, 然後通過ajax的方式從後臺載入數據, 然後將數據和Grid綁定。 數據往往不是一頁可以顯示完的, 所以要加分頁;然後就是根據關鍵欄位做排序, 做篩選過濾。 作為後端人員, 要考慮的是如何優雅的實現分頁、排序、篩選的功能。

本文先談談篩選。 因為分頁、排序、篩選這3個動作, 一定是先處理篩選的——篩選後的結果再去排序, 然後再做分頁 , 才有意義。

篩選首先要考慮如下兩個問題:

1) 欄位類型

2) 比較方式

以下麵的模擬數據為例( 該數據為伺服器的性能監控, 包括處理器、記憶體的監控結果和時間)。

ServerName

ProcessorMaxValue

ProcessorMinValue

ProcessorAvgValue

MemoryMaxValue

MemoryMinValue

MemoryAvgValue

DateTime

Server1

8

3

3.29

82.18

82.11

82.14

2016/10/1

Server1

10

3

3.29

82.23

82.12

82.17

2016/10/2

Server1

11

3

3.32

82.21

82.15

82.18

2016/10/3

Server1

10

3

3.29

82.21

82.10

82.16

2016/10/4

Server1

10

3

3.42

82.20

82.12

82.15

2016/10/5

Server2

10

3

3.40

82.20

82.12

82.16

2016/10/6

Server2

9

3

4.08

82.22

82.11

82.15

2016/10/7

Server2

10

3

3.69

82.20

82.12

82.16

2016/10/8

Server3

11

3

4.13

82.21

82.14

82.16

2016/10/9

Server3

11

3

4.03

82.20

82.15

82.17

2016/10/10

 

對於用戶來講, 可能會使用所有的欄位來做過濾。比如 "ServerName like 'Server'", "ProcessorMaxValue>10 ", "DateTime < '2016/10/9'"。

 

小結下, 比較常見的欄位類型有字元串、數值、日期,以及boolean值。為什麼要強調欄位類型, 因為一樣的值在不同的欄位類型要求下, 比較結果是不同的, 比如說數字11>2 , 但字元串”11”<”2”。

其次考慮比較方式, 比較常見的有“大於、大於等於、等於、小於等於、小於、不等於”, 其次還有 “in (…set)”; 字元串類型可能有”包含”, “開頭匹配”, “結尾匹配”等。

 

如果需求比較固定,直接在代碼中依次處理有限的若幹欄位的篩選完全不是事。可是實際的項目中,這種情況很少。 更多的是, 客戶一會要加這條件, 一會要加那條件。 如果都老老實實的一個一個加, 代碼就很容易臃腫,甚至失控了。

本文中推薦的是使用Expression方案。 由於Expression是 對集合進行操作, 所以不使用於自己拼SQL然後使用SQLCommand的場景。比較適用於:

1) 使用EntityFramework作為ORM框架的

2) 直接對全集合處理的

先感受下代碼

  1 public class CriteriaCollectionHandler : ICollectionHandler
  2    {
  3        /* By Harvey Hu. @2016 */
  4  
  5        protected string PropertyName { get; set; }
  6  
  7        protected ComparerEnum Comparer { get; set; }
  8  
  9        protected object Target { get; set; }  // 
 10  
 11        public CriteriaCollectionHandler(string propertyName, object target, ComparerEnum comparer)
 12        {
 13            this.PropertyName = propertyName;
 14            this.Comparer = comparer;
 15            this.Target = target;
 16        }
 17  
 18        private IQueryable<T> Filter<T>(IQueryable<T> source, string propertyName, ComparerEnum comparer, object target)
 19        {
 20            var type = typeof(T);
 21            var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
 22  
 23  
 24  
 25            var parameter = Expression.Parameter(type, "p");
 26            Expression propertyAccess = Expression.MakeMemberAccess(parameter, property);
 27            if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
 28            {
 29                var getValueOrDefault = property.PropertyType.GetMethods().First(p => p.Name == "GetValueOrDefault");
 30                propertyAccess = Expression.Call(propertyAccess, getValueOrDefault);
 31            }
 32            var constExpression = Expression.Constant(ConvertTo(target, property.PropertyType)); // 轉換為target的類型,以作比較
 33            Expression comparisionExpression;
 34            switch (comparer)
 35            {
 36                case ComparerEnum.Eq:
 37                    comparisionExpression = Expression.Equal(propertyAccess, constExpression);
 38                    break;
 39                case ComparerEnum.Ne:
 40                    comparisionExpression = Expression.NotEqual(propertyAccess, constExpression);
 41                    break;
 42                case ComparerEnum.Lt:
 43                    comparisionExpression = Expression.LessThan(propertyAccess, constExpression);
 44                    break;
 45                case ComparerEnum.Gt:
 46                    comparisionExpression = Expression.GreaterThan(propertyAccess, constExpression);
 47                    break;
 48                case ComparerEnum.Le:
 49                    comparisionExpression = Expression.LessThanOrEqual(propertyAccess, constExpression);
 50                    break;
 51                case ComparerEnum.Ge:
 52                    comparisionExpression = Expression.GreaterThanOrEqual(propertyAccess, constExpression);
 53                    break;
 54                case ComparerEnum.StringLike:
 55                    if (property.PropertyType != typeof(string))
 56                    {
 57                        throw new NotSupportedException("StringLike is only suitable for string type property!");
 58                    }
 59  
 60  
 61                    var stringContainsMethod = typeof(CriteriaCollectionHandler).GetMethod("StringContains");
 62  
 63                    comparisionExpression = Expression.Call(stringContainsMethod, propertyAccess, constExpression);
 64  
 65                    break;
 66                default:
 67                    comparisionExpression = Expression.Equal(propertyAccess, constExpression);
 68                    break;
 69            }
 70  
 71  
 72            var compareExp = Expression.Lambda(comparisionExpression, parameter);
 73            var typeArguments = new Type[] { type };
 74            var methodName = "Where"; //sortOrder == SortDirection.Ascending ? "OrderBy" : "OrderByDescending";
 75            var resultExp = Expression.Call(typeof(Queryable), methodName, typeArguments, source.Expression, Expression.Quote(compareExp));
 76  
 77            return source.Provider.CreateQuery<T>(resultExp);
 78        }
 79  
 80        public static bool StringContains(string value, string subValue)
 81        {
 82            if (value == null)
 83            {
 84                return false;
 85            }
 86  
 87            return value.Contains(subValue);
 88        }
 89  
 90  
 91        protected object ConvertTo(object convertibleValue, Type targetType)
 92        {
 93            if (null == convertibleValue)
 94            {
 95                return null;
 96            }
 97  
 98            if (!targetType.IsGenericType)
 99            {
100                return Convert.ChangeType(convertibleValue, targetType);
101            }
102            else
103            {
104                Type genericTypeDefinition = targetType.GetGenericTypeDefinition();
105                if (genericTypeDefinition == typeof(Nullable<>))
106                {
107                    var temp = Convert.ChangeType(convertibleValue, Nullable.GetUnderlyingType(targetType));
108                    var result = Activator.CreateInstance(targetType, temp);
109                    return result;
110                }
111            }
112            throw new InvalidCastException(string.Format("Invalid cast from type \"{0}\" to type \"{1}\".", convertibleValue.GetType().FullName, targetType.FullName));
113        }
114  
115  
116        public virtual ICollection<T> Execute<T>(ICollection<T> values)
117        {
118            var result = Filter(values.AsQueryable(), this.PropertyName, this.Comparer, this.Target).ToList();
119            return result;
120        }
121  
122    }

 

使用示例(偽碼):

1 var criteria1 = New CriteriaCollectionHandler(“ServerName”, “server”, ComparerEnum.StringLike);  // serverName like 'server'”
2 var criteria2 = New CriteriaCollectionHandler(“ProcessorMaxValue”, 10, ComparerEnum.Gt);
3 var criteria3 = New CriteriaCollectionHandler(“Datetime”, Datetime.Parse("2016/12/9"), ComparerEnum.lt);
4 ICollection<T> result =  criteria3.Execute(
5                                             criteria2.Execute(
6                                                      criteria1.Execute(YourDataCollection)));

 

 

核心是Filter()方法 ——IQueryable<T> Filter<T>(IQueryable<T> source, string propertyName, ComparerEnum comparer, object target)。

ICollectionHandler是用來處理集合Collection的對象介面,前面提到的分頁、排序和篩選處理, 都可以適用於這個介面。這個介面的Execute方法處理一個集合,並返回一個集合。篩選也是這個邏輯,所以適用這個介面。

在Filter()方法中, 通過Expression構建了一個Lamda表達式, 如p=>p.Property == target。這個表達式有幾個問題需要註意下:

1) 如何取到p.Property? 通過類型反射獲取。

2) 如何取到判斷操作? 根據比較符comparer枚舉。如果是常規比較, 則直接調用Expression的相關方法生成, 比如Expression.Equal(); 如果是特殊, 則通過Expression.Call調用自定義的方法生成, 比如StringLike

3) 比較值的類型用什麼?獲取p.Property類型,並將target強制轉換為該類型;參考ConvertTo()方法。

4) 是否支持Nullable類型?支持。但這個是個比較坑的事情。因為Nullable<T>實際上不支持和T的直接比較,所以不能將target轉換為Nullable<T>類型,只能是T類型,因此lamda表達式只能用p=>p.Property.GetValueOrDefault() == target 規格來處理 。所以在ConvertTo()方法中, 對nullable<T>類型也做了判斷處理。

Lamda表達式構造好了, 就可以通過Linq的Where方法來實現篩選了。這同樣適用Expression的Call方法構造出來。最後通過IQuaryable的IQueryProvider
的CreateQuery()方法完成調用。

 5) 是否支持其他比較操作? 通過適當的擴展,我想應該可以實現的。 比如StringLike就是我們自己擴展的比較方法, 當然這個不是EntityFramework提供的,所以不支持EF的Queryable。

 

代碼實現分析到此暫告段落。 在實際使用中, 將每個條件都分別封裝成CriteriaCollectionHandler對象, 然後依次調用即可完成”邏輯與”的操作。參考上面的實現示例。

如果要實現”邏輯或”怎麼辦?目前的考慮結果是將兩個集合intersect()處理。如果各位有什麼更好的辦法, 歡迎回覆討論。

 

下一篇, 我將討論下一些特殊欄位的情況, 比如非Public的Property過濾。

 

註: 使用Expression過程也參考了博客園的其他朋友的文章。在此貢獻出來, 也希望能幫助一些朋友。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在程式中,使用查找功能是少之不了。今天在ASP.NET環境下演示一回。 在cshtml視圖中,有三個文本框,讓用戶輸入關鍵詞,然後點擊最右連的“搜索”銨鈕,如果有結果將顯示於下麵。 Html: 表格放2行。一行是標題,一行作為輸入框。jQuery代碼: controller: 處理好條件,傳入資料庫 ...
  • 在C#多線程之線程池篇中,我們將學習多線程訪問共用資源的一些通用的技術,我們將學習到以下知識點: 線上程池中調用委托 線上程池中執行非同步操作 線程池和並行度 實現取消選項 使用等待句柄和超時 使用計時器 使用後臺工作組件 在前面的“C#多線程之基礎篇”以及“C#多線程之線程同步篇”中,我們學習瞭如何 ...
  • 之前以為BinaryWriter寫string會嚴格按構造時指定的編碼(不指定則是無BOM的UTF8)寫入string的二進位,如下麵的代碼: 因為字母a的utf8編碼是97,所以我預期data只有1個元素且值為97,而實際上,data有兩個元素,依次為1、97,顯然97代表a,但前面的1是什麼鬼, ...
  • 本文轉自:http://blog.kuoruan.com/24.html。感謝原作者。 什麼是Android SDK SDK:(software development kit)軟體開發工具包。被軟體開發工程師用於為特定的軟體包、軟體框架、硬體平臺、操作系統等建立應用軟體的開發工具的集合。而 And... ...
  • c#比較兩個數組的差異 ...
  • 將DataTable中的某列轉換成數組或者List ...
  • 採用bootstrap框架樣式 ...
  • 新建工作薄 生成工作表縮略圖 添加PDF書簽 添加圖片的超鏈接 設置工作表標簽顏色 創建數據透視圖和數據透視表 字體的上標和下標效果 設置工作表背景圖片 設置單元格屬性 換行符和文字環繞 設置公式的註意事項 Excel工作表中插入/刪除行 先行後列填充數據 生成Excel的方法 在單元格中如何添加邊... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...