Reputation:
Based on my question from yesterday:
if I had to append to my existing 'where' expression, how would i append?
Expression<Func<Client, bool>> clientWhere = c => true;
if (filterByClientFName)
{
clientWhere = c => c.ClientFName == searchForClientFName;
}
if (filterByClientLName)
{
clientWhere = c => c.ClientLName == searchForClientLName;
}
The user can input either first name or last name or both. If they enter both i want to append to the expression. Trying to see if there is an equivalent of an append where i could do
clientWhere.Append or clientWhere += add new expression
or something similar
Upvotes: 65
Views: 44267
Reputation: 194
Much easier and elegant solution from 2020 :)
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.And);
}
Works for IQueryable.
Upvotes: 2
Reputation: 2880
If you encounter a similar problem, you can find all possible solutions in this great topic. Or just use PredicateBuilder is awesome helper for this poporse.
var predicate = PredicateBuilder.True<Client>();
if (filterByClientFName)
{
predicate = predicate.And(c => c.ClientFName == searchForClientFName);
}
if (filterByClientLName)
{
predicate = predicate.And(c => c.ClientLName == searchForClientLName);
}
var result = context.Clients.Where(predicate).ToArray();
It is some builder implementation.
public static class PredicateBuilder
{
// Creates a predicate that evaluates to true.
public static Expression<Func<T, bool>> True<T>() { return param => true; }
// Creates a predicate that evaluates to false.
public static Expression<Func<T, bool>> False<T>() { return param => false; }
// Creates a predicate expression from the specified lambda expression.
public static Expression<Func<T, bool>> Create<T>(Expression<Func<T, bool>> predicate) { return predicate; }
// Combines the first predicate with the second using the logical "and".
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.AndAlso);
}
// Combines the first predicate with the second using the logical "or".
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.OrElse);
}
// Negates the predicate.
public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expression)
{
var negated = Expression.Not(expression.Body);
return Expression.Lambda<Func<T, bool>>(negated, expression.Parameters);
}
// Combines the first expression with the second using the specified merge function.
static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
{
// zip parameters (map from parameters of second to parameters of first)
var map = first.Parameters
.Select((f, i) => new { f, s = second.Parameters[i] })
.ToDictionary(p => p.s, p => p.f);
// replace parameters in the second lambda expression with the parameters in the first
var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
// create a merged lambda expression with parameters from the first expression
return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
}
class ParameterRebinder : ExpressionVisitor
{
readonly Dictionary<ParameterExpression, ParameterExpression> map;
ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
{
this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
}
public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
{
return new ParameterRebinder(map).Visit(exp);
}
protected override Expression VisitParameter(ParameterExpression p)
{
ParameterExpression replacement;
if (map.TryGetValue(p, out replacement))
{
p = replacement;
}
return base.VisitParameter(p);
}
}
}
Upvotes: 11
Reputation: 9
I tried to implement this kind of stuff. Took me a day to find out. My solution is based on filter in a loop based on a Array of predicate. As a note, it s totally Generic and based Reflection because the only information about class and field are String. To make it simple, i call directly the Model class but in a project you should go by a controler who is calling the Model.
So here we go : The Model part where T is a Generic in the class
public class DALXmlRepository<T> where T : class
{
public T GetItem(Array predicate)
{
IQueryable<T> QueryList = null;
QueryList = ObjectList.AsQueryable<T>().Where((Expression<Func<T, bool>>)predicate.GetValue(0));
for (int i = 1; i < predicate.GetLength(0); i++)
{
QueryList = QueryList.Where((Expression<Func<T, bool>>)predicate.GetValue(i));
}
if (QueryList.FirstOrDefault() == null)
throw new InvalidOperationException(this.GetType().GetGenericArguments().First().Name + " not found.");
return QueryList.FirstOrDefault();
}
}
Now the LambdaExpression Builder, it's a base one(with String type or something else) , you can improve it with more functionnality :
private static Expression BuildLambdaExpression(Type GenericArgument, string FieldName, string FieldValue)
{
LambdaExpression lambda = null;
Expression Criteria = null;
Random r = new Random();
ParameterExpression predParam = Expression.Parameter(GenericArgument, r.Next().ToString());
if (GenericArgument.GetProperty(FieldName).PropertyType == typeof(string))
{
Expression left = Expression.PropertyOrField(predParam, FieldName);
Expression LefttoUpper = Expression.Call(left, "ToUpper", null, null);
//Type du champ recherché
Type propType = GenericArgument.GetProperty(FieldName).PropertyType;
Expression right = Expression.Constant(FieldValue, propType);
Expression RighttoUpper = Expression.Call(right, "ToUpper", null, null);
Criteria = Expression.Equal(LefttoUpper, RighttoUpper);
}
else
{
Expression left = Expression.PropertyOrField(predParam, FieldName);
Type propType = GenericArgument.GetProperty(FieldName).PropertyType;
Expression right = Expression.Constant(Convert.ChangeType(FieldValue, propType), propType);
Criteria = Expression.Equal(left, right);
}
lambda = Expression.Lambda(Criteria, predParam);
return lambda;
}
Now the Calling function :
public static Hashtable GetItemWithFilter(string Entity, XMLContext contextXML, Hashtable FieldsNameToGet, Hashtable FieldFilter)
{
//Get the type
Type type = Type.GetType("JP.Model.BO." + Entity + ", JPModel");
Type CtrlCommonType = typeof(CtrlCommon<>).MakeGenericType( type );
//Making an instance DALXmlRepository<xxx> XMLInstance = new DALXmlRepository<xxx>(contextXML);
ConstructorInfo ci = CtrlCommonType.GetConstructor(new Type[] { typeof(XMLContext), typeof(String) });
IControleur DalInstance = (IControleur)ci.Invoke(new object[] { contextXML, null });
//Building the string type Expression<func<T,bool>> to init the array
Type FuncType = typeof(Func<,>).MakeGenericType( type ,typeof(bool));
Type ExpressType = typeof(Expression<>).MakeGenericType(FuncType);
Array lambda = Array.CreateInstance(ExpressType,FieldFilter.Count);
MethodInfo method = DalInstance.GetType().GetMethod("GetItem", new Type[] { lambda.GetType() });
if (method == null)
throw new InvalidOperationException("GetItem(Array) doesn't exist for " + DalInstance.GetType().GetGenericArguments().First().Name);
int j = 0;
IDictionaryEnumerator criterias = FieldFilter.GetEnumerator();
criterias.Reset();
while (criterias.MoveNext())
{
if (!String.IsNullOrEmpty(criterias.Key.ToString()))
{
lambda.SetValue(BuildLambdaExpression(type, criterias.Key.ToString(), criterias.Value.ToString()),j);
}
else
{
throw new JPException(JPException.MessageKey.CONTROLER_PARAMFIELD_EMPTY, "GetItemWithFilter", criterias.Key.ToString());
}
j++;
}
Object item = method.Invoke(DalInstance, new object[] { lambda });
}
The argument are : String Entity : Entity class name. XMLContext : it s the unit of work of the repository, argument i use to initialize the Model class Hashtable FieldsNameToGet : Index/value of the list of the field i want to get back Hashtable FieldFilter : the key/Value with FieldName/Content used to make the Lambda expression
Good Luck.
Upvotes: -1
Reputation: 2998
It´s not exactly the answer for your question, but, I was looking for the same thing you are, and then I've found a better answer to my question.
Instead of building a dynamic Expression, you could retrieve the IQueryable and then filter what you want like this:
var customers = CustomerRepository.AllEntities();
if (!forename.IsNullOrEmpty())
customers = customers.Where(p => p.Forename == forename);
if (!familyname.IsNullOrEmpty())
customers = customers.Where(p => p.FamilyNames.Any(n => n.Name==familyname));
if (dob.HasValue)
customers = customers.Where(p => p.DOB == dob);
Note: I was concerned about executing more then one ".Where" statement because I was afraid this would generate more than one query in the DataBase, or because I would have to retrive all records and then filter them, but this is not true, Linq dynamic generate just one query only when you call .ToList() method.
Here you can see original question that I've took the example from.
Upvotes: 2
Reputation: 925
Or something to add to Josh (Put it in my bag of tricks):
public static IQueryable<TSource> ObjectFilter<TSource>(this TSource SearchObject, List<Predicate<TSource>> andCriteria, List<Predicate<TSource>> orCriteria) where TSource : IQueryable<TSource>
{
//Yeah :)
Expression<Func<TSource, bool>> ObjectWhere = O => andCriteria.All(pred => pred(O)) && orCriteria.Any(pred => pred(O));
return SearchObject.Where<TSource>(ObjectWhere);
}
Upvotes: 1
Reputation: 1771
Take a look at Predicate Builder, I believe this might work for you.
Upvotes: 6
Reputation: 28600
I believe you can just do the following:
Expression<Func<Client, bool>> clientWhere = c => true;
if (filterByClientFName)
{
var prefix = clientWhere.Compile();
clientWhere = c => prefix(c) && c.ClientFName == searchForClientFName;
}
if (filterByClientLName)
{
var prefix = clientWhere.Compile();
clientWhere = c => prefix(c) && c.ClientLName == searchForClientLName;
}
If you need to keep everything in Expression
-land (to use with IQueryable
), you could also do the following:
Expression<Func<Client, bool>> clientWhere = c => true;
if (filterByClientFName)
{
Expression<Func<Client, bool>> newPred =
c => c.ClientFName == searchForClientFName;
clientWhere = Expression.Lambda<Func<Freight, bool>>(
Expression.AndAlso(clientWhere, newPred), clientWhere.Parameters);
}
if (filterByClientLName)
{
Expression<Func<Client, bool>> newPred =
c => c.ClientLName == searchForClientLName;
clientWhere = Expression.Lambda<Func<Freight, bool>>(
Expression.AndAlso(clientWhere, newPred), clientWhere.Parameters);
}
This can be made less verbose by defining this extension method:
public static Expression<TDelegate> AndAlso<TDelegate>(this Expression<TDelegate> left, Expression<TDelegate> right)
{
return Expression.Lambda<TDelegate>(Expression.AndAlso(left, right), left.Parameters);
}
You can then use syntax like this:
Expression<Func<Client, bool>> clientWhere = c => true;
if (filterByClientFName)
{
clientWhere = clientWhere.AndAlso(c => c.ClientFName == searchForClientFName);
}
if (filterByClientLName)
{
clientWhere = clientWhere.AndAlso(c => c.ClientLName == searchForClientLName);
}
Upvotes: 80
Reputation: 12975
This is a complex scenario. You are almost building your own query engine on top of LINQ. JaredPar's solution (where did it go?) is great if you want a logical AND between all of your criteria, but that may not always be the case.
When I was wrangling with this in one of my project recently, I created two Lists:
List<Predicate<T>> andCriteria;
List<Predicate<T>> orCriteria;
(In this case, T is Client, for you)
I would populate the Lists with predicates that I want to be true. For instance,
decimal salRequirement = 50000.00;
andCriteria.Add(c => c.Salary > salRequirement);
orCriteria.Add(c => c.IsMarried);
Then, I would check against all the criteria in the Lists in my Where clause. For instance:
Expression<Func<Client, bool>> clientWhere =
c => andCriteria.All(pred => pred(c) ) && orCriteria.Any(pred => pred(c) );
This could also be done with a for-loop for readability's sake. Remember to use the correct order of operations when applying your OR and AND clauses.
Upvotes: 14