I am creating a way for users to filter results based on start and end values. An example of this would be:
?RangeColumn=DisplayId&RangeStart=640,830,670&RangeEnd=650,850,680
In this case, the api would return items where the "DisplayId" is between 640-650 or 830-850 or 670-680. My solution works but the query looks pretty bad:
{t => ((((t.DisplayId >= { Item = 640 }.Item) AndAlso (t.DisplayId <= { Item = 650 }.Item)) OrElse ((t.DisplayId >= { Item = 830 }.Item) AndAlso (t.DisplayId <= { Item = 850 }.Item))) OrElse ((t.DisplayId >= { Item = 670 }.Item) AndAlso (t.DisplayId <= { Item = 680 }.Item)))}
The user can have an unlimited number of ranges so this could quickly get crazy really fast so I'm wondering if there is a better way for me to do what I am trying to do. Here are the methods that are relevant to the problem:
private static IQueryable<T> FilterOnRange<T>(Type entityType, RangeProperties rangeProperties, IQueryable<T> query)
{
var startCount = rangeProperties.RangeStart.Count();
var endCount = rangeProperties.RangeEnd.Count();
if (startCount > 0 || endCount > 0)
{
if (startCount != endCount && (startCount > 1 || endCount > 1))
throw new RangeFilterException();
}
else
return query;
PropertyInfo pi = GetFilterPropertyInfo(entityType, rangeProperties.RangeColumnName);
if (pi != null)
{
bool propertyIsNullableType = pi.PropertyType.IsGenericType && pi.PropertyType.GetGenericTypeDefinition().Equals(typeof(Nullable<>));
bool isInteger = pi.PropertyType.Equals(typeof(int)) || (propertyIsNullableType && Nullable.GetUnderlyingType(pi.PropertyType).Equals(typeof(int)));
bool isDateTimeOffset = pi.PropertyType.Equals(typeof(DateTimeOffset)) || (propertyIsNullableType && Nullable.GetUnderlyingType(pi.PropertyType).Equals(typeof(DateTimeOffset)));
var maxCount = Math.Max(startCount, endCount);
ParameterExpression argParam = Expression.Parameter(typeof(T), "t");
if (isInteger)
{
if (maxCount == 1)
{
string start = startCount > 0 ? rangeProperties.RangeStart.Single() : null;
string end = endCount > 0 ? rangeProperties.RangeEnd.Single() : null;
var lambda = RangeInt<T>(argParam, start, end, rangeProperties.RangeColumnName, propertyIsNullableType);
query = query.Where(lambda);
}
else
{
Expression<Func<T, bool>> outerLambda = null;
for (int i = 0; i < maxCount; i++)
{
string start = rangeProperties.RangeStart[i];
string end = rangeProperties.RangeEnd[i];
var lambda1 = RangeInt<T>(argParam, start, end, rangeProperties.RangeColumnName, propertyIsNullableType);
Expression<Func<T, bool>> tempLambda = null;
i++;
if (i >= maxCount)
{
tempLambda = lambda1;
}
else
{
start = rangeProperties.RangeStart[i];
end = rangeProperties.RangeEnd[i];
var lambda2 = RangeInt<T>(argParam, start, end, rangeProperties.RangeColumnName, propertyIsNullableType);
var body = Expression.OrElse(lambda1.Body, lambda2.Body);
tempLambda = Expression.Lambda<Func<T, bool>>(body, lambda1.Parameters[0]);
}
if (outerLambda == null)
outerLambda = tempLambda;
else
{
var body = Expression.OrElse(outerLambda.Body, tempLambda.Body);
outerLambda = Expression.Lambda<Func<T, bool>>(body, outerLambda.Parameters[0]);
}
}
query = query.Where(outerLambda);
}
}
else if (isDateTimeOffset)
{
if (maxCount == 1)
{
string start = startCount > 0 ? rangeProperties.RangeStart.Single() : null;
string end = endCount > 0 ? rangeProperties.RangeEnd.Single() : null;
var lambda = RangeDateTime<T>(argParam, start, end, rangeProperties.RangeColumnName, propertyIsNullableType);
query = query.Where(lambda);
}
else
{
Expression<Func<T, bool>> outerLambda = null;
for (int i = 0; i < maxCount; i++)
{
string start = rangeProperties.RangeStart[i];
string end = rangeProperties.RangeEnd[i];
var lambda1 = RangeDateTime<T>(argParam, start, end, rangeProperties.RangeColumnName, propertyIsNullableType);
Expression<Func<T, bool>> tempLambda = null;
i++;
if (i >= maxCount)
{
tempLambda = lambda1;
}
else
{
start = rangeProperties.RangeStart[i];
end = rangeProperties.RangeEnd[i];
var lambda2 = RangeDateTime<T>(argParam, start, end, rangeProperties.RangeColumnName, propertyIsNullableType);
var body = Expression.OrElse(lambda1.Body, lambda2.Body);
tempLambda = Expression.Lambda<Func<T, bool>>(body, lambda1.Parameters[0]);
}
if (outerLambda == null)
outerLambda = tempLambda;
else
{
var body = Expression.OrElse(outerLambda.Body, tempLambda.Body);
outerLambda = Expression.Lambda<Func<T, bool>>(body, outerLambda.Parameters[0]);
}
}
query = query.Where(outerLambda);
}
}
else
throw new InvalidOperationException("Only Datetime and Integer is supported for a range filter.");
}
return query;
}
private static Expression<Func<T, bool>> RangeInt<T>(ParameterExpression argParam, string start, string end, string columnName, bool propertyIsNullableType)
{
int? rangeStart = null;
int tempRangeStart = int.MinValue;
if (int.TryParse(start, out tempRangeStart))
rangeStart = tempRangeStart;
else
rangeStart = int.MinValue;
int? rangeEnd = null;
int tempRangeEnd = int.MaxValue;
if (int.TryParse(end, out tempRangeEnd))
rangeEnd = tempRangeEnd;
else
rangeEnd = int.MaxValue;
if (rangeEnd < rangeStart)
throw new InvalidOperationException("RangeEnd must be greater than RangeStart.");
Expression rangeProperty = Expression.Property(argParam, columnName);
MemberExpression comparisonValue2 = null;
if (propertyIsNullableType)
{
var item = new { Item = (int?)rangeStart };
var comparisonValue = Expression.Constant(item);
comparisonValue2 = Expression.MakeMemberAccess(comparisonValue, item.GetType().GetMember("Item").First());
}
else
{
var item = new { Item = (int)rangeStart };
var comparisonValue = Expression.Constant(item);
comparisonValue2 = Expression.MakeMemberAccess(comparisonValue, item.GetType().GetMember("Item").First());
}
Expression e1 = Expression.GreaterThanOrEqual(rangeProperty, comparisonValue2);
MemberExpression comparisonValue4 = null;
if (propertyIsNullableType)
{
var item2 = new { Item = (int?)rangeEnd };
var comparisonValue3 = Expression.Constant(item2);
comparisonValue4 = Expression.MakeMemberAccess(comparisonValue3, item2.GetType().GetMember("Item").First());
}
else
{
var item2 = new { Item = (int)rangeEnd };
var comparisonValue3 = Expression.Constant(item2);
comparisonValue4 = Expression.MakeMemberAccess(comparisonValue3, item2.GetType().GetMember("Item").First());
}
Expression e2 = Expression.LessThanOrEqual(rangeProperty, comparisonValue4);
Expression e3 = Expression.AndAlso(e1, e2);
return Expression.Lambda<Func<T, bool>>(e3, argParam);
}
private static Expression<Func<T, bool>> RangeDateTime<T>(ParameterExpression argParam, string start, string end, string columnName, bool propertyIsNullableType)
{
DateTimeOffset? rangeStart = null;
DateTimeOffset tempRangeStart = DateTimeOffset.MinValue;
if (DateTimeOffset.TryParse(start, out tempRangeStart))
rangeStart = tempRangeStart;
else
rangeStart = DateTimeOffset.MinValue;
DateTimeOffset? rangeEnd = null;
DateTimeOffset tempRangeEnd = DateTimeOffset.MaxValue;
if (DateTimeOffset.TryParse(end, out tempRangeEnd))
rangeEnd = tempRangeEnd;
else
rangeEnd = DateTimeOffset.MaxValue;
if (rangeEnd < rangeStart)
throw new InvalidOperationException("RangeEnd must be greater than RangeStart.");
Expression rangeProperty = Expression.Property(argParam, columnName);
MemberExpression comparisonValue2 = null;
if (propertyIsNullableType)
{
var item = new { Item = (DateTimeOffset?)rangeStart };
var comparisonValue = Expression.Constant(item);
comparisonValue2 = Expression.MakeMemberAccess(comparisonValue, item.GetType().GetMember("Item").First());
}
else
{
var item = new { Item = (DateTimeOffset)rangeStart };
var comparisonValue = Expression.Constant(item);
comparisonValue2 = Expression.MakeMemberAccess(comparisonValue, item.GetType().GetMember("Item").First());
}
Expression e1 = Expression.GreaterThanOrEqual(rangeProperty, comparisonValue2);
MemberExpression comparisonValue4 = null;
if (propertyIsNullableType)
{
var item2 = new { Item = (DateTimeOffset?)rangeEnd };
var comparisonValue3 = Expression.Constant(item2);
comparisonValue4 = Expression.MakeMemberAccess(comparisonValue3, item2.GetType().GetMember("Item").First());
}
else
{
var item2 = new { Item = (DateTimeOffset)rangeEnd };
var comparisonValue3 = Expression.Constant(item2);
comparisonValue4 = Expression.MakeMemberAccess(comparisonValue3, item2.GetType().GetMember("Item").First());
}
Expression e2 = Expression.LessThanOrEqual(rangeProperty, comparisonValue4);
Expression e3 = Expression.AndAlso(e1, e2);
return Expression.Lambda<Func<T, bool>>(e3, argParam);
}