using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Objects.DataClasses;
using System.Globalization;
using System.Linq.Expressions;
public struct SearchFilterResultsData<T> {
public IEnumerable<T> Results;
public bool HasCount { get { return Count.HasValue; } }
public class SortPager : Pager {
public string SortField { get; set; }
public bool SortDescending { get; set; }
public class SearchFilter : SortPager {
public Dictionary<string, string> Search { get; set; }
protected enum Operations {
protected static readonly string[] Operators = new[] { "=", ">=", "<=", ">", "<", " - " };
public class SortPager<T> : SearchFilter {
public IOrderedQueryable<T> Sort(IQueryable<T> list) {
string sortField = SortField ?? GetPrimaryKeyName();
if (sortField == null) return list.OrderBy(x => true);
return Sort(list, sortField, SortDescending);
public IOrderedQueryable<T> Sort(IQueryable<T> query, string memberName, bool sortDescending) {
PropertyInfo pi = typeof(T).GetProperty(memberName);
if (pi == null) throw new ArgumentException(String.Format("Order By property {0} not found on object", memberName));
var typeParams = new[] { Expression.Parameter(typeof(T), "") };
return (IOrderedQueryable<T>)query.Provider.CreateQuery(
sortDescending ? "OrderByDescending" : "OrderBy",
new[] { typeof(T), pi.PropertyType },
Expression.Lambda(Expression.Property(typeParams[0], pi), typeParams))
public string GetPrimaryKeyName() {
foreach (var property in typeof(T).GetProperties()) {
var attributes = property.GetCustomAttributes(false);
if (attributes.OfType<EdmScalarPropertyAttribute>().FirstOrDefault(a => a.EntityKeyProperty) != null) {
if (attributes.OfType<DatabaseGeneratedAttribute>().FirstOrDefault(a => a.DatabaseGeneratedOption == DatabaseGeneratedOption.Identity) != null) {
public class SearchFilterException : ArgumentOutOfRangeException {
public string[] ErrorFields { get; set; }
public string ShowPopupMessage { get; set; }
public interface ISearchFilter {
Dictionary<string, string> Search { get; set; }
string JsonDictionary { get; set; }
string SortField { get; set; }
bool SortDescending { get; set; }
public class SearchFilter<T> : SortPager<T>, ISearchFilter {
public SearchFilter(ISearchFilter parameters) {
Search = parameters.Search;
SortField = parameters.SortField;
SortDescending = parameters.SortDescending;
JsonDictionary = parameters.JsonDictionary;
public NameValueCollection FilterErrors = new NameValueCollection();
public string ErrorMessagePopup { get; protected set; }
public SearchFilterResultsData<T> GetResults(IQueryable<T> list, bool getCount) {
var results = Filter(list);
return new SearchFilterResultsData<T> {
Count = getCount ? (int?) results.Count() : null,
Results = Sort(results).Skip(Skip).Take(Take).ToArray()
public IQueryable<T> Filter(IQueryable<T> list) {
if (Search != null && Search.Count > 0) {
foreach (string key in Search.Keys) {
if (Search.TryGetValue(key, out value)) {
list = list.Where(CreateFilterExpression(t, key, value));
catch (SearchFilterException ex) {
Array.ForEach(ex.ErrorFields, x => FilterErrors.Add(x, ex.Message));
if (!string.IsNullOrWhiteSpace(ex.ShowPopupMessage)) ErrorMessagePopup = ex.Message;
catch(FormatException ex) {
FilterErrors.Add(key, ex.Message);
ErrorMessagePopup = ex.Message;
protected Expression<Func<T, bool>> CreateFilterExpression(Type t, string propertyName, string value) {
PropertyInfo propertyInfo = t.GetProperty(propertyName);
if (propertyInfo == null) throw new InvalidOperationException(String.Format("Search parameters define a filter on {0}, but a property with that name doesn't exist in type {1}", propertyName, t));
var filterValueSource = value;
var op = Operations.Equals;
if (value.Contains(" - ")) {
for (var bl = 0; bl < Operators.Length - 1; bl++) {
if (value.StartsWith(Operators[bl])) {
filterValueSource = value.Substring(opStr.Length).Trim();
ExpressionProvider filter;
ExpressionValueParser parser = (type, s) => CreateConstantExpression(type, s.Trim());
filter = (expression, type, s) => Expression.Equal(expression, parser(type, s));
case Operations.GreaterThan:
filter = (expression, type, s) => Expression.GreaterThan(expression, parser(type, s));
case Operations.GreaterThanOrEqual:
filter = (expression, type, s) => Expression.GreaterThanOrEqual(expression, parser(type, s));
case Operations.LessThan:
filter = (expression, type, s) => Expression.LessThan(expression, parser(type, s));
case Operations.LessThanOrEqual:
filter = (expression, type, s) => Expression.LessThanOrEqual(expression, parser(type, s));
filter = (expression, type, filterValue) => {
var values = SplitBetweenStatement(filterValue);
return BetweenExpression(expression, parser(type, values.Item1), parser(type, values.Item2));
var fieldType = propertyInfo.GetGetMethod().ReturnType;
if (fieldType == typeof(String)) {
if (op == Operations.Equals && (opStr == string.Empty)) {
filter = CreateStringSearchLambda;
} else if ((fieldType == typeof(DateTime)) || (fieldType == typeof(DateTime?))) {
parser = (type, s) => CreateConstantExpression(fieldType, ParseDate(s));
if (op == Operations.Equals) {
var minDate = ParseDateLocal(filterValueSource);
if (minDate.Hour == 0 && minDate.Minute == 0 && minDate.Second == 0) {
var values = GetDateTimeMinMaxValues(filterValueSource);
filter = (expression, type, filterValue) => BetweenExpression(expression, Expression.Constant(values.Item1, type), Expression.Constant(values.Item2, type));
} else if (op == Operations.Between) {
var values = SplitBetweenStatement(filterValueSource);
var minValues = GetDateTimeMinMaxValues(values.Item1);
var maxValues = GetDateTimeMinMaxValues(values.Item2);
filter = (expression, type, filterValue) => BetweenExpression(expression, Expression.Constant(minValues.Item1, type), Expression.Constant(maxValues.Item2, type));
} else if ((fieldType == typeof(Decimal)) || (fieldType == typeof(Decimal?))) {
if (op == Operations.Equals) {
var values = GetDecimalMinMaxValues(filterValueSource, 2);
filter = (expression, type, filterValue) => BetweenExpression(expression, Expression.Constant(values.Item1, type), Expression.Constant(values.Item2, type));
} else if (op == Operations.Between) {
var values = SplitBetweenStatement(filterValueSource);
var minValues = GetDecimalMinMaxValues(values.Item1, 2);
var maxValues = GetDecimalMinMaxValues(values.Item2, 2);
filter = (expression, type, filterValue) => BetweenExpression(expression, Expression.Constant(minValues.Item1, type), Expression.Constant(maxValues.Item2, type));
} else if ((fieldType == typeof (Boolean)) || (fieldType == typeof (Boolean?))) {
parser = (type, s) => CreateConstantExpression(fieldType, ParseBoolean(s));
filter = (expression, type, s) => Expression.Equal(expression, parser(type, s));
ParameterExpression parameterExpression = Expression.Parameter(t, propertyName + "SearchFilterExpression");
MemberExpression memberExpression = Expression.Property(parameterExpression, propertyInfo);
return Expression.Lambda<Func<T, bool>>(filter(memberExpression, fieldType, filterValueSource), parameterExpression);
protected Boolean ParseBoolean(string value) {
switch (value.ToLower()) {
protected delegate Expression ExpressionProvider(MemberExpression propertyExpression, Type propertyType, string filterValue);
protected delegate ConstantExpression ExpressionValueParser(Type fieldType, string value);
private static Tuple<string, string> SplitBetweenStatement(string filterValue) {
var values = filterValue.Split(new[] { Operators.Last() }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).Take(2).ToArray();
return new Tuple<string, string>(values[0], values[1]);
private static BinaryExpression BetweenExpression(Expression expression, ConstantExpression minExpression, ConstantExpression maxExpression) {
return Expression.And(Expression.GreaterThanOrEqual(expression, minExpression), Expression.LessThanOrEqual(expression, maxExpression));
protected Tuple<DateTime, DateTime> GetDateTimeMinMaxValues(string userInputString) {
var realDate = ParseDate(userInputString);
return new Tuple<DateTime, DateTime>(realDate, realDate.AddDays(1));
protected DateTime ParseDate(string value) {
if (!DateTime.TryParseExact(value, "d", CultureInfo.CurrentCulture, DateTimeStyles.AssumeLocal | DateTimeStyles.AdjustToUniversal, out result)) {
result = DateTime.Parse(value, CultureInfo.CurrentCulture, DateTimeStyles.AssumeLocal | DateTimeStyles.AdjustToUniversal);
protected DateTime ParseDateLocal(string value) {
if (!DateTime.TryParseExact(value, "d", CultureInfo.CurrentCulture, DateTimeStyles.None, out result)) {
result = DateTime.Parse(value, CultureInfo.CurrentCulture, DateTimeStyles.None);
protected MethodCallExpression CreateStringSearchLambda(MemberExpression m, Type fieldType, string value) {
string methodName = "Contains";
if (value.StartsWith("%") && value.EndsWith("%")) {
value = value.Substring(1, value.Length - 2);
} else if (value.StartsWith("%")) {
value = value.Substring(1);
} else if (value.EndsWith("%")) {
methodName = "StartsWith";
value = value.Substring(0, value.Length - 1);
MethodInfo method = typeof (string).GetMethod(methodName, new[] {typeof (string)});
ConstantExpression someValue = Expression.Constant(value, typeof (string));
return Expression.Call(m, method, new Expression[]{ someValue });
protected Tuple<Decimal, Decimal> GetDecimalMinMaxValues(string userInputString, int numDisplayDecimalPlaces) {
var decimalValue = Decimal.Parse(userInputString);
var numMantissaQueryDigits = userInputString.Trim().Length - Math.Floor(Math.Log10((double)Math.Abs(decimalValue)) + 1);
if (userInputString.Contains(".")) numMantissaQueryDigits--;
if (numMantissaQueryDigits < numDisplayDecimalPlaces) {
max = min + (decimal)Math.Pow(10, -numMantissaQueryDigits);
var epsilon = (decimal)Math.Pow(10, -Math.Max(numMantissaQueryDigits, numDisplayDecimalPlaces)) / 2;
min = decimalValue - epsilon;
max = decimalValue + epsilon;
return new Tuple<Decimal, Decimal>(min, max);
protected ConstantExpression CreateConstantExpression(Type fieldType, object fieldValue) {
if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Nullable<>)) {
nfieldType = Nullable.GetUnderlyingType(fieldType);
object value = Convert.ChangeType(fieldValue, nfieldType);
return Expression.Constant(value, fieldType);
public string JsonDictionary { get; set; }