77using AutoMapper . Internal ;
88using Microsoft . EntityFrameworkCore ;
99using Microsoft . EntityFrameworkCore . Infrastructure ;
10- using Newtonsoft . Json ;
1110using NorthwindCRUD ;
1211using Swashbuckle . AspNetCore . SwaggerGen ;
1312
14- public enum FilterType
15- {
16- And = 0 ,
17- Or = 1 ,
18- }
19-
20- public interface IQuery
21- {
22- public string Entity { get ; set ; }
23-
24- public string [ ] ReturnFields { get ; set ; }
25-
26- [ SuppressMessage ( "Naming" , "CA1716:Identifiers should not match keywords" , Justification = "required name" ) ]
27- public FilterType Operator { get ; set ; }
28-
29- public IQueryFilter [ ] FilteringOperands { get ; set ; }
30- }
31-
32- public interface IQueryFilter
33- {
34- // Basic condition
35- public string ? FieldName { get ; set ; }
36-
37- public bool ? IgnoreCase { get ; set ; }
38-
39- public IQueryFilterCondition ? Condition { get ; set ; }
40-
41- public object ? SearchVal { get ; set ; }
42-
43- public IQuery ? SearchTree { get ; set ; }
44-
45- // And/Or
46- [ SuppressMessage ( "Naming" , "CA1716:Identifiers should not match keywords" , Justification = "required name" ) ]
47- public FilterType ? Operator { get ; set ; }
48-
49- public IQueryFilter [ ] FilteringOperands { get ; set ; }
50- }
51-
52- public interface IQueryFilterCondition
53- {
54- public string Name { get ; set ; }
55-
56- public bool IsUnary { get ; set ; }
57-
58- public string IconName { get ; set ; }
59- }
60-
61- public class Query : IQuery
62- {
63- public string Entity { get ; set ; }
64-
65- public string [ ] ReturnFields { get ; set ; }
66-
67- public FilterType Operator { get ; set ; }
68-
69- public IQueryFilter [ ] FilteringOperands { get ; set ; }
70- }
71-
72- public class QueryFilter : IQueryFilter
73- {
74- // Basic condition
75- public string ? FieldName { get ; set ; }
76-
77- public bool ? IgnoreCase { get ; set ; }
78-
79- public IQueryFilterCondition ? Condition { get ; set ; }
80-
81- public object ? SearchVal { get ; set ; }
82-
83- public IQuery ? SearchTree { get ; set ; }
84-
85- // And/Or
86- public FilterType ? Operator { get ; set ; }
87-
88- public IQueryFilter [ ] FilteringOperands { get ; set ; }
89- }
90-
91- public class QueryFilterCondition : IQueryFilterCondition
92- {
93- public string Name { get ; set ; }
94-
95- public bool IsUnary { get ; set ; }
96-
97- public string IconName { get ; set ; }
98- }
99-
100- public abstract class InterfaceToConcreteClassConverter < TI , TC > : JsonConverter
101- {
102- public override bool CanConvert ( Type objectType ) => objectType == typeof ( TI ) ;
103-
104- public override object ? ReadJson ( JsonReader reader , Type objectType , object ? existingValue , JsonSerializer serializer ) => serializer . Deserialize < TC > ( reader ) ;
105-
106- public override void WriteJson ( JsonWriter writer , object ? value , JsonSerializer serializer ) => serializer . Serialize ( writer , value ) ;
107-
108- }
109-
110- public class QueryConverter : InterfaceToConcreteClassConverter < IQuery , Query >
111- {
112- }
113-
114- public class QueryFilterConverter : InterfaceToConcreteClassConverter < IQueryFilter , QueryFilter >
115- {
116- }
117-
118- public class QueryFilterConditionConverter : InterfaceToConcreteClassConverter < IQueryFilterCondition , QueryFilterCondition >
119- {
120- }
121-
12213/// <summary>
12314/// A generic query executor that can be used to execute queries on IQueryable data sources.
12415/// </summary>
12516[ SuppressMessage ( "StyleCop.CSharp.SpacingRules" , "SA1009:Closing parenthesis should be spaced correctly" , Justification = "..." ) ]
17+ [ SuppressMessage ( "StyleCop.CSharp.SpacingRules" , "SA1025:Code should not contain multiple whitespace in a row" , Justification = "..." ) ]
12618public static class QueryExecutor
12719{
128- public static TEntity [ ] Run < TEntity > ( this IQueryable < TEntity > source , IQuery ? query )
20+ public static TEntity [ ] Run < TEntity > ( this IQueryable < TEntity > source , Query ? query )
12921 {
13022 var infrastructure = source as IInfrastructure < IServiceProvider > ;
13123 var serviceProvider = infrastructure ! . Instance ;
@@ -134,7 +26,7 @@ public static TEntity[] Run<TEntity>(this IQueryable<TEntity> source, IQuery? qu
13426 return db is not null ? BuildQuery ( db , source , query ) . ToArray ( ) : Array . Empty < TEntity > ( ) ;
13527 }
13628
137- private static IQueryable < TEntity > BuildQuery < TEntity > ( DataContext db , IQueryable < TEntity > source , IQuery ? query )
29+ private static IQueryable < TEntity > BuildQuery < TEntity > ( DataContext db , IQueryable < TEntity > source , Query ? query )
13830 {
13931 if ( query is null )
14032 {
@@ -154,7 +46,7 @@ private static IQueryable<TEntity> BuildQuery<TEntity>(DataContext db, IQueryabl
15446 }
15547 }
15648
157- private static Expression < Func < TEntity , bool > > BuildExpression < TEntity > ( DataContext db , IQueryable < TEntity > source , IQueryFilter [ ] filters , FilterType filterType )
49+ private static Expression < Func < TEntity , bool > > BuildExpression < TEntity > ( DataContext db , IQueryable < TEntity > source , QueryFilter [ ] filters , FilterType filterType )
15850 {
15951 var parameter = Expression . Parameter ( typeof ( TEntity ) , "entity" ) ;
16052 var finalExpression = null as Expression ;
@@ -178,7 +70,7 @@ private static Expression<Func<TEntity, bool>> BuildExpression<TEntity>(DataCont
17870 : ( TEntity _ ) => true ;
17971 }
18072
181- private static Expression BuildConditionExpression < TEntity > ( DataContext db , IQueryable < TEntity > source , IQueryFilter filter , ParameterExpression parameter )
73+ private static Expression BuildConditionExpression < TEntity > ( DataContext db , IQueryable < TEntity > source , QueryFilter filter , ParameterExpression parameter )
18274 {
18375 if ( filter . FieldName is not null && filter . IgnoreCase is not null && filter . Condition is not null )
18476 {
@@ -190,26 +82,26 @@ private static Expression BuildConditionExpression<TEntity>(DataContext db, IQue
19082 var emptyValue = GetEmptyValue ( targetType ) ;
19183 Expression condition = filter . Condition . Name switch
19284 {
193- "null" => targetType . IsNullableType ( ) ? Expression . Equal ( field , Expression . Constant ( targetType . GetDefaultValue ( ) ) ) : Expression . Constant ( false ) ,
194- "notNull" => targetType . IsNullableType ( ) ? Expression . NotEqual ( field , Expression . Constant ( targetType . GetDefaultValue ( ) ) ) : Expression . Constant ( true ) ,
195- "empty" => Expression . Or ( Expression . Equal ( field , emptyValue ) , targetType . IsNullableType ( ) ? Expression . Equal ( field , Expression . Constant ( targetType . GetDefaultValue ( ) ) ) : Expression . Constant ( false ) ) ,
196- "notEmpty" => Expression . And ( Expression . NotEqual ( field , emptyValue ) , targetType . IsNullableType ( ) ? Expression . NotEqual ( field , Expression . Constant ( targetType . GetDefaultValue ( ) ) ) : Expression . Constant ( true ) ) ,
197- "equals" => Expression . Equal ( field , searchValue ) ,
198- "doesNotEqual" => Expression . NotEqual ( field , searchValue ) ,
199- "in" => BuildInExpression ( db , filter . SearchTree , field ) ,
200- "notIn" => Expression . Not ( BuildInExpression ( db , filter . SearchTree , field ) ) ,
201- "contains" => Expression . Call ( field , typeof ( string ) . GetMethod ( "Contains" , new [ ] { typeof ( string ) } ) ! , searchValue ) ,
202- "doesNotContain" => Expression . Not ( Expression . Call ( field , typeof ( string ) . GetMethod ( "Contains" , new [ ] { typeof ( string ) } ) ! , searchValue ) ) ,
203- "startsWith" => Expression . Call ( field , typeof ( string ) . GetMethod ( "StartsWith" , new [ ] { typeof ( string ) } ) ! , searchValue ) ,
204- "endsWith" => Expression . Call ( field , typeof ( string ) . GetMethod ( "EndsWith" , new [ ] { typeof ( string ) } ) ! , searchValue ) ,
205- "greaterThan" => Expression . GreaterThan ( field , searchValue ) ,
206- "lessThan" => Expression . LessThan ( field , searchValue ) ,
207- "greaterThanOrEqualTo" => Expression . GreaterThanOrEqual ( field , searchValue ) ,
208- "lessThanOrEqualTo" => Expression . LessThanOrEqual ( field , searchValue ) ,
209- "all" => throw new NotImplementedException ( "Not implemented" ) ,
210- "true" => Expression . IsTrue ( field ) ,
211- "false" => Expression . IsFalse ( field ) ,
212- _ => throw new NotImplementedException ( "Not implemented" ) ,
85+ "null" => targetType . IsNullableType ( ) ? Expression . Equal ( field , Expression . Constant ( targetType . GetDefaultValue ( ) ) ) : Expression . Constant ( false ) ,
86+ "notNull" => targetType . IsNullableType ( ) ? Expression . NotEqual ( field , Expression . Constant ( targetType . GetDefaultValue ( ) ) ) : Expression . Constant ( true ) ,
87+ "empty" => Expression . Or ( Expression . Equal ( field , emptyValue ) , targetType . IsNullableType ( ) ? Expression . Equal ( field , Expression . Constant ( targetType . GetDefaultValue ( ) ) ) : Expression . Constant ( false ) ) ,
88+ "notEmpty" => Expression . And ( Expression . NotEqual ( field , emptyValue ) , targetType . IsNullableType ( ) ? Expression . NotEqual ( field , Expression . Constant ( targetType . GetDefaultValue ( ) ) ) : Expression . Constant ( true ) ) ,
89+ "equals" => Expression . Equal ( field , searchValue ) ,
90+ "doesNotEqual" => Expression . NotEqual ( field , searchValue ) ,
91+ "in" => BuildInExpression ( db , filter . SearchTree , field ) ,
92+ "notIn" => Expression . Not ( BuildInExpression ( db , filter . SearchTree , field ) ) ,
93+ "contains" => Expression . Call ( field , typeof ( string ) . GetMethod ( "Contains" , new [ ] { typeof ( string ) } ) ! , searchValue ) ,
94+ "doesNotContain" => Expression . Not ( Expression . Call ( field , typeof ( string ) . GetMethod ( "Contains" , new [ ] { typeof ( string ) } ) ! , searchValue ) ) ,
95+ "startsWith" => Expression . Call ( field , typeof ( string ) . GetMethod ( "StartsWith" , new [ ] { typeof ( string ) } ) ! , searchValue ) ,
96+ "endsWith" => Expression . Call ( field , typeof ( string ) . GetMethod ( "EndsWith" , new [ ] { typeof ( string ) } ) ! , searchValue ) ,
97+ "greaterThan" => Expression . GreaterThan ( field , searchValue ) ,
98+ "lessThan" => Expression . LessThan ( field , searchValue ) ,
99+ "greaterThanOrEqualTo" => Expression . GreaterThanOrEqual ( field , searchValue ) ,
100+ "lessThanOrEqualTo" => Expression . LessThanOrEqual ( field , searchValue ) ,
101+ "all" => throw new NotImplementedException ( "Not implemented" ) ,
102+ "true" => Expression . IsTrue ( field ) ,
103+ "false" => Expression . IsFalse ( field ) ,
104+ _ => throw new NotImplementedException ( "Not implemented" ) ,
213105 } ;
214106 if ( filter . IgnoreCase . Value && field . Type == typeof ( string ) )
215107 {
@@ -232,7 +124,7 @@ private static Expression BuildConditionExpression<TEntity>(DataContext db, IQue
232124 }
233125 }
234126
235- private static Expression BuildInExpression ( DataContext db , IQuery ? query , MemberExpression field )
127+ private static Expression BuildInExpression ( DataContext db , Query ? query , MemberExpression field )
236128 {
237129 if ( field . Type == typeof ( string ) )
238130 {
@@ -270,40 +162,28 @@ private static Expression BuildInExpression(DataContext db, IQuery? query, Membe
270162 }
271163 }
272164
273- private static IEnumerable < dynamic > RunSubquery ( DataContext db , IQuery ? query )
165+ private static IEnumerable < dynamic > RunSubquery ( DataContext db , Query ? query )
274166 {
275167 // var t = query?.Entity.ToLower(CultureInfo.InvariantCulture);
276168 // var p = db.GetType().GetProperty(t, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance) ?? throw new InvalidOperationException($"Property '{t}' not found on type '{db.GetType()}'");
277169 // var q = p.GetValue(db) as IQueryable<dynamic>;
278170 // return q is null ? Array.Empty<dynamic>() : q.Run(query).ToArray();
279171 var t = query ? . Entity . ToLower ( CultureInfo . InvariantCulture ) ?? string . Empty ;
280- switch ( t )
281- {
282- case "addresses" :
283- return db . Suppliers . Run ( query ) . ToArray ( ) ;
284- case "categories" :
285- return db . Categories . Run ( query ) . ToArray ( ) ;
286- case "products" :
287- return db . Products . Run ( query ) . ToArray ( ) ;
288- case "regions" :
289- return db . Regions . Run ( query ) . ToArray ( ) ;
290- case "territories" :
291- return db . Territories . Run ( query ) . ToArray ( ) ;
292- case "employees" :
293- return db . Employees . Run ( query ) . ToArray ( ) ;
294- case "customers" :
295- return db . Customers . Run ( query ) . ToArray ( ) ;
296- case "orders" :
297- return db . Orders . Run ( query ) . ToArray ( ) ;
298- case "orderdetails" :
299- return db . OrderDetails . Run ( query ) . ToArray ( ) ;
300- case "shippers" :
301- return db . Shippers . Run ( query ) . ToArray ( ) ;
302- case "suppliers" :
303- return db . Suppliers . Run ( query ) . ToArray ( ) ;
304- default :
305- return Array . Empty < dynamic > ( ) ;
306- }
172+ return t switch
173+ {
174+ "addresses" => db . Suppliers . Run ( query ) . ToArray ( ) ,
175+ "categories" => db . Categories . Run ( query ) . ToArray ( ) ,
176+ "products" => db . Products . Run ( query ) . ToArray ( ) ,
177+ "regions" => db . Regions . Run ( query ) . ToArray ( ) ,
178+ "territories" => db . Territories . Run ( query ) . ToArray ( ) ,
179+ "employees" => db . Employees . Run ( query ) . ToArray ( ) ,
180+ "customers" => db . Customers . Run ( query ) . ToArray ( ) ,
181+ "orders" => db . Orders . Run ( query ) . ToArray ( ) ,
182+ "orderdetails" => db . OrderDetails . Run ( query ) . ToArray ( ) ,
183+ "shippers" => db . Shippers . Run ( query ) . ToArray ( ) ,
184+ "suppliers" => db . Suppliers . Run ( query ) . ToArray ( ) ,
185+ _ => Array . Empty < dynamic > ( ) ,
186+ } ;
307187 }
308188
309189 private static dynamic ? ProjectField ( dynamic ? obj , string field )
@@ -343,65 +223,3 @@ private static Expression<Func<TEntity, dynamic>> BuildProjectionExpression<TEnt
343223 return Expression . Lambda < Func < TEntity , dynamic > > ( body , parameter ) ;
344224 }
345225}
346-
347- [ SuppressMessage ( "StyleCop.CSharp.SpacingRules" , "SA1025:Code should not contain multiple whitespace in a row" , Justification = "..." ) ]
348- public static class SqlGenerator
349- {
350- public static string GenerateSql ( IQuery query )
351- {
352- var selectClause = BuildSelectClause ( query ) ;
353- var whereClause = BuildWhereClause ( query . FilteringOperands , query . Operator ) ;
354- return $ "{ selectClause } { whereClause } ;";
355- }
356-
357- private static string BuildSelectClause ( IQuery query )
358- {
359- var fields = query . ReturnFields != null && query . ReturnFields . Any ( )
360- ? string . Join ( ", " , query . ReturnFields )
361- : "*" ;
362- return $ "SELECT { fields } FROM { query . Entity } ";
363- }
364-
365- private static string BuildWhereClause ( IQueryFilter [ ] filters , FilterType filterType )
366- {
367- if ( filters == null || ! filters . Any ( ) )
368- {
369- return string . Empty ;
370- }
371-
372- var conditions = filters . Select ( BuildCondition ) . ToArray ( ) ;
373- var conjunction = filterType == FilterType . And ? " AND " : " OR " ;
374- return $ "WHERE { string . Join ( conjunction , conditions ) } ";
375- }
376-
377- private static string BuildCondition ( IQueryFilter filter )
378- {
379- var field = filter . FieldName ;
380- var condition = filter . Condition ? . Name ;
381- var value = filter . SearchVal != null ? $ "'{ filter . SearchVal } '" : "NULL" ;
382- var subquery = filter . SearchTree != null ? $ "({ GenerateSql ( filter . SearchTree ) } )" : string . Empty ;
383- return condition switch
384- {
385- "null" => $ "{ field } IS NULL",
386- "notNull" => $ "{ field } IS NOT NULL",
387- "empty" => $ "{ field } = ''",
388- "notEmpty" => $ "{ field } <> ''",
389- "equals" => $ "{ field } = { value } ",
390- "doesNotEqual" => $ "{ field } <> { value } ",
391- "in" => $ "{ field } IN ({ subquery } )",
392- "notIn" => $ "{ field } NOT IN ({ subquery } )",
393- "contains" => $ "{ field } LIKE '%{ filter . SearchVal } %'",
394- "doesNotContain" => $ "{ field } NOT LIKE '%{ filter . SearchVal } %'",
395- "startsWith" => $ "{ field } LIKE '{ filter . SearchVal } %'",
396- "endsWith" => $ "{ field } LIKE '%{ filter . SearchVal } '",
397- "greaterThan" => $ "{ field } > { value } ",
398- "lessThan" => $ "{ field } < { value } ",
399- "greaterThanOrEqualTo" => $ "{ field } >= { value } ",
400- "lessThanOrEqualTo" => $ "{ field } <= { value } ",
401- "all" => throw new NotImplementedException ( "Not implemented" ) ,
402- "true" => $ "{ field } = TRUE",
403- "false" => $ "{ field } = FALSE",
404- _ => throw new NotImplementedException ( $ "Condition '{ condition } ' is not implemented") ,
405- } ;
406- }
407- }
0 commit comments