Here is a class I wrote a while ago for building dynamic lambda expression to be used against a LINQ to SQL data context. Yes, it's a giant beast that shows I didn't have any refactoring skills at some point in time, but the intent of the class is good and will hopefully help out someone.
using System;
using System.Linq.Expressions;
namespace DataAccess.DomainModel.Entities.BaseEntities
{
public abstract class BaseSearchClass<T>
{
#region Variables
private SearchClauseSeparator _clauseSeparator = SearchClauseSeparator.Or;
#endregion
#region Properties
public SearchClauseSeparator ClauseSeparator
{
get { return _clauseSeparator; }
set { _clauseSeparator = value; }
}
#endregion
#region Expression Building Methods
public virtual Expression<Func<T, bool>> BuildExpression()
{
// Create return object
var param = Expression.Parameter(typeof(T), "x");
Expression workingExpression = null;
// Loop through all properties
foreach (var property in this.GetType().GetProperties())
{
// Skip if missing SearchProperty attribute
var attrs = property.GetCustomAttributes(typeof(SearchableProperty), false);
if (attrs == null || attrs.Length == 0) continue;
// Get the attribute properties
var attr = attrs[0] as SearchableProperty;
var searchOperation = attr.SearchOperation;
var dataFieldName = attr.DataFieldName;
if (string.IsNullOrEmpty(dataFieldName)) dataFieldName = property.Name;
var prependOperator = attr.PrependOperator;
var expressionBuildingDelegate = attr.ExpressionBuildingDelegate;
// Get the value
var value = property.GetValue(this, null);
if (value == null) continue;
// Build the expression
var exp = GenerateExpressionForProperty(param, searchOperation, dataFieldName, value, expressionBuildingDelegate);
if (exp == null) continue;
// Combine expressions accordingly
if ((_clauseSeparator == SearchClauseSeparator.Or && prependOperator == SearchClauseSeparator.None) || prependOperator == SearchClauseSeparator.Or)
{
workingExpression = OrCombineExpressions(workingExpression, exp);
}
else
{
workingExpression = AndCombineExpressions(workingExpression, exp);
}
}
// See if expression is null
if (workingExpression == null)
{
workingExpression = CreateDefaultExpression(param);
}
// Return the lambda of the expression
return Expression.Lambda<Func<T, bool>>(workingExpression, param);
}
private Expression GenerateExpressionForProperty(Expression parameter, SearchOperation searchOperation,
string propertyName, object value, ExpressionDelegate expressionDelegate)
{
// Stop if value is null
if (value == null) return null;
// See if field is nullable
var propertyType = typeof(T).GetProperty(propertyName).PropertyType;
var isNullable = propertyType.Name.StartsWith("Nullable");
// See which operation to perform
var operation = expressionDelegate;
if (expressionDelegate == null) operation = DetermineOperator(searchOperation);
// See if value is an array
if (value.GetType().IsArray)
{
Expression returnExpression = null;
var theArray = value as Array;
foreach (var item in theArray)
{
returnExpression =
OrCombineExpressions(returnExpression,
BuildSingleExpression(parameter, propertyName, propertyType,
item, operation, isNullable));
}
return returnExpression;
}
// See if value is a DualValuedObject (for performing BETWEEN operations)
if (value is DualValuedObject)
{
// Stop if either value is null
var dvo = value as DualValuedObject;
if (dvo.Value1 == null || dvo.Value2 == null) return null;
// Build left side
var left = BuildSingleExpression(parameter, propertyName, propertyType,
dvo.Value1, Expression.GreaterThanOrEqual, isNullable);
// Build right side
var right = BuildSingleExpression(parameter, propertyName, propertyType,
dvo.Value2, Expression.LessThanOrEqual, isNullable);
// Combine them using AND
return AndCombineExpressions(left, right);
}
// Return a single expression
return BuildSingleExpression(parameter, propertyName, propertyType, value, operation, isNullable);
}
private Expression BuildSingleExpression(Expression parameter, string propertyName, Type propertyType,
object value, ExpressionDelegate delegateFunction, bool isNullable)
{
// Throw exception if property and value or not of the same type
if (!propertyType.ToString().Contains(value.GetType().ToString()))
{
throw new Exception(propertyName + " is a " + propertyType.ToString() +
", invalid attempt to compare to a " + value.GetType().ToString());
}
// Create the left and right hands of the expression
var left = Expression.Property(parameter, propertyName);
if (isNullable) left = Expression.Property(left, "Value");
var right = Expression.Constant(value);
// Combine using the specified operation
return delegateFunction(left, right);
}
private Expression OrCombineExpressions(Expression left, Expression right)
{
return (left == null) ? right : Expression.Or(left, right);
}
private Expression AndCombineExpressions(Expression left, Expression right)
{
return (left == null) ? right : Expression.And(left, right);
}
private Expression CreateDefaultExpression(Expression parameter)
{
return BuildSingleExpression(parameter, "ID", typeof(int), 0, Expression.GreaterThan, false);
}
#endregion
#region SearchOperation Methods
private ExpressionDelegate DetermineOperator(SearchOperation type)
{
switch (type)
{
case SearchOperation.GreaterThanOrEqualTo :
return Expression.GreaterThanOrEqual;
case SearchOperation.GreaterThan :
return Expression.GreaterThan;
case SearchOperation.LessThanOrEqualTo :
return Expression.LessThanOrEqual;
case SearchOperation.LessThan :
return Expression.LessThan;
case SearchOperation.Contains :
return Contains;
case SearchOperation.StartsWith :
return StartsWith;
case SearchOperation.EndsWith :
return EndsWith;
case SearchOperation.Equal :
default:
return Expression.Equal;
}
}
private static Expression CustomStringEvaluator(string stringEvaluator, Expression property, ConstantExpression value)
{
var method = typeof(string).GetMethod(stringEvaluator, new[] { typeof(string) });
return Expression.Call(property, method, value);
}
private static Expression Contains(Expression property, ConstantExpression value)
{
return CustomStringEvaluator("Contains", property, value);
}
private static Expression StartsWith(Expression property, ConstantExpression value)
{
return CustomStringEvaluator("StartsWith", property, value);
}
private static Expression EndsWith(Expression property, ConstantExpression value)
{
return CustomStringEvaluator("EndsWith", property, value);
}
#endregion
}
}