動態構造任意複雜的 Linq Where 表達式

来源:https://www.cnblogs.com/coredx/archive/2020/03/06/12423929.html
-Advertisement-
Play Games

前言 Linq 是 C# 中一個非常好用的集合處理庫,用好了能幫我們簡化大量又臭又長的嵌套迴圈,使處理邏輯清晰可見。EF 查詢主要也是依賴 Linq。但是 Linq 相對 sql 也存在一些缺點,最主要的就是動態構造查詢的難度。sql 只需要簡單進行字元串拼接,操作難度很低(當然出錯也相當容易),而 ...


前言

       Linq 是 C# 中一個非常好用的集合處理庫,用好了能幫我們簡化大量又臭又長的嵌套迴圈,使處理邏輯清晰可見。EF 查詢主要也是依賴 Linq。但是 Linq 相對 sql 也存在一些缺點,最主要的就是動態構造查詢的難度。sql 只需要簡單進行字元串拼接,操作難度很低(當然出錯也相當容易),而 Linq 表達式由於對強類型表達式樹的依賴,動態構造查詢表達式基本相當於手寫 AST(抽象語法樹),可以說難度暴增。

       AST 已經進入編譯原理的領域,對電腦系統的瞭解程度需求比一般 crud 寫業務代碼高了幾個量級,也導致很多人覺得 EF 不好用,為了寫個動態查詢要學編譯原理這個代價還是挺高的。後來也有一些類似 DynamicLinq 的類庫能用表達式字元串寫動態查詢。

       本著學習精神,研究了一段時間,寫了一個在我的想象力範圍內,可以動態構造任意複雜的 Where 表達式的輔助類。這個輔助類的過濾條件使用了 JqGrid 的高級查詢的數據結構,這是我第一個知道能生成複雜嵌套查詢,並且查詢數據使用 json 方便解析的 js 表格插件。可以無縫根據 JqGrid 的高級查詢生成 Where 表達式。

正文

實現

       JqGrid 高級查詢數據結構定義,用來反序列化:

 1     public class JqGridParameter
 2     {
 3         /// <summary>
 4         /// 是否搜索,本來應該是bool,true
 5         /// </summary>
 6         public string _search { get; set; }
 7         /// <summary>
 8         /// 請求發送次數,方便伺服器處理重覆請求
 9         /// </summary>
10         public long Nd { get; set; }
11         /// <summary>
12         /// 當頁數據條數
13         /// </summary>
14         public int Rows { get; set; }
15         /// <summary>
16         /// 頁碼
17         /// </summary>
18         public int Page { get; set; }
19         /// <summary>
20         /// 排序列,多列排序時為排序列名+空格+排序方式,多個列之間用逗號隔開。例:id asc,name desc
21         /// </summary>
22         public string Sidx { get; set; }
23         /// <summary>
24         /// 分離後的排序列
25         /// </summary>
26         public string[][] SIdx => Sidx.Split(", ").Select(s => s.Split(" ")).ToArray();
27         /// <summary>
28         /// 排序方式:asc、desc
29         /// </summary>
30         public string Sord { get; set; }
31         /// <summary>
32         /// 高級搜索條件json
33         /// </summary>
34         public string Filters { get; set; }
35 
36         /// <summary>
37         /// 序列化的高級搜索對象
38         /// </summary>
39         public JqGridSearchRuleGroup FilterObject => Filters.IsNullOrWhiteSpace()
40             ? new JqGridSearchRuleGroup { Rules = new[] { new JqGridSearchRule { Op = SearchOper, Data = SearchString, Field = SearchField } } }
41             : JsonSerializer.Deserialize<JqGridSearchRuleGroup>(Filters ?? string.Empty);
42 
43         /// <summary>
44         /// 簡單搜索欄位
45         /// </summary>
46         public string SearchField { get; set; }
47         /// <summary>
48         /// 簡單搜索關鍵字
49         /// </summary>
50         public string SearchString { get; set; }
51         /// <summary>
52         /// 簡單搜索操作
53         /// </summary>
54         public string SearchOper { get; set; }
55 
56     }
57 
58     /// <summary>
59     /// 高級搜索條件組
60     /// </summary>
61     public class JqGridSearchRuleGroup
62     {
63         /// <summary>
64         /// 條件組合方式:and、or
65         /// </summary>
66         public string GroupOp { get; set; }
67         /// <summary>
68         /// 搜索條件集合
69         /// </summary>
70         public JqGridSearchRule[] Rules { get; set; }
71         /// <summary>
72         /// 搜索條件組集合
73         /// </summary>
74         public JqGridSearchRuleGroup[] Groups { get; set; }
75     }
76 
77     /// <summary>
78     /// 高級搜索條件
79     /// </summary>
80     public class JqGridSearchRule
81     {
82         /// <summary>
83         /// 搜索欄位
84         /// </summary>
85         public string Field { get; set; }
86         /// <summary>
87         /// 搜索欄位的大駝峰命名
88         /// </summary>
89         public string PascalField => Field?.Length > 0 ? Field.Substring(0, 1).ToUpper() + Field.Substring(1) : Field;
90         /// <summary>
91         /// 搜索操作
92         /// </summary>
93         public string Op { get; set; }
94         /// <summary>
95         /// 搜索關鍵字
96         /// </summary>
97         public string Data { get; set; }
98     }

       Where 條件生成器,代碼有點多,有點複雜。不過註釋也很多,稍微耐心點應該不難看懂:

  1     /// <summary>
  2     /// JqGrid搜索表達式擴展
  3     /// </summary>
  4     public static class JqGridSearchExtensions
  5     {
  6         //前端的(不)屬於條件搜索需要傳遞一個json數組的字元串作為參數
  7         //為了避免在搜索字元串的時候分隔符是搜索內容的一部分導致搜索關鍵字出錯
  8         //無論定義什麼分隔符都不能完全避免這種尷尬的情況,所以使用標準的json以絕後患
  9         /// <summary>
 10         /// 根據搜索條件構造where表達式,支持JqGrid高級搜索
 11         /// </summary>
 12         /// <typeparam name="T">搜索的對象類型</typeparam>
 13         /// <param name="ruleGroup">JqGrid搜索條件組</param>
 14         /// <param name="propertyMap">屬性映射,把搜索規則的名稱映射到屬性名稱,如果屬性是複雜類型,使用點號可以繼續訪問內部屬性</param>
 15         /// <returns>where表達式</returns>
 16         public static Expression<Func<T, bool>> BuildWhere<T>(JqGridSearchRuleGroup ruleGroup, IDictionary<string, string> propertyMap)
 17         {
 18             ParameterExpression parameter = Expression.Parameter(typeof(T), "searchObject");
 19 
 20             return Expression.Lambda<Func<T, bool>>(BuildGroupExpression<T>(ruleGroup, parameter, propertyMap), parameter);
 21         }
 22 
 23         /// <summary>
 24         /// 構造搜索條件組的表達式(一個組中可能包含若幹子條件組)
 25         /// </summary>
 26         /// <typeparam name="T">搜索的對象類型</typeparam>
 27         /// <param name="group">條件組</param>
 28         /// <param name="parameter">參數表達式</param>
 29         /// <param name="propertyMap">屬性映射</param>
 30         /// <returns>返回bool的條件組的表達式</returns>
 31         private static Expression BuildGroupExpression<T>(JqGridSearchRuleGroup group, ParameterExpression parameter, IDictionary<string, string> propertyMap)
 32         {
 33             List<Expression> expressions = new List<Expression>();
 34             foreach (var rule in group.Rules ?? new JqGridSearchRule[0])
 35             {
 36                 expressions.Add(BuildRuleExpression<T>(rule, parameter, propertyMap));
 37             }
 38 
 39             foreach (var subGroup in group.Groups ?? new JqGridSearchRuleGroup[0])
 40             {
 41                 expressions.Add(BuildGroupExpression<T>(subGroup, parameter, propertyMap));
 42             }
 43 
 44             if (expressions.Count == 0)
 45             {
 46                 throw new InvalidOperationException("構造where子句異常,生成了0個比較條件表達式。");
 47             }
 48 
 49             if (expressions.Count == 1)
 50             {
 51                 return expressions[0];
 52             }
 53 
 54             var expression = expressions[0];
 55             switch (group.GroupOp)
 56             {
 57                 case "AND":
 58                     foreach (var exp in expressions.Skip(1))
 59                     {
 60                         expression = Expression.AndAlso(expression, exp);
 61                     }
 62                     break;
 63                 case "OR":
 64                     foreach (var exp in expressions.Skip(1))
 65                     {
 66                         expression = Expression.OrElse(expression, exp);
 67                     }
 68                     break;
 69                 default:
 70                     throw new InvalidOperationException($"不支持創建{group.GroupOp}類型的邏輯運算表達式");
 71             }
 72 
 73             return expression;
 74         }
 75 
 76         private static readonly string[] SpecialRuleOps = {"in", "ni", "nu", "nn"};
 77 
 78         /// <summary>
 79         /// 構造條件表達式
 80         /// </summary>
 81         /// <typeparam name="T">搜索的對象類型</typeparam>
 82         /// <param name="rule">條件</param>
 83         /// <param name="parameter">參數</param>
 84         /// <param name="propertyMap">屬性映射</param>
 85         /// <returns>返回bool的條件表達式</returns>
 86         private static Expression BuildRuleExpression<T>(JqGridSearchRule rule, ParameterExpression parameter,
 87             IDictionary<string, string> propertyMap)
 88         {
 89             Expression l;
 90 
 91             string[] names = null;
 92             //如果實體屬性名稱和前端名稱不一致,或者屬性是一個自定義類型,需要繼續訪問其內部屬性,使用點號分隔
 93             if (propertyMap?.ContainsKey(rule.Field) == true)
 94             {
 95                 names = propertyMap[rule.Field].Split('.', StringSplitOptions.RemoveEmptyEntries);
 96                 l = Expression.Property(parameter, names[0]);
 97                 foreach (var name in names.Skip(1))
 98                 {
 99                     l = Expression.Property(l, name);
100                 }
101             }
102             else
103             {
104                 l = Expression.Property(parameter, rule.PascalField);
105             }
106 
107             Expression r = null; //值表達式
108             Expression e; //返回bool的各種比較表達式
109 
110             //屬於和不屬於比較是多值比較,需要調用Contains方法,而不是調用比較操作符
111             //為空和不為空的右值為常量null,不需要構造
112             var specialRuleOps = SpecialRuleOps;
113 
114             var isNullable = false;
115             var pt = typeof(T);
116             if(names != null)
117             {
118                 foreach(var name in names)
119                 {
120                     pt = pt.GetProperty(name).PropertyType;
121                 }
122             }
123             else
124             {
125                 pt = pt.GetProperty(rule.PascalField).PropertyType;
126             }
127 
128             //如果屬性類型是可空值類型,取出內部類型
129             if (pt.IsDerivedFrom(typeof(Nullable<>)))
130             {
131                 isNullable = true;
132                 pt = pt.GenericTypeArguments[0];
133             }
134 
135             //根據屬性類型創建要比較的常量值表達式(也就是r)
136             if (!specialRuleOps.Contains(rule.Op))
137             {
138                 switch (pt)
139                 {
140                     case Type ct when ct == typeof(bool):
141                         r = BuildConstantExpression(rule, bool.Parse);
142                         break;
143 
144                     #region 文字
145 
146                     case Type ct when ct == typeof(char):
147                         r = BuildConstantExpression(rule, str => str[0]);
148                         break;
149                     case Type ct when ct == typeof(string):
150                         r = BuildConstantExpression(rule, str => str);
151                         break;
152 
153                     #endregion
154 
155                     #region 有符號整數
156 
157                     case Type ct when ct == typeof(sbyte):
158                         r = BuildConstantExpression(rule, sbyte.Parse);
159                         break;
160                     case Type ct when ct == typeof(short):
161                         r = BuildConstantExpression(rule, short.Parse);
162                         break;
163                     case Type ct when ct == typeof(int):
164                         r = BuildConstantExpression(rule, int.Parse);
165                         break;
166                     case Type ct when ct == typeof(long):
167                         r = BuildConstantExpression(rule, long.Parse);
168                         break;
169 
170                     #endregion
171 
172                     #region 無符號整數
173 
174                     case Type ct when ct == typeof(byte):
175                         r = BuildConstantExpression(rule, byte.Parse);
176                         break;
177                     case Type ct when ct == typeof(ushort):
178                         r = BuildConstantExpression(rule, ushort.Parse);
179                         break;
180                     case Type ct when ct == typeof(uint):
181                         r = BuildConstantExpression(rule, uint.Parse);
182                         break;
183                     case Type ct when ct == typeof(ulong):
184                         r = BuildConstantExpression(rule, ulong.Parse);
185                         break;
186 
187                     #endregion
188 
189                     #region 小數
190 
191                     case Type ct when ct == typeof(float):
192                         r = BuildConstantExpression(rule, float.Parse);
193                         break;
194                     case Type ct when ct == typeof(double):
195                         r = BuildConstantExpression(rule, double.Parse);
196                         break;
197                     case Type ct when ct == typeof(decimal):
198                         r = BuildConstantExpression(rule, decimal.Parse);
199                         break;
200 
201                     #endregion
202 
203                     #region 其它常用類型
204 
205                     case Type ct when ct == typeof(DateTime):
206                         r = BuildConstantExpression(rule, DateTime.Parse);
207                         break;
208                     case Type ct when ct == typeof(DateTimeOffset):
209                         r = BuildConstantExpression(rule, DateTimeOffset.Parse);
210                         break;
211                     case Type ct when ct == typeof(Guid):
212                         r = BuildConstantExpression(rule, Guid.Parse);
213                         break;
214                     case Type ct when ct.IsEnum:
215                         r = Expression.Constant(rule.Data.ToEnumObject(ct));
216                         break;
217 
218                     #endregion
219 
220                     default:
221                         throw new InvalidOperationException($"不支持創建{pt.FullName}類型的數據表達式");
222                 }
223             }
224 
225             if (r != null && pt.IsValueType && isNullable)
226             {
227                 var gt = typeof(Nullable<>).MakeGenericType(pt);
228                 r = Expression.Convert(r, gt);
229             }
230 
231             switch (rule.Op)
232             {
233                 case "eq": //等於
234                     e = Expression.Equal(l, r);
235                     break;
236                 case "ne": //不等於
237                     e = Expression.NotEqual(l, r);
238                     break;
239                 case "lt": //小於
240                     e = Expression.LessThan(l, r);
241                     break;
242                 case "le": //小於等於
243                     e = Expression.LessThanOrEqual(l, r);
244                     break;
245                 case "gt": //大於
246                     e = Expression.GreaterThan(l, r);
247                     break;
248                 case "ge": //大於等於
249                     e = Expression.GreaterThanOrEqual(l, r);
250                     break;
251                 case "bw": //開頭是(字元串)
252                     if (pt == typeof(string))
253                     {
254                         e = Expression.Call(l, pt.GetMethod(nameof(string.StartsWith), new[] {typeof(string)}), r);
255                     }
256                     else
257                     {
258                         throw new InvalidOperationException($"不支持創建{pt.FullName}類型的開始於表達式");
259                     }
260 
261                     break;
262                 case "bn": //開頭不是(字元串)
263                     if (pt == typeof(string))
264                     {
265                         e = Expression.Not(Expression.Call(l, pt.GetMethod(nameof(string.StartsWith), new[] {typeof(string)}), r));
266                     }
267                     else
268                     {
269                         throw new InvalidOperationException($"不支持創建{pt.FullName}類型的不開始於表達式");
270                     }
271 
272                     break;
273                 case "ew": //結尾是(字元串)
274                     if (pt == typeof(string))
275                     {
276                         e = Expression.Call(l, pt.GetMethod(nameof(string.EndsWith), new[] {typeof(string)}), r);
277                     }
278                     else
279                     {
280                         throw new InvalidOperationException($"不支持創建{pt.FullName}類型的結束於表達式");
281                     }
282 
283                     break;
284                 case "en": //結尾不是(字元串)
285                     if (pt == typeof(string))
286                     {
287                         e = Expression.Not(Expression.Call(l, pt.GetMethod(nameof(string.EndsWith), new[] {typeof(string)}), r));
288                     }
<

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

-Advertisement-
Play Games
更多相關文章
  • 1,被 synchronized 修飾的方法,鎖的對象是方法的調用者(實例對象) 2,被 static 修飾的方法,鎖的對象就是 Class模板對象,這個則全局唯一 問題7: 一個普通同步方法,一個靜態同步方法,只有一個手機,請問先執行sendEmail 還是 sendSMS public clas ...
  • Java自身UI界面太醜怎麼辦?通過以下代碼就可以將Java應用程式GUI設置成Windows風格: 設置前: 設置後: ...
  • 本文章為本人原創,適合於剛入坑C語言,對於指針的定義和用法模糊不清的同學,如有不正,請各位指出。 從根本來說,指針變數也是變數,只是int變成了int *,以此類推。只不過指針變數裡面放的內容是普通變數在存儲空間的地址(某種奇怪的16進位地址格式,感興趣可自行百度) 定義指針變數的格式:int/do ...
  • 記錄 編碼約定 學習過程。 命名空間約定 如果沒有使用using指令,項目也沒有預設導入合適的命名空間,訪問這些命名空間或者類型時,則需要“完全限定名稱”。 namespace ConsoleApp4 { class Program { static void Main(string[] args) ...
  • 除基於屬性的動畫系統外,WPF提供了一種創建基於幀的動畫的方法,這種方法只使用代碼。需要做的全部工作是響應靜態的CompositionTarge.Rendering事件,觸發該事件是為了給每幀獲取內容。這是一種非常低級的方法,除非使用標準的基於屬性的動畫模型不能滿足需要(例如,構建簡單的側邊滾動游戲 ...
  • 事件的基本使用 聲明一個事件很簡單,只需在聲明一個委托對象時加上event關鍵字就行。如下: public delegate void PriceChangedHandler (decimal oldPrice, decimal newPrice);public class IPhone6 { pu ...
  • 如果要擴展LINQ查詢方法集,只需要為IEnumerable<T>擴展方法。 第一種:擴展聚合方法,類似已有的Max、Min,可以給具體類型擴展,也可以給泛型擴展。 using System; using System.Collections; using System.Collections.Ge ...
  • 場景:動態庫UFileDev(運行時版本v4.0.30319)內置方法調用了動態庫ICSharpCode.SharpZipLib(運行時版本v2.0.50727)。調用動態庫UFileDev過程中一直報錯如下: Pre-bind state information LOG: DisplayName ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...