Skip to content

Commit e20466a

Browse files
authored
SpecificationBuilderExtensions: Search All Fields: skip enums (#518)
1 parent 871b69a commit e20466a

1 file changed

Lines changed: 147 additions & 145 deletions

File tree

Lines changed: 147 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -1,147 +1,149 @@
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

Comments
 (0)