Skip to content

Commit b04ee1f

Browse files
MilosMilos
authored andcommitted
Dramatical reduction of repeating code blocks.
1 parent 2f3e5d1 commit b04ee1f

File tree

6 files changed

+206
-228
lines changed

6 files changed

+206
-228
lines changed

src/JsonApiDotNetCore/Extensions/IQueryableExtensions.cs

Lines changed: 149 additions & 198 deletions
Original file line numberDiff line numberDiff line change
@@ -81,70 +81,17 @@ public static IOrderedQueryable<TSource> Sort<TSource>(this IOrderedQueryable<TS
8181
}
8282
}
8383

84-
public static IOrderedQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, AttrQuery attrQuery)
85-
{
86-
return CallGenericOrderMethod(source, attrQuery.Attribute, null, "OrderBy");
87-
}
88-
public static IOrderedQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, RelatedAttrQuery relatedAttrQuery)
89-
{
90-
return CallGenericOrderMethod(source, relatedAttrQuery.Attribute, relatedAttrQuery.RelationshipAttribute, "OrderBy");
91-
}
92-
93-
public static IOrderedQueryable<TSource> OrderByDescending<TSource>(this IQueryable<TSource> source, AttrQuery attrQuery)
94-
{
95-
return CallGenericOrderMethod(source, attrQuery.Attribute, null, "OrderByDescending");
96-
}
97-
public static IOrderedQueryable<TSource> OrderByDescending<TSource>(this IQueryable<TSource> source, RelatedAttrQuery relatedAttrQuery)
98-
{
99-
return CallGenericOrderMethod(source, relatedAttrQuery.Attribute, relatedAttrQuery.RelationshipAttribute, "OrderByDescending");
100-
}
84+
public static IOrderedQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, BaseAttrQuery baseAttrQuery)
85+
=> CallGenericOrderMethod(source, baseAttrQuery, "OrderBy");
10186

102-
public static IOrderedQueryable<TSource> ThenBy<TSource>(this IOrderedQueryable<TSource> source, AttrQuery attrQuery)
103-
{
104-
return CallGenericOrderMethod(source, attrQuery.Attribute, null, "ThenBy");
105-
}
106-
public static IOrderedQueryable<TSource> ThenBy<TSource>(this IOrderedQueryable<TSource> source, RelatedAttrQuery relatedAttrQuery)
107-
{
108-
return CallGenericOrderMethod(source, relatedAttrQuery.Attribute, relatedAttrQuery.RelationshipAttribute, "ThenBy");
109-
}
87+
public static IOrderedQueryable<TSource> OrderByDescending<TSource>(this IQueryable<TSource> source, BaseAttrQuery baseAttrQuery)
88+
=> CallGenericOrderMethod(source, baseAttrQuery, "OrderByDescending");
11089

111-
public static IOrderedQueryable<TSource> ThenByDescending<TSource>(this IOrderedQueryable<TSource> source, AttrQuery attrQuery)
112-
{
113-
return CallGenericOrderMethod(source, attrQuery.Attribute, null, "ThenByDescending");
114-
}
115-
public static IOrderedQueryable<TSource> ThenByDescending<TSource>(this IOrderedQueryable<TSource> source, RelatedAttrQuery relatedAttrQuery)
116-
{
117-
return CallGenericOrderMethod(source, relatedAttrQuery.Attribute, relatedAttrQuery.RelationshipAttribute, "ThenByDescending");
118-
}
90+
public static IOrderedQueryable<TSource> ThenBy<TSource>(this IOrderedQueryable<TSource> source, BaseAttrQuery baseAttrQuery)
91+
=> CallGenericOrderMethod(source, baseAttrQuery, "ThenBy");
11992

120-
private static IOrderedQueryable<TSource> CallGenericOrderMethod<TSource>(IQueryable<TSource> source, AttrAttribute attr, RelationshipAttribute relationAttr, string method)
121-
{
122-
// {x}
123-
var parameter = Expression.Parameter(typeof(TSource), "x");
124-
125-
//var property = Expression.Property(parameter, attr.InternalAttributeName);
126-
127-
MemberExpression member;
128-
// {x.relationship.propertyName}
129-
if (relationAttr != null)
130-
{
131-
var relation = Expression.PropertyOrField(parameter, relationAttr.InternalRelationshipName);
132-
member = Expression.Property(relation, attr.InternalAttributeName);
133-
}
134-
// {x.propertyName}
135-
else
136-
member = Expression.Property(parameter, attr.InternalAttributeName);
137-
138-
// {x=>x.propertyName} or {x=>x.relationship.propertyName}
139-
var lambda = Expression.Lambda(member, parameter);
140-
141-
// REFLECTION: source.OrderBy(x => x.Property)
142-
var orderByMethod = typeof(Queryable).GetMethods().First(x => x.Name == method && x.GetParameters().Length == 2);
143-
var orderByGeneric = orderByMethod.MakeGenericMethod(typeof(TSource), member.Type);
144-
var result = orderByGeneric.Invoke(null, new object[] { source, lambda });
145-
146-
return (IOrderedQueryable<TSource>)result;
147-
}
93+
public static IOrderedQueryable<TSource> ThenByDescending<TSource>(this IOrderedQueryable<TSource> source, BaseAttrQuery baseAttrQuery)
94+
=> CallGenericOrderMethod(source, baseAttrQuery, "ThenByDescending");
14895

14996
public static IQueryable<TSource> Filter<TSource>(this IQueryable<TSource> source, IJsonApiContext jsonApiContext, FilterQuery filterQuery)
15097
{
@@ -157,120 +104,17 @@ public static IQueryable<TSource> Filter<TSource>(this IQueryable<TSource> sourc
157104
return source.Filter(new AttrQuery(jsonApiContext, filterQuery));
158105
}
159106

160-
public static IQueryable<TSource> Filter<TSource>(this IQueryable<TSource> source, AttrQuery filterQuery)
107+
public static IQueryable<TSource> Filter<TSource>(this IQueryable<TSource> source, BaseAttrQuery filterQuery)
161108
{
162109
if (filterQuery == null)
163110
return source;
164111

165-
var concreteType = typeof(TSource);
166-
var property = concreteType.GetProperty(filterQuery.Attribute.InternalAttributeName);
167-
var op = filterQuery.FilterOperation;
168-
169-
if (property == null)
170-
throw new ArgumentException($"'{filterQuery.Attribute.InternalAttributeName}' is not a valid property of '{concreteType}'");
171-
172-
try
173-
{
174-
if (op == FilterOperations.@in || op == FilterOperations.nin)
175-
{
176-
string[] propertyValues = filterQuery.PropertyValue.Split(',');
177-
var lambdaIn = ArrayContainsPredicate<TSource>(propertyValues, property.Name, op);
178-
179-
return source.Where(lambdaIn);
180-
}
181-
else if (op == FilterOperations.isnotnull || op == FilterOperations.isnull) {
182-
// {model}
183-
var parameter = Expression.Parameter(concreteType, "model");
184-
// {model.Id}
185-
var left = Expression.PropertyOrField(parameter, property.Name);
186-
var right = Expression.Constant(null);
187-
188-
var body = GetFilterExpressionLambda(left, right, op);
189-
var lambda = Expression.Lambda<Func<TSource, bool>>(body, parameter);
190-
191-
return source.Where(lambda);
192-
}
193-
else
194-
{ // convert the incoming value to the target value type
195-
// "1" -> 1
196-
var convertedValue = TypeHelper.ConvertType(filterQuery.PropertyValue, property.PropertyType);
197-
// {model}
198-
var parameter = Expression.Parameter(concreteType, "model");
199-
// {model.Id}
200-
var left = Expression.PropertyOrField(parameter, property.Name);
201-
// {1}
202-
var right = Expression.Constant(convertedValue, property.PropertyType);
203-
204-
var body = GetFilterExpressionLambda(left, right, op);
205-
206-
var lambda = Expression.Lambda<Func<TSource, bool>>(body, parameter);
207-
208-
return source.Where(lambda);
209-
}
210-
}
211-
catch (FormatException)
212-
{
213-
throw new JsonApiException(400, $"Could not cast {filterQuery.PropertyValue} to {property.PropertyType.Name}");
214-
}
215-
}
216-
217-
public static IQueryable<TSource> Filter<TSource>(this IQueryable<TSource> source, RelatedAttrQuery filterQuery)
218-
{
219-
if (filterQuery == null)
220-
return source;
221-
222-
var concreteType = typeof(TSource);
223-
var relation = concreteType.GetProperty(filterQuery.RelationshipAttribute.InternalRelationshipName);
224-
if (relation == null)
225-
throw new ArgumentException($"'{filterQuery.RelationshipAttribute.InternalRelationshipName}' is not a valid relationship of '{concreteType}'");
226-
227-
var relatedType = filterQuery.RelationshipAttribute.Type;
228-
var relatedAttr = relatedType.GetProperty(filterQuery.Attribute.InternalAttributeName);
229-
if (relatedAttr == null)
230-
throw new ArgumentException($"'{filterQuery.Attribute.InternalAttributeName}' is not a valid attribute of '{filterQuery.RelationshipAttribute.InternalRelationshipName}'");
231-
232-
try
233-
{
234-
if (filterQuery.FilterOperation == FilterOperations.@in || filterQuery.FilterOperation == FilterOperations.nin)
235-
{
236-
string[] propertyValues = filterQuery.PropertyValue.Split(',');
237-
var lambdaIn = ArrayContainsPredicate<TSource>(propertyValues, relatedAttr.Name, filterQuery.FilterOperation, relation.Name);
238-
239-
return source.Where(lambdaIn);
240-
}
241-
else
242-
{
243-
// convert the incoming value to the target value type
244-
// "1" -> 1
245-
var convertedValue = TypeHelper.ConvertType(filterQuery.PropertyValue, relatedAttr.PropertyType);
246-
// {model}
247-
var parameter = Expression.Parameter(concreteType, "model");
248-
249-
// {model.Relationship}
250-
var leftRelationship = Expression.PropertyOrField(parameter, relation.Name);
251-
252-
// {model.Relationship.Attr}
253-
var left = Expression.PropertyOrField(leftRelationship, relatedAttr.Name);
254-
255-
// {1}
256-
var right = Expression.Constant(convertedValue, relatedAttr.PropertyType);
257-
258-
var body = GetFilterExpressionLambda(left, right, filterQuery.FilterOperation);
259-
260-
var lambda = Expression.Lambda<Func<TSource, bool>>(body, parameter);
261-
262-
return source.Where(lambda);
263-
}
264-
}
265-
catch (FormatException)
266-
{
267-
throw new JsonApiException(400, $"Could not cast {filterQuery.PropertyValue} to {relatedAttr.PropertyType.Name}");
268-
}
112+
if (filterQuery.FilterOperation == FilterOperations.@in || filterQuery.FilterOperation == FilterOperations.nin)
113+
return CallGenericWhereContainsMethod(source,filterQuery);
114+
else
115+
return CallGenericWhereMethod(source, filterQuery);
269116
}
270117

271-
private static bool IsNullable(Type type) => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
272-
273-
274118
private static Expression GetFilterExpressionLambda(Expression left, Expression right, FilterOperations operation)
275119
{
276120
Expression body;
@@ -318,35 +162,6 @@ private static Expression GetFilterExpressionLambda(Expression left, Expression
318162
return body;
319163
}
320164

321-
private static Expression<Func<TSource, bool>> ArrayContainsPredicate<TSource>(string[] propertyValues, string fieldname, FilterOperations op, string relationName = null)
322-
{
323-
ParameterExpression entity = Expression.Parameter(typeof(TSource), "entity");
324-
MemberExpression member;
325-
if (!string.IsNullOrEmpty(relationName))
326-
{
327-
var relation = Expression.PropertyOrField(entity, relationName);
328-
member = Expression.Property(relation, fieldname);
329-
}
330-
else
331-
member = Expression.Property(entity, fieldname);
332-
333-
var method = ContainsMethod.MakeGenericMethod(member.Type);
334-
var obj = TypeHelper.ConvertListType(propertyValues, member.Type);
335-
336-
if (op == FilterOperations.@in)
337-
{
338-
// Where(i => arr.Contains(i.column))
339-
var contains = Expression.Call(method, new Expression[] { Expression.Constant(obj), member });
340-
return Expression.Lambda<Func<TSource, bool>>(contains, entity);
341-
}
342-
else
343-
{
344-
// Where(i => !arr.Contains(i.column))
345-
var notContains = Expression.Not(Expression.Call(method, new Expression[] { Expression.Constant(obj), member }));
346-
return Expression.Lambda<Func<TSource, bool>>(notContains, entity);
347-
}
348-
}
349-
350165
public static IQueryable<TSource> Select<TSource>(this IQueryable<TSource> source, List<string> columns)
351166
{
352167
if (columns == null || columns.Count == 0)
@@ -388,5 +203,141 @@ public static IQueryable<T> PageForward<T>(this IQueryable<T> source, int pageSi
388203

389204
return source;
390205
}
206+
207+
#region Generic method calls
208+
209+
private static IOrderedQueryable<TSource> CallGenericOrderMethod<TSource>(IQueryable<TSource> source, BaseAttrQuery baseAttrQuery, string method)
210+
{
211+
// {x}
212+
var parameter = Expression.Parameter(typeof(TSource), "x");
213+
MemberExpression member;
214+
// {x.relationship.propertyName}
215+
if (baseAttrQuery.IsAttributeOfRelationship)
216+
{
217+
var relation = Expression.PropertyOrField(parameter, baseAttrQuery.RelationshipAttribute.InternalRelationshipName);
218+
member = Expression.Property(relation, baseAttrQuery.Attribute.InternalAttributeName);
219+
}
220+
// {x.propertyName}
221+
else
222+
member = Expression.Property(parameter, baseAttrQuery.Attribute.InternalAttributeName);
223+
224+
// {x=>x.propertyName} or {x=>x.relationship.propertyName}
225+
var lambda = Expression.Lambda(member, parameter);
226+
227+
// REFLECTION: source.OrderBy(x => x.Property)
228+
var orderByMethod = typeof(Queryable).GetMethods().First(x => x.Name == method && x.GetParameters().Length == 2);
229+
var orderByGeneric = orderByMethod.MakeGenericMethod(typeof(TSource), member.Type);
230+
var result = orderByGeneric.Invoke(null, new object[] { source, lambda });
231+
232+
return (IOrderedQueryable<TSource>)result;
233+
}
234+
235+
private static IQueryable<TSource> CallGenericWhereMethod<TSource>(IQueryable<TSource> source, BaseAttrQuery filter)
236+
{
237+
var op = filter.FilterOperation;
238+
var concreteType = typeof(TSource);
239+
PropertyInfo relationProperty = null;
240+
PropertyInfo property = null;
241+
MemberExpression left;
242+
ConstantExpression right;
243+
244+
// {model}
245+
var parameter = Expression.Parameter(concreteType, "model");
246+
// Is relationship attribute
247+
if (filter.IsAttributeOfRelationship)
248+
{
249+
relationProperty = concreteType.GetProperty(filter.RelationshipAttribute.InternalRelationshipName);
250+
if (relationProperty == null)
251+
throw new ArgumentException($"'{filter.RelationshipAttribute.InternalRelationshipName}' is not a valid relationship of '{concreteType}'");
252+
253+
var relatedType = filter.RelationshipAttribute.Type;
254+
property = relatedType.GetProperty(filter.Attribute.InternalAttributeName);
255+
if (property == null)
256+
throw new ArgumentException($"'{filter.Attribute.InternalAttributeName}' is not a valid attribute of '{filter.RelationshipAttribute.InternalRelationshipName}'");
257+
258+
var leftRelationship = Expression.PropertyOrField(parameter, filter.RelationshipAttribute.InternalRelationshipName);
259+
// {model.Relationship}
260+
left = Expression.PropertyOrField(leftRelationship, property.Name);
261+
}
262+
// Is standalone attribute
263+
else
264+
{
265+
property = concreteType.GetProperty(filter.Attribute.InternalAttributeName);
266+
if (property == null)
267+
throw new ArgumentException($"'{filter.Attribute.InternalAttributeName}' is not a valid property of '{concreteType}'");
268+
269+
// {model.Id}
270+
left = Expression.PropertyOrField(parameter, property.Name);
271+
}
272+
273+
try
274+
{
275+
if (op == FilterOperations.isnotnull || op == FilterOperations.isnull)
276+
right = Expression.Constant(null);
277+
else
278+
{
279+
// convert the incoming value to the target value type
280+
// "1" -> 1
281+
var convertedValue = TypeHelper.ConvertType(filter.PropertyValue, property.PropertyType);
282+
// {1}
283+
right = Expression.Constant(convertedValue, property.PropertyType);
284+
}
285+
286+
var body = GetFilterExpressionLambda(left, right, filter.FilterOperation);
287+
var lambda = Expression.Lambda<Func<TSource, bool>>(body, parameter);
288+
289+
return source.Where(lambda);
290+
}
291+
catch (FormatException)
292+
{
293+
throw new JsonApiException(400, $"Could not cast {filter.PropertyValue} to {property.PropertyType.Name}");
294+
}
295+
}
296+
297+
private static IQueryable<TSource> CallGenericWhereContainsMethod<TSource>(IQueryable<TSource> source, BaseAttrQuery filter)
298+
{
299+
var concreteType = typeof(TSource);
300+
var property = concreteType.GetProperty(filter.Attribute.InternalAttributeName);
301+
302+
try
303+
{
304+
var propertyValues = filter.PropertyValue.Split(QueryConstants.COMMA);
305+
ParameterExpression entity = Expression.Parameter(concreteType, "entity");
306+
MemberExpression member;
307+
if (filter.IsAttributeOfRelationship)
308+
{
309+
var relation = Expression.PropertyOrField(entity, filter.RelationshipAttribute.InternalRelationshipName);
310+
member = Expression.Property(relation, filter.Attribute.InternalAttributeName);
311+
}
312+
else
313+
member = Expression.Property(entity, filter.Attribute.InternalAttributeName);
314+
315+
var method = ContainsMethod.MakeGenericMethod(member.Type);
316+
var obj = TypeHelper.ConvertListType(propertyValues, member.Type);
317+
318+
if (filter.FilterOperation == FilterOperations.@in)
319+
{
320+
// Where(i => arr.Contains(i.column))
321+
var contains = Expression.Call(method, new Expression[] { Expression.Constant(obj), member });
322+
var lambda = Expression.Lambda<Func<TSource, bool>>(contains, entity);
323+
324+
return source.Where(lambda);
325+
}
326+
else
327+
{
328+
// Where(i => !arr.Contains(i.column))
329+
var notContains = Expression.Not(Expression.Call(method, new Expression[] { Expression.Constant(obj), member }));
330+
var lambda = Expression.Lambda<Func<TSource, bool>>(notContains, entity);
331+
332+
return source.Where(lambda);
333+
}
334+
}
335+
catch (FormatException)
336+
{
337+
throw new JsonApiException(400, $"Could not cast {filter.PropertyValue} to {property.PropertyType.Name}");
338+
}
339+
}
340+
341+
#endregion
391342
}
392343
}

0 commit comments

Comments
 (0)