동적 구조 임 의 복잡 한 Linq Where 표현 식

머리말
       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         /// 
 4         ///     ,     bool,true
 5         /// 
 6         public string _search { get; set; }
 7         /// 
 8         /// 9         /// 
10         public long Nd { get; set; }
11         /// 
12         ///       
13         /// 
14         public int Rows { get; set; }
15         /// 
16         ///   
17         /// 
18         public int Page { get; set; }
19         /// 
20         ///    ,          +  +    ,          。 :id asc,name desc
21         /// 
22         public string Sidx { get; set; }
23         /// 
24         ///        
25         /// 
26         public string[][] SIdx => Sidx.Split(", ").Select(s => s.Split(" ")).ToArray();
27         /// 
28         ///     :asc、desc
29         /// 
30         public string Sord { get; set; }
31         /// 
32         ///       json
33         /// 
34         public string Filters { get; set; }
35 
36         /// 
37         ///           
38         /// 
39         public JqGridSearchRuleGroup FilterObject => Filters.IsNullOrWhiteSpace()
40             ? new JqGridSearchRuleGroup { Rules = new[] { new JqGridSearchRule { Op = SearchOper, Data = SearchString, Field = SearchField } } }
41             : JsonSerializer.Deserialize(Filters ?? string.Empty);
42 
43         /// 
44         ///       
45         /// 
46         public string SearchField { get; set; }
47         /// 
48         ///        
49         /// 
50         public string SearchString { get; set; }
51         /// 
52         ///       
53         /// 
54         public string SearchOper { get; set; }
55 
56     }
57 
58     /// 
59     ///        
60     /// 
61     public class JqGridSearchRuleGroup
62     {
63         /// 
64         ///       :and、or
65         /// 
66         public string GroupOp { get; set; }
67         /// 
68         ///       
69         /// 
70         public JqGridSearchRule[] Rules { get; set; }
71         /// 
72         ///        
73         /// 
74         public JqGridSearchRuleGroup[] Groups { get; set; }
75     }
76 
77     /// 
78     ///       
79     /// 
80     public class JqGridSearchRule
81     {
82         /// 
83         ///     
84         /// 
85         public string Field { get; set; }
86         /// 
87         ///           
88         /// 
89         public string PascalField => Field?.Length > 0 ? Field.Substring(0, 1).ToUpper() + Field.Substring(1) : Field;
90         /// 
91         ///     
92         /// 
93         public string Op { get; set; }
94         /// 
95         ///      
96         /// 
97         public string Data { get; set; }
98     }

       Where , , 。 , :

  1     /// 
  2     /// JqGrid       
  3     /// 
  4     public static class JqGridSearchExtensions
  5     {
  6         //   ( )            json          
  7         //                                  
  8         //                       ,       json    
  9         /// 
 10         ///         where   ,  JqGrid    
 11         /// 
 12         ///        
 13         /// JqGrid     
 14         ///     ,               ,         ,              
 15         /// where   
 16         public static Expressionbool>> BuildWhere(JqGridSearchRuleGroup ruleGroup, IDictionary<string, string> propertyMap)
 17         {
 18             ParameterExpression parameter = Expression.Parameter(typeof(T), "searchObject");
 19 
 20             return Expression.Lambdabool>>(BuildGroupExpression(ruleGroup, parameter, propertyMap), parameter);
 21         }
 22 
 23         /// 
 24         ///            (              )
 25         /// 
 26         ///        
 27         ///    
 28         ///      
 29         ///     
 30         ///   bool        
 31         private static Expression BuildGroupExpression(JqGridSearchRuleGroup group, ParameterExpression parameter, IDictionary<string, string> propertyMap)
 32         {
 33             List expressions = new List();
 34             foreach (var rule in group.Rules ?? new JqGridSearchRule[0])
 35             {
 36                 expressions.Add(BuildRuleExpression(rule, parameter, propertyMap));
 37             }
 38 
 39             foreach (var subGroup in group.Groups ?? new JqGridSearchRuleGroup[0])
 40             {
 41                 expressions.Add(BuildGroupExpression(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         /// 
 79         ///        
 80         /// 
 81         ///        
 82         ///   
 83         ///   
 84         ///     
 85         ///   bool      
 86         private static Expression BuildRuleExpression(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                     }
289                     else
290                     {
291                         throw new InvalidOperationException($"     {pt.FullName}          ");
292                     }
293 
294                     break;
295                 case "cn": //  (   )
296                     if (pt == typeof(string))
297                     {
298                         e = Expression.Call(l, pt.GetMethod(nameof(string.Contains), new[] {typeof(string)}), r);
299                     }
300                     else
301                     {
302                         throw new InvalidOperationException($"     {pt.FullName}        ");
303                     }
304 
305                     break;
306                 case "nc": //   (   )
307                     if (pt == typeof(string))
308                     {
309                         e = Expression.Not(Expression.Call(l, pt.GetMethod(nameof(string.Contains), new[] {typeof(string)}), r));
310                     }
311                     else
312                     {
313                         throw new InvalidOperationException($"     {pt.FullName}        ");
314                     }
315 
316                     break;
317                 case "in": //  (        )
318                     e = BuildContainsExpression(rule, l, pt);
319                     break;
320                 case "ni": //   (         )
321                     e = Expression.Not(BuildContainsExpression(rule, l, pt));
322                     break;
323                 case "nu": //  
324                     r = Expression.Constant(null);
325                     e = Expression.Equal(l, r);
326                     break;
327                 case "nn": //   
328                     r = Expression.Constant(null);
329                     e = Expression.Not(Expression.Equal(l, r));
330                     break;
331                 case "bt": //  
332                     throw new NotImplementedException($"      {rule.Op}        ");
333                 default:
334                     throw new InvalidOperationException($"     {rule.Op}        ");
335             }
336 
337             return e;
338 
339             static Expression BuildConstantExpression(JqGridSearchRule jRule, Func<string, TValue> valueConvertor)
340             {
341                 var rv = valueConvertor(jRule.Data);
342                 return Expression.Constant(rv);
343             }
344         }
345 
346         /// 
347         ///   Contains     
348         /// 
349         ///   
350         ///   
351         ///     
352         /// Contains     
353         private static Expression BuildContainsExpression(JqGridSearchRule rule, Expression parameter, Type parameterType)
354         {
355             Expression e = null;
356 
357             var genMethod = typeof(Queryable).GetMethods()
358                 .Single(m => m.Name == nameof(Queryable.Contains) && m.GetParameters().Length == 2);
359 
360             var jsonArray = JsonSerializer.Deserialize<string[]>(rule.Data);
361 
362             switch (parameterType)
363             {
364                 #region   
365 
366                 case Type ct when ct == typeof(char):
367                     if (jsonArray.Any(o => o.Length != 1)) {throw new InvalidOperationException("                 ");}
368                     e = CallContains(parameter, jsonArray, str => str[0], genMethod, ct);
369                     break;
370                 case Type ct when ct == typeof(string):
371                     e = CallContains(parameter, jsonArray, str => str, genMethod, ct);
372                     break;
373 
374                 #endregion
375 
376                 #region      
377 
378                 case Type ct when ct == typeof(sbyte):
379                     e = CallContains(parameter, jsonArray, sbyte.Parse, genMethod, ct);
380                     break;
381                 case Type ct when ct == typeof(short):
382                     e = CallContains(parameter, jsonArray, short.Parse, genMethod, ct);
383                     break;
384                 case Type ct when ct == typeof(int):
385                     e = CallContains(parameter, jsonArray, int.Parse, genMethod, ct);
386                     break;
387                 case Type ct when ct == typeof(long):
388                     e = CallContains(parameter, jsonArray, long.Parse, genMethod, ct);
389                     break;
390 
391                 #endregion
392 
393                 #region      
394 
395                 case Type ct when ct == typeof(byte):
396                     e = CallContains(parameter, jsonArray, byte.Parse, genMethod, ct);
397                     break;
398                 case Type ct when ct == typeof(ushort):
399                     e = CallContains(parameter, jsonArray, ushort.Parse, genMethod, ct);
400                     break;
401                 case Type ct when ct == typeof(uint):
402                     e = CallContains(parameter, jsonArray, uint.Parse, genMethod, ct);
403                     break;
404                 case Type ct when ct == typeof(ulong):
405                     e = CallContains(parameter, jsonArray, ulong.Parse, genMethod, ct);
406                     break;
407 
408                 #endregion
409 
410                 #region   
411 
412                 case Type ct when ct == typeof(float):
413                     e = CallContains(parameter, jsonArray, float.Parse, genMethod, ct);
414                     break;
415                 case Type ct when ct == typeof(double):
416                     e = CallContains(parameter, jsonArray, double.Parse, genMethod, ct);
417                     break;
418                 case Type ct when ct == typeof(decimal):
419                     e = CallContains(parameter, jsonArray, decimal.Parse, genMethod, ct);
420                     break;
421 
422                 #endregion
423 
424                 #region       
425 
426                 case Type ct when ct == typeof(DateTime):
427                     e = CallContains(parameter, jsonArray, DateTime.Parse, genMethod, ct);
428                     break;
429                 case Type ct when ct == typeof(DateTimeOffset):
430                     e = CallContains(parameter, jsonArray, DateTimeOffset.Parse, genMethod, ct);
431                     break;
432                 case Type ct when ct == typeof(Guid):
433                     e = CallContains(parameter, jsonArray, Guid.Parse, genMethod, ct);
434                     break;
435                 case Type ct when ct.IsEnum:
436                     e = CallContains(Expression.Convert(parameter, typeof(object)), jsonArray, enumString => enumString.ToEnumObject(ct), genMethod, ct);
437                     break;
438 
439                     #endregion
440             }
441 
442             return e;
443 
444             static MethodCallExpression CallContains(Expression pa, string[] jArray, Func<string, T> selector, MethodInfo genericMethod, Type type)
445             {
446                 var data = jArray.Select(selector).ToArray().AsQueryable();
447                 var method = genericMethod.MakeGenericMethod(type);
448 
449                 return Expression.Call(null, method, new[] { Expression.Constant(data), pa });
450             }
451         }
452     }

        Razor Page , , GitHub :

 1         public async Task OnGetUserListAsync([FromQuery]JqGridParameter jqGridParameter)
 2         {
 3             var usersQuery = _userManager.Users.AsNoTracking();
 4             if (jqGridParameter._search == "true")
 5             {
 6                 usersQuery = usersQuery.Where(BuildWhere(jqGridParameter.FilterObject, null));
 7             }
 8 
 9             var users = usersQuery.Include(u => u.UserRoles).ThenInclude(ur => ur.Role).OrderBy(u => u.InsertOrder)
10                 .Skip((jqGridParameter.Page - 1) * jqGridParameter.Rows).Take(jqGridParameter.Rows).ToList();
11             var userCount = usersQuery.Count();
12             var pageCount = Ceiling((double) userCount / jqGridParameter.Rows);
13             return new JsonResult(
14                 new
15                 {
16                     rows //    
17                         = users.Select(u => new
18                         {
19                             u.UserName,
20                             u.Gender,
21                             u.Email,
22                             u.PhoneNumber,
23                             u.EmailConfirmed,
24                             u.PhoneNumberConfirmed,
25                             u.CreationTime,
26                             u.CreatorId,
27                             u.Active,
28                             u.LastModificationTime,
29                             u.LastModifierId,
30                             u.InsertOrder,
31                             u.ConcurrencyStamp,
32                             //   JqGrid      
33                             u.Id //       ,            ,               ,    
34                         }),
35                     total = pageCount, //   
36                     page = jqGridParameter.Page, //    
37                     records = userCount //    
38                 }
39             );
40         }

       프로젝트 를 시작 하면 / Identity / manage / Users / Index 에 접근 하여 사용 할 수 있 습 니 다.
결어
       이번 실천 을 통 해 표현 식 트 리 에 관 한 지식 을 깊이 알 게 되 었 습 니 다. 표현 식 트 리 는 컴 파일 절차 에서 고급 구조 라 고 할 수 있 습 니 다. 간식 을 견 딜 수 있 는 지 알 수 있 습 니 다. IL 이 야 말로 정말 어 지 럽 고 원생 어 셈 블 리 보다 별로 좋 지 않 습 니 다.C \ # 정말 재 미 있 습 니 다. 입문 은 간단 하지만 내 부 는 아주 깊 습 니 다. 소 백 과 대신 에 게 는 완전히 두 가지 언어 입 니 다.자바 는 자바 8 시 에 Stream 과 Lambda 표현 식 기능 을 추 가 했 습 니 다. 딱 봐 도 Linq 를 표시 하 는 것 같 습 니 다. 하지만 그 이름 은 정말 한 마디 로 말 하기 어렵 습 니 다. 코드 를 쓰 는 것 을 보 니 목 에 가시 가 걸 린 것 같 습 니 다. 상당히 불쾌 합 니 다.... 때문에 Stream 시스템 에 표현 식 트 리 가 부족 합 니 다. 이러한 동적 구조 조회 식 기능 은 처음부터 지원 되 지 않 습 니 다.게다가 자바 에는 익명 형식 이 없고 대상 초기 화기 가 없 으 며 매번 사용 합 니 다. Stream 은 힘 든 부분 입 니 다. 중간 과정의 데이터 구조 도 전문 적 으로 종 류 를 써 야 합 니 다. 모든 중간 류 는 하나의 파일 을 독점 해 야 하기 때문에 정말 어 지 럽 습 니 다.베껴 도 불합격 이 야!
       C \ # var 키 워드 를 도입 하 는 핵심 은 익명 형식 을 위 한 서비스 입 니 다. 컴 파일 러 가 자동 으로 생 성 되 는 유형 이기 때문에 코드 를 쓸 때 이름 이 전혀 없습니다. var 를 사용 하지 않 고 무엇 을 사용 합 니까?변 수 를 간소화 하고 코드 를 초기 화 하 는 것 은 순 조로 운 것 입 니 다.그 결과 자바 가 반 을 베 꼈 는 지 가장 중요 하지 않 은 반 을 베 꼈 는 지 변 수 를 간소화 하여 코드 를 초기 화 했다.자바 하 는 놈 들 이 무슨 생각 을 하 는 지 모 르 겠 어.
 
       전 재 는 다음 과 같은 내용 을 완전 하 게 보류 하고 눈 에 띄 는 위치 에 표시 하 십시오. 권한 을 위 임 받 지 않 고 다음 과 같은 내용 을 삭제 하여 전재 도용 을 할 경우 법률 적 책임 을 추궁 할 권 리 를 보류 합 니 다!
본문 주소:https://www.cnblogs.com/coredx/p/12423929.html
전체 소스 코드: Github
안에 여러 가지 작은 것들 이 있 는데, 이것 은 그 중의 하나 일 뿐, 싫어 하지 않 는 다 면 스타 해 볼 수 있다.

좋은 웹페이지 즐겨찾기