1- using System . Linq . Expressions ;
2- using System . Reflection ;
3-
4- namespace FSH . WebApi . Application . Common . Specification ;
5-
6- // See https://github.com/ardalis/Specification/issues/53
7- public static class SpecificationBuilderExtensions
8- {
9- public static ISpecificationBuilder < T > SearchBy < T > ( this ISpecificationBuilder < T > query , BaseFilter filter ) =>
10- query
11- . SearchByKeyword ( filter . Keyword )
12- . AdvancedSearch ( filter . AdvancedSearch ) ;
13-
14- public static ISpecificationBuilder < T > PaginateBy < T > ( this ISpecificationBuilder < T > query , PaginationFilter filter )
15- {
16- if ( filter . PageNumber <= 0 )
17- {
18- filter . PageNumber = 1 ;
19- }
20-
21- if ( filter . PageSize <= 0 )
22- {
23- filter . PageSize = 10 ;
24- }
25-
26- if ( filter . PageNumber > 1 )
1+ using System . Linq . Expressions ;
2+ using System . Reflection ;
3+
4+ namespace FSH . WebApi . Application . Common . Specification ;
5+
6+ // See https://github.com/ardalis/Specification/issues/53
7+ public static class SpecificationBuilderExtensions
8+ {
9+ public static ISpecificationBuilder < T > SearchBy < T > ( this ISpecificationBuilder < T > query , BaseFilter filter ) =>
10+ query
11+ . SearchByKeyword ( filter . Keyword )
12+ . AdvancedSearch ( filter . AdvancedSearch ) ;
13+
14+ public static ISpecificationBuilder < T > PaginateBy < T > ( this ISpecificationBuilder < T > query , PaginationFilter filter )
15+ {
16+ if ( filter . PageNumber <= 0 )
2717 {
28- query = query . Skip ( ( filter . PageNumber - 1 ) * filter . PageSize ) ;
29- }
30-
31- return query
32- . Take ( filter . PageSize )
33- . OrderBy ( filter . OrderBy ) ;
34- }
35-
36- public static IOrderedSpecificationBuilder < T > SearchByKeyword < T > (
37- this ISpecificationBuilder < T > specificationBuilder ,
38- string ? keyword ) =>
39- specificationBuilder . AdvancedSearch ( new Search { Keyword = keyword } ) ;
40-
41- public static IOrderedSpecificationBuilder < T > AdvancedSearch < T > (
42- this ISpecificationBuilder < T > specificationBuilder ,
43- Search ? search )
44- {
45- if ( ! string . IsNullOrEmpty ( search ? . Keyword ) )
46- {
47- if ( search . Fields ? . Any ( ) is true )
48- {
49- // search seleted fields (can contain deeper nested fields)
50- foreach ( string field in search . Fields )
51- {
52- var paramExpr = Expression . Parameter ( typeof ( T ) ) ;
53-
54- Expression propertyExpr = paramExpr ;
55- foreach ( string member in field . Split ( '.' ) )
56- {
57- propertyExpr = Expression . PropertyOrField ( propertyExpr , member ) ;
58- }
59-
60- specificationBuilder . AddSearchPropertyByKeyword ( propertyExpr , paramExpr , search . Keyword ) ;
61- }
62- }
63- else
64- {
65- // search all fields (only first level)
66- foreach ( var property in typeof ( T ) . GetProperties ( )
67- . Where ( prop => Type . GetTypeCode ( Nullable . GetUnderlyingType ( prop . PropertyType ) ?? prop . PropertyType ) != TypeCode . Object ) )
68- {
69- var paramExpr = Expression . Parameter ( typeof ( T ) ) ;
70- var propertyExpr = Expression . Property ( paramExpr , property ) ;
71-
72- specificationBuilder . AddSearchPropertyByKeyword ( propertyExpr , paramExpr , search . Keyword ) ;
73- }
74- }
75- }
76-
77- return new OrderedSpecificationBuilder < T > ( specificationBuilder . Specification ) ;
78- }
79-
80- private static void AddSearchPropertyByKeyword < T > ( this ISpecificationBuilder < T > specificationBuilder , Expression propertyExpr , ParameterExpression paramExpr , string keyword )
81- {
82- if ( propertyExpr is not MemberExpression memberExpr || memberExpr . Member is not PropertyInfo property )
83- {
84- throw new ArgumentException ( "propertyExpr must be a property expression." , nameof ( propertyExpr ) ) ;
85- }
86-
87- // Generate lambda [ x => x.Property ] for string properties
88- // or [ x => ((object)x.Property) == null ? null : x.Property.ToString() ] for other properties
89- Expression selectorExpr =
90- property . PropertyType == typeof ( string )
91- ? propertyExpr
92- : Expression . Condition (
93- Expression . Equal (
94- Expression . Convert ( propertyExpr , typeof ( object ) ) ,
95- Expression . Constant ( null , typeof ( object ) ) ) ,
96- Expression . Constant ( null , typeof ( string ) ) ,
97- Expression . Call ( propertyExpr , "ToString" , null , null ) ) ;
98-
99- var selector = Expression . Lambda < Func < T , string > > ( selectorExpr , paramExpr ) ;
100-
101- ( ( List < SearchExpressionInfo < T > > ) specificationBuilder . Specification . SearchCriterias )
102- . Add ( new SearchExpressionInfo < T > ( selector , $ "%{ keyword } %", 1 ) ) ;
103- }
104-
105- public static IOrderedSpecificationBuilder < T > OrderBy < T > (
106- this ISpecificationBuilder < T > specificationBuilder ,
107- string [ ] ? orderByFields )
108- {
109- if ( orderByFields is not null )
110- {
111- foreach ( var field in ParseOrderBy ( orderByFields ) )
112- {
113- var paramExpr = Expression . Parameter ( typeof ( T ) ) ;
114-
115- Expression propertyExpr = paramExpr ;
116- foreach ( string member in field . Key . Split ( '.' ) )
117- {
118- propertyExpr = Expression . PropertyOrField ( propertyExpr , member ) ;
119- }
120-
121- var keySelector = Expression . Lambda < Func < T , object ? > > (
122- Expression . Convert ( propertyExpr , typeof ( object ) ) ,
123- paramExpr ) ;
124-
125- ( ( List < OrderExpressionInfo < T > > ) specificationBuilder . Specification . OrderExpressions )
126- . Add ( new OrderExpressionInfo < T > ( keySelector , field . Value ) ) ;
127- }
128- }
129-
130- return new OrderedSpecificationBuilder < T > ( specificationBuilder . Specification ) ;
131- }
132-
133- private static Dictionary < string , OrderTypeEnum > ParseOrderBy ( string [ ] orderByFields ) =>
134- new ( orderByFields . Select ( ( orderByfield , index ) =>
135- {
136- string [ ] fieldParts = orderByfield . Split ( ' ' ) ;
137- string field = fieldParts [ 0 ] ;
138- bool descending = fieldParts . Length > 1 && fieldParts [ 1 ] . StartsWith ( "Desc" , StringComparison . OrdinalIgnoreCase ) ;
139- var orderBy = index == 0
140- ? descending ? OrderTypeEnum . OrderByDescending
141- : OrderTypeEnum . OrderBy
142- : descending ? OrderTypeEnum . ThenByDescending
143- : OrderTypeEnum . ThenBy ;
144-
145- return new KeyValuePair < string , OrderTypeEnum > ( field , orderBy ) ;
146- } ) ) ;
18+ filter . PageNumber = 1 ;
19+ }
20+
21+ if ( filter . PageSize <= 0 )
22+ {
23+ filter . PageSize = 10 ;
24+ }
25+
26+ if ( filter . PageNumber > 1 )
27+ {
28+ query = query . Skip ( ( filter . PageNumber - 1 ) * filter . PageSize ) ;
29+ }
30+
31+ return query
32+ . Take ( filter . PageSize )
33+ . OrderBy ( filter . OrderBy ) ;
34+ }
35+
36+ public static IOrderedSpecificationBuilder < T > SearchByKeyword < T > (
37+ this ISpecificationBuilder < T > specificationBuilder ,
38+ string ? keyword ) =>
39+ specificationBuilder . AdvancedSearch ( new Search { Keyword = keyword } ) ;
40+
41+ public static IOrderedSpecificationBuilder < T > AdvancedSearch < T > (
42+ this ISpecificationBuilder < T > specificationBuilder ,
43+ Search ? search )
44+ {
45+ if ( ! string . IsNullOrEmpty ( search ? . Keyword ) )
46+ {
47+ if ( search . Fields ? . Any ( ) is true )
48+ {
49+ // search seleted fields (can contain deeper nested fields)
50+ foreach ( string field in search . Fields )
51+ {
52+ var paramExpr = Expression . Parameter ( typeof ( T ) ) ;
53+
54+ Expression propertyExpr = paramExpr ;
55+ foreach ( string member in field . Split ( '.' ) )
56+ {
57+ propertyExpr = Expression . PropertyOrField ( propertyExpr , member ) ;
58+ }
59+
60+ specificationBuilder . AddSearchPropertyByKeyword ( propertyExpr , paramExpr , search . Keyword ) ;
61+ }
62+ }
63+ else
64+ {
65+ // search all fields (only first level)
66+ foreach ( var property in typeof ( T ) . GetProperties ( )
67+ . Where ( prop => ( Nullable . GetUnderlyingType ( prop . PropertyType ) ?? prop . PropertyType ) is { } propertyType
68+ && ! propertyType . IsEnum
69+ && Type . GetTypeCode ( propertyType ) != TypeCode . Object ) )
70+ {
71+ var paramExpr = Expression . Parameter ( typeof ( T ) ) ;
72+ var propertyExpr = Expression . Property ( paramExpr , property ) ;
73+
74+ specificationBuilder . AddSearchPropertyByKeyword ( propertyExpr , paramExpr , search . Keyword ) ;
75+ }
76+ }
77+ }
78+
79+ return new OrderedSpecificationBuilder < T > ( specificationBuilder . Specification ) ;
80+ }
81+
82+ private static void AddSearchPropertyByKeyword < T > ( this ISpecificationBuilder < T > specificationBuilder , Expression propertyExpr , ParameterExpression paramExpr , string keyword )
83+ {
84+ if ( propertyExpr is not MemberExpression memberExpr || memberExpr . Member is not PropertyInfo property )
85+ {
86+ throw new ArgumentException ( "propertyExpr must be a property expression." , nameof ( propertyExpr ) ) ;
87+ }
88+
89+ // Generate lambda [ x => x.Property ] for string properties
90+ // or [ x => ((object)x.Property) == null ? null : x.Property.ToString() ] for other properties
91+ Expression selectorExpr =
92+ property . PropertyType == typeof ( string )
93+ ? propertyExpr
94+ : Expression . Condition (
95+ Expression . Equal (
96+ Expression . Convert ( propertyExpr , typeof ( object ) ) ,
97+ Expression . Constant ( null , typeof ( object ) ) ) ,
98+ Expression . Constant ( null , typeof ( string ) ) ,
99+ Expression . Call ( propertyExpr , "ToString" , null , null ) ) ;
100+
101+ var selector = Expression . Lambda < Func < T , string > > ( selectorExpr , paramExpr ) ;
102+
103+ ( ( List < SearchExpressionInfo < T > > ) specificationBuilder . Specification . SearchCriterias )
104+ . Add ( new SearchExpressionInfo < T > ( selector , $ "%{ keyword } %", 1 ) ) ;
105+ }
106+
107+ public static IOrderedSpecificationBuilder < T > OrderBy < T > (
108+ this ISpecificationBuilder < T > specificationBuilder ,
109+ string [ ] ? orderByFields )
110+ {
111+ if ( orderByFields is not null )
112+ {
113+ foreach ( var field in ParseOrderBy ( orderByFields ) )
114+ {
115+ var paramExpr = Expression . Parameter ( typeof ( T ) ) ;
116+
117+ Expression propertyExpr = paramExpr ;
118+ foreach ( string member in field . Key . Split ( '.' ) )
119+ {
120+ propertyExpr = Expression . PropertyOrField ( propertyExpr , member ) ;
121+ }
122+
123+ var keySelector = Expression . Lambda < Func < T , object ? > > (
124+ Expression . Convert ( propertyExpr , typeof ( object ) ) ,
125+ paramExpr ) ;
126+
127+ ( ( List < OrderExpressionInfo < T > > ) specificationBuilder . Specification . OrderExpressions )
128+ . Add ( new OrderExpressionInfo < T > ( keySelector , field . Value ) ) ;
129+ }
130+ }
131+
132+ return new OrderedSpecificationBuilder < T > ( specificationBuilder . Specification ) ;
133+ }
134+
135+ private static Dictionary < string , OrderTypeEnum > ParseOrderBy ( string [ ] orderByFields ) =>
136+ new ( orderByFields . Select ( ( orderByfield , index ) =>
137+ {
138+ string [ ] fieldParts = orderByfield . Split ( ' ' ) ;
139+ string field = fieldParts [ 0 ] ;
140+ bool descending = fieldParts . Length > 1 && fieldParts [ 1 ] . StartsWith ( "Desc" , StringComparison . OrdinalIgnoreCase ) ;
141+ var orderBy = index == 0
142+ ? descending ? OrderTypeEnum . OrderByDescending
143+ : OrderTypeEnum . OrderBy
144+ : descending ? OrderTypeEnum . ThenByDescending
145+ : OrderTypeEnum . ThenBy ;
146+
147+ return new KeyValuePair < string , OrderTypeEnum > ( field , orderBy ) ;
148+ } ) ) ;
147149}
0 commit comments