Advanced Dynamic LINQ Expression
// sample query to generate dynamically
_people.Where(x =>
(x.Age > 60 && x.Address.City == "Paoli") ||
(x.Address.Street == "Market Street"));
// The following code represents the use of the class in creating the query dynamically
var lambda = new ExpressionCriteria<Person>()
.Add("Age", 60, ExpressionType.GreaterThan)
And()
.Add("Address.City", "Paoli", ExpressionType.Equal)
Or()
.Add("Address.Street", "Market Street", ExpressionType.Equal)
.GetLambda().Compile();
var result = _people.Where(lambda);
//There are three critical elements of a criterion element:
//Property Name: The property of the Expression Criteria's type that will be used as the basis of a criterion element.
//Value: The value used, in conjunction with an expression type to compare to the property.
//Operator: The Expression Type used to compare the Property Name with the Value.
//The following is an example of the Property, Value, Operator relationship:
//x.Age > 60
//Property: Age
//Value: 60
//Operator: Greater Than
//When the Add() method is invoked, the current state of the _andOr variable is applied as well. By default, the value is set to And.
//Each criterion has four attributes:
//Property Name
//Value
//Expression Type
//AndOr
//When an Expression Criterion Expression is created, the property name is validated against the specified type.
// Class used for the above codes
public class ExpressionCriterion<T>
{
public ExpressionCriterion(string propertyName, object value, ExpressionType op, string andOr = "And")
{
AndOr = andOr;
PropertyName = propertyName;
Value = value;
Operator = op;
validateProperty(typeof(T), propertyName);
}
PropertyInfo validateProperty(Type type, string propertyName)
{
string[] parts = propertyName.Split('.');
var info = (parts.Length > 1)
? validateProperty(
type.GetProperty(
parts[0]).PropertyType,
parts.Skip(1).Aggregate((a, i) =>
a + "." + i)) : type.GetProperty(propertyName);
if (info == null)
throw new ArgumentException(propertyName,
$"Property {propertyName} is not a member of {type.Name}");
return info;
}
public string PropertyName { get; }
public object Value { get; }
public ExpressionType Operator { get; }
public string AndOr { get; }
}
public class ExpressionCriteria<T>
{
List<ExpressionCriterion<T>> _expressionCriterion = new List<ExpressionCriterion<T>>();
private string _andOr = "And";
public ExpressionCriteria<T> And()
{
_andOr = "And";
return this;
}
public ExpressionCriteria<T> Or()
{
_andOr = "Or";
return this;
}
public ExpressionCriteria<T> Add(string propertyName, object value, ExpressionType op)
{
var newCriterion = new ExpressionCriterion<T>(propertyName, value, op, _andOr);
_expressionCriterion.Add(newCriterion);
return this;
}
// The first step in creating a lambda expression is to first create an expression:
public BinaryExpression GetExpression(ParameterExpression parameter, ExpressionCriterion<T> expressionCriteria)
{
Expression expression = parameter;
foreach (var member in expressionCriteria.PropertyName.Split('.'))
{
expression = Expression.PropertyOrField(expression, member);
}
return Expression.MakeBinary(expressionCriteria.Operator, expression, Expression.Constant(expressionCriteria.Value));
}
// Once the expression is created, a lambda expression can be created:
public Expression<Func<T, bool>> GetLambda()
{
Expression expression = null;
var parameterExpression = Expression.Parameter(typeof(T), typeof(T).Name.ToLower());
foreach (var item in _expressionCriterion)
{
if (expression == null)
{
expression = GetExpression(parameterExpression, item);
}
else
{
expression = item.AndOr == "And" ? Expression.And(expression, GetExpression(parameterExpression, item)) :
Expression.Or(expression, GetExpression(parameterExpression, item));
}
}
return expression != null ?
Expression.Lambda<Func<T, bool>>(expression, parameterExpression) : null;
}
}