I have a list of Linq expressions like:
List<Expression<Func<Customer, bool>>>
I need to add a whole bunch of predicates from a search page, like:
x.Name.Contains(searchString)
x.Description.Contains(searchString)
...
I want to create a method so I don't end up with a mass of duplicated code. Something with a signature like:
void AddCustomerPredicate(List<Expression<Func<Customer, bool>>> predicates, Expression<Func<Customer, string>> prop, string searchString);
Which I would use like:
var predicates = new List<Expression<Func<Customer, bool>>>();
AddCustomerPredicate(predicates, x => x.Name, this.Name);
AddCustomerPredicate(predicates, x => x.Description, this.Description);
...
I've simplified the problem a bit, but that is the gist of it. I haven't done much work with expression trees and the like, so I'm not sure how to implement this method?
**EDIT**
I might have over simplified the problem too much. I know how to add to the list like predicates.Add(x => x.Name.Contains(this.searchString)), but I have various things I want to do on each search parameter before adding it to the list (e.g. check for null or empty). I therefore want to call a method on each search parameter as above, so all of that stuff can be contained in a single method.
What you really need is to have a method for translated Expression<Func<T, string>> to Expression<Func<T, bool>> :
public static Expression<Func<T, bool>> GetContainsExpression<T>(Expression<Func<T, string>> prop, string searchString)
{
var method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(searchString, typeof(string));
var containsMethodExp = Expression.Call(prop.Body, method, someValue);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, prop.Parameters[0]);
}
This is inspired by this other answer : How do I create an expression tree to represent 'String.Contains(“term”)' in C#?
var predicates = new List<Expression<Func<Customer, bool>>>();
predicates.Add(GetContainsExpression((Customer x) => x.Name, this.Name));
predicates.Add(GetContainsExpression((Customer x) => x.Description, this.Description));
I believe this will do what you want, and will also handle null property values (so calling Contains() does not throw):
private static void AddCustomerPredicate(
List<Expression<Func<Customer, bool>>> predicates,
Expression<Func<Customer, string>> accessor,
string searchString)
{
var x = accessor.Parameters[0];
var temp = Expression.Variable(typeof(string), "temp");
var predicate = Expression.Lambda<Func<Customer, bool>>(
Expression.Block(
new[] { temp },
Expression.Assign(
temp,
accessor.Body),
Expression.AndAlso(
Expression.IsFalse(
Expression.ReferenceEqual(
temp,
Expression.Default(typeof(string)))),
Expression.Call(
temp,
"Contains",
Type.EmptyTypes,
Expression.Constant(searchString)))),
x);
predicates.Add(predicate);
}
Related
I have a linq query which is as follows:
var query = _appDbContext.Persons
.Where(p => p.Id == 123)
.Select(lambda)
.ToList();
I use lambda because i have a variable in my select statement. The lambda:
var wantedData = "Adress";
var x = Expression.Parameter(typeof(Person), "x");
var body = Expression.PropertyOrField(x, wantedData);
var lambda = Expression.Lambda<Func<Person, Adress>>(body, x);
The query will return the addresses for person with id 123. Let's say instead of the addresses I would like to recieve all subscriptions this person has. Only setting wantedData = "Subscription" will not work, because of the "Adress" in the lambda statement. So I'm looking for a way to use a variable to change "Adress" in the lambda statement.
I tried the following, which obviously does not work.
var lambda = Expression.Lambda<Func<Person, wantedData>>(body, x);
Is there a way to do this?
Ok, I managed to create a generic query method that takes an Expression as parameter.
EDIT: This will receive a string value and will try to match it with an existing property on your Person class.
private IEnumerable<TResult> GetPersonData<TResult>(Expression<Func<Person, TResult>> selectExpression)
{
return _dbContext.Persons
// .Where(filter)
.Select(selectExpression)
.ToList();
}
public IEnumerable<object> GetData(string dataPropertyName)
{
switch(dataPropertyName) {
case nameof(Person.Address): return GetPersonData(p => p.Address);
case nameof(Person.Subscription): return GetPersonData(p => p.Subscription);
// other cases
default: throw new InvalidArgumentException("Invalid property name");
}
}
note that this code is just an example written on the spot and it might not work directly with copy-paste
I've made something similar to an OrderBy and tweak it a little for your select. This only works if you know the return type.
You can create an extension method with the following code:
public static IQueryable<TResult> Select<T,TResult>(this IQueryable<T> source, string propertyName)
{
var prop = typeof(T).GetProperties()
.FirstOrDefault(x => x.Name.ToUpper() == propertyName.ToUpper());
ParameterExpression pe = Expression.Parameter(typeof(T), "x");
MemberExpression body = Expression.Property(pe, prop.Name);
LambdaExpression lambda = Expression.Lambda(body, new ParameterExpression[] { pe });
MethodCallExpression selectCallExpression = Expression.Call(
typeof(Queryable),
"Select",
new Type[] { source.ElementType, prop.PropertyType },
source.Expression,
lambda);
return source.Provider.CreateQuery<TResult>(selectCallExpression);
}
That way, you can use it like:
var query = _appDbContext.Persons
.Where(p => p.Id == 123)
.Select<Person,string>(wantedData)
.ToList();
i try to query (linq to entities EF Core) a navigation properties collection, so i use any() like this :
var query = context.MyTable.Where(x => x.mycollectionproperties.Any(p => p.myprop == myvar );
It's work perfectly but now i want to construct the predicate and not defined it directly in the query.
so i do :
Func<T, bool> mypredicate = (p => p.myprop == myvar);
var query = context.MyTable.Where(x => x.mycollectionproperties.Any(mypredicate);
(I have replace T by my entity name)
but this generate an error : Object of type 'System.Linq.Expressions.TypedParameterExpression' cannot be converted to type 'System.Linq.Expressions.LambdaExpression'.
How can i construct my predicate to use it on Any() collection ?
Thank's
This line for example:
var query = context.MyTable.Where(x => x.mycollectionproperties.Any(p => p.myprop == 1));
When compiled will be compiled to something like this:
var xParameter = Expression.Parameter(typeof(Entity1), "x");
var pParameter = Expression.Parameter(typeof(Entity2), "p");
var anyMethod =
typeof(Enumerable)
.GetMethods()
.Single(x => x.Name == "Any" && x.GetParameters().Length == 2)
.MakeGenericMethod(typeof(Entity2));
var anyCondition = Expression.Lambda<Func<Entity2, bool>>(
Expression.Equal(
Expression.Property(
pParameter,
typeof(Entity2).GetProperty("myprop").GetMethod),
Expression.Constant(1, typeof(int))),
pParameter);
var query = context.MyTable.Where(
Expression.Lambda<Func<Entity1, bool>>(
Expression.Call(
null,
anyMethod,
new Expression[] {
Expression.Property(
xParameter,
typeof(Entity1).GetProperty("mycollectionproperties").GetMethod),
anyCondition
}),
xParameter));
This is called an expression tree. See this reference for more details:
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/expression-trees/
Although the Any method takes a Func, when constructing the expression tree, notice that an expression (Expression<Func<Entity2, bool>>) is given to the Any method.
There doesn't seem to be a way from C# to give the Any method an expression instead of a Func even if the whole thing is an expression tree (I mean in a parameterized way like you want to achieve).
The most obvious way to achieve what you want is to use the code from this post and replace the anyCondition variable with whatever expression you want to use for the condition inside Any.
Another way is to construct part of the expression tree "normally" and pass null to the Any method and then use an expression visitor to replace the null with your expression. Here is how such visitor would look like:
public class AnyMethodArgumentReplacingVisitor : ExpressionVisitor
{
private readonly Expression expression;
public AnyMethodArgumentReplacingVisitor(Expression expression)
{
this.expression = expression;
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.Name == "Any")
{
return Expression.Call(node.Object, node.Method, node.Arguments[0], expression);
}
return base.VisitMethodCall(node);
}
}
Here is how you would use it:
Expression<Func<Entity2, bool>> predicate =
a => a.myprop == 2;
Expression<Func<Entity1, bool>> expression =
b => b.mycollectionproperties.Any(null);
var expression2 =
(Expression<Func<Entity1, bool>>)
new AnyMethodArgumentReplacingVisitor(predicate).Visit(expression);
Please note that such visitor would replace the call to any Any method. It also assumes that only the overload of Any that takes a predicate is used. There is another overload of Any that does not take a predicate. If you need to use that, you need to adjust the code.
It looks to me your problem is in your definition of
Func<T, bool> mypredicate = (p => p.myprop == myvar);
You should not use T, you should use the type of mycollectionproperties
Assuming the property mycollectionproperties is defined as something like this
....
public IQueryable<YourType> mycollectionproperties { get; set; }
....
Then you should declare mypredicate as
Func<YourType, bool> mypredicate = (p => p.myprop == myvar);
You can see a working sample on .NetFiddle
I am using dynamic Linq for generic search.
I have list of Ids:
List<int> idList = new List<int> { 1, 5, 6};
In plain Linq, I would write:
q = q.Where(a => idList.Contains(a.MyId));
But now I have to use System.Linq.Dynamic because I don't know in advance name of the column.
string someId = "CustomId";
q = q.Where("#0"+ ".Contains(" + someId + ")", idList.ToArray());
But this gives error:
"No applicable method 'Contains' exists in type 'Int32'"
How can I achieve this?
Is there some extension library that implements Contains for dynamic Linq or some other way.
You could write something like this that builds your query function dynamically:
public static Func<ObjT, bool> PropertyCheck<ObjT, PropT>(string propertyName, Expression<Func<PropT, bool>> predicate)
{
var paramExpr = Expression.Parameter(typeof(ObjT));
var propExpr = Expression.Property(paramExpr, propertyName);
return Expression.Lambda<Func<ObjT, bool>>(Expression.Invoke(predicate, propExpr), paramExpr).Compile();
}
Then, it could be used like this:
foos.Where(PropertyCheck<Foo, int>("MyId", x => idList.Contains(x)));
Of course, you could also just provide your own Where extension method that does all that at once:
public static IEnumerable<T> Where<T, PropT>(this IEnumerable<T> self, string propertyName, Expression<Func<PropT, bool>> predicate)
{
var paramExpr = Expression.Parameter(typeof(T));
var propExpr = Expression.Property(paramExpr, propertyName);
return self.Where<T>(Expression.Lambda<Func<T, bool>>(Expression.Invoke(predicate, propExpr), paramExpr).Compile());
}
foos.Where<Foo, int>("MyId", x => idList.Contains(x));
You could use the expressions to do this dynamic query, try something like this, for sample:
import these namespaces:
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
And try this:
// a reference parameter
var x = Expression.Parameter(typeof (YourType), "x");
// contains method
var containsMethod = typeof (string).GetMethod("Contains", new[] {typeof (string)});
// reference a field
var fieldExpression = Expression.Property(instance, "PropertyName");
// your value
var valueExpression = Expression.Constant(yourId);
// call the contains from a property and apply the value
var containsValueExpression = Expression.Call(fieldExpression, containsMethod, valueExpression);
// create your final lambda Expression
var filterLambda = Expression.Lambda<Func<YourType, bool>>(containsValueExpression, x);
// apply on your query
q = q.Where(finalLambda);
Obs: make sure your property has a method called contains.
If you look at the source of Dynamic LINQ then you can see that parsing in many cases depends on variable predefinedTypes.
In your case you need change this variable like this
static readonly Type[] predefinedTypes = {
....
,typeof(List<int>)
};
after that next code will be work
List<int> idList = new List<int> { 1, 5, 6};
....
string someId = "CustomId";
q = q.Where("#0.Contains(" + someId + ")", idList);
#Felipe Oriani in his 90% answer used the string.Contains method and the yourId single value, but asked was:
q = q.Where(a => idList.Contains(a.MyId));
which is member (property) access to the a.
So here is the final tested extension method:
/// <summary>
/// Creates lambda expression predicate: (TEntity entity) => collection.Contains(entity.property)
/// </summary>
public static Expression<Func<TEntity, bool>> ContainsExpression<TEntity, TProperty, TCollection>(
this TCollection collection,
Expression<Func<TEntity, TProperty>> property
)
where TCollection : ICollection<TProperty>
{
// contains method
MethodInfo containsMethod = typeof(TCollection).GetMethod(nameof(collection.Contains), new[] { typeof(TProperty) });
// your value
ConstantExpression collectionInstanceExpression = Expression.Constant(collection);
// call the contains from a property and apply the value
var containsValueExpression = Expression.Call(collectionInstanceExpression, containsMethod, property.Body);
// create your final lambda Expression
Expression<Func<TEntity, bool>> result = Expression.Lambda<Func<TEntity, bool>>(containsValueExpression, property.Parameters[0]);
return result;
}
The example:
List<int> idList = new List<int> { 1, 5, 6 };
Expression<Func<MyEntity,int>> idExpression = entity => entity.Id;
var contains = idList.ContainsExpression(idExpression)
IQueryable<MyEntity> q = DbContext.Set<MyEntity>().Where(contains);
Another way to skin this cat would be to convert the contains to ORs.
someArray.Constains(someField) is equivalent to:
someField == someArray[0] or someField == someArray[1] and so on.
It's not ideal but if the array is small it could work.
I started with the IQueryable extension methods from this example on CodePlex.
What i believe i need is an IQueryable extension method to "Where", where the method signature looks like:
public static IQueryable<T> Where<T>(this IQueryable<T> source, string columnName, string keyword)
and effectively does this (assuming T.columnName is of type string):
source.Where(p => p.ColumnName.Contains("keyword"))
using the above CodePlex example, i think i understand how he got the OrderBy method working, but my problem seems a bit more complex and I don't know how to get the Contains("keyword") part working.
Thanks in advance,
--Ed
Update: 9/13/2010 6:26pm PST
I thought the following would work, but end up getting a NotSupportedException (The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.) when I execute the expression via Count(). Any ideas?
public static IQueryable<T> Where<T>(this IQueryable<T> source, string columnName, string keyword)
{
var type = typeof(T);
var property = type.GetProperty(columnName);
if (property.PropertyType == typeof(string))
{
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var sel = Expression.Lambda<Func<T, string>>(propertyAccess, parameter);
var compiledSel = sel.Compile();
return source.Where(item => compiledSel(item).Contains(keyword));
}
else
{
return source;
}
}
public static IQueryable<T> Where<T>(
this IQueryable<T> source, string columnName, string keyword)
{
var arg = Expression.Parameter(typeof(T), "p");
var body = Expression.Call(
Expression.Property(arg, columnName),
"Contains",
null,
Expression.Constant(keyword));
var predicate = Expression.Lambda<Func<T, bool>>(body, arg);
return source.Where(predicate);
}
Well a couple of years later, but if anybody still need this, here it is:
public static IQueryable<T> Has<T>(this IQueryable<T> source, string propertyName, string keyword)
{
if (source == null || propertyName.IsNull() || keyword.IsNull())
{
return source;
}
keyword = keyword.ToLower();
var parameter = Expression.Parameter(source.ElementType, String.Empty);
var property = Expression.Property(parameter, propertyName);
var CONTAINS_METHOD = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var TO_LOWER_METHOD = typeof(string).GetMethod("ToLower", new Type[] { });
var toLowerExpression = Expression.Call(property, TO_LOWER_METHOD);
var termConstant = Expression.Constant(keyword, typeof(string));
var containsExpression = Expression.Call(toLowerExpression, CONTAINS_METHOD, termConstant);
var predicate = Expression.Lambda<Func<T, bool>>(containsExpression, parameter);
var methodCallExpression = Expression.Call(typeof(Queryable), "Where",
new Type[] { source.ElementType },
source.Expression, Expression.Quote(predicate));
return source.Provider.CreateQuery<T>(methodCallExpression);
}
I called my method "Has" just to keep it short, sample usage:
filtered = filtered.AsQueryable().Has("City", strCity)
And you can concatenate to make it even more expressive:
filtered = filtered.AsQueryable().Has("Name", strName).Has("City", strCity).Has("State", strState);
By the way, the "IsNull()" attached to the strings is just another simple extension method:
public static Boolean IsNull(this string str)
{
return string.IsNullOrEmpty(str);
}
The .Contains("keyword") part is exactly right in your example.
It's the p.ColumnName part that's going to cause trouble.
Now, there are a number of ways of doing this, generally involving either reflection or Expression<>, neither of which is particularly efficent.
The problem here is by passing the column name as a string, you are doing to undo that exact thing LINQ was invented to allow.
However, there are probably better ways of accomplishing your overall task besides that way.
So, let's look at alternate ways:
You want to be able to say :
var selector = new Selector("Column1", "keyword");
mylist.Where(item => selector(item));
and have it was the equivalent of
mylist.Where(item=> item.Column1.Contains("keyword"));
How 'bout we go with:
Func<MyClass, string> selector = i => i.Column1;
mylist.Where(item => selector(item).Contains("keyword"));
or
Func<MyClass, bool> selector = i => i.Column1.Contains("keyword");
mylist.Where(item => selector(item));
These are easily expanded for alternatives:
Func<MyClass, string> selector;
if (option == 1)
selector = i => i.Column1;
else
selector = i => i.Column2;
mylist.Where(item => selector(item).Contains("keyword"));
See, in generics type of the object works dynamically. So when p.ColumnName is taken as string, the Contains of string is been executed.
Generally for any lambda expression you specify, it parses the thing into an Expression and ultimately produces the output at runtime.
If you see my post :
http://www.abhisheksur.com/2010/09/use-of-expression-trees-in-lamda-c.html
you will understand how it works actually.
How would I go about using an Expression Tree to dynamically create a predicate that looks something like...
(p.Length== 5) && (p.SomeOtherProperty == "hello")
So that I can stick the predicate into a lambda expression like so...
q.Where(myDynamicExpression)...
I just need to be pointed in the right direction.
Update: Sorry folks, I left out the fact that I want the predicate to have multiple conditions as above. Sorry for the confusion.
Original
Like so:
var param = Expression.Parameter(typeof(string), "p");
var len = Expression.PropertyOrField(param, "Length");
var body = Expression.Equal(
len, Expression.Constant(5));
var lambda = Expression.Lambda<Func<string, bool>>(
body, param);
Updated
re (p.Length== 5) && (p.SomeOtherProperty == "hello"):
var param = Expression.Parameter(typeof(SomeType), "p");
var body = Expression.AndAlso(
Expression.Equal(
Expression.PropertyOrField(param, "Length"),
Expression.Constant(5)
),
Expression.Equal(
Expression.PropertyOrField(param, "SomeOtherProperty"),
Expression.Constant("hello")
));
var lambda = Expression.Lambda<Func<SomeType, bool>>(body, param);
Use the predicate builder.
http://www.albahari.com/nutshell/predicatebuilder.aspx
Its pretty easy!
To combine several predicates with the && operator, you join them together two at a time.
So if you have a list of Expression objects called predicates, do this:
Expression combined = predicates.Aggregate((l, r) => Expression.AndAlso(l, r));
To associate Lambda expression each other:
An other way is to use the following code. It s more flexible than the Schotime answer in my advice and work perfectly. No external Nuggets needed
Framework 4.0
// Usage first.Compose(second, Expression.And)
public static Expression<T> Compose<T>(this Expression<T> First, Expression<T> Second, Func<Expression, Expression, Expression> Merge)
{
// build parameter map (from parameters of second to parameters of first)
Dictionary<ParameterExpression,ParameterExpression> 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 parameters from the first
Expression secondBody = ParameterRebinder.ReplaceParameters(map, Second.Body);
// apply composition of lambda expression bodies to parameters from the first expression
return Expression.Lambda<T>(Merge(First.Body, secondBody), First.Parameters);
}
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);
}
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> First, Expression<Func<T, bool>> second)
{
return First.Compose(second, Expression.Or);
}
public class ParameterRebinder : ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, ParameterExpression> map;
public 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);
}
}
I have a open-source project called Exprelsior that provides very simple ways to create dynamic predicates:
Based on your example:
var exp1 = ExpressionBuilder.CreateBinary<YourClass>("MyProperty.Length", 5, ExpressionOperator.Equals);
var exp2 = ExpressionBuilder.CreateBinary<YourClass>("SomeOtherProperty", "hello", ExpressionOperator.Equals);
var fullExp = exp1.And(exp2);
// Use it normally...
q.Where(fullExp)
It even supports full text predicate generation, so you can receive any dynamic query from an HTTP GET method, for example:
var exp1 = ExpressionBuilder.CreateBinaryFromQuery<YourClass>("eq('MyProperty.Length', '5')");
var exp2 = ExpressionBuilder.CreateBinaryFromQuery<YourClass>("eq('SomeOtherProperty', 'hello')");
var fullExp = exp1.And(exp2);
// Use it normally...
q.Where(fullExp)
It supports a lot more data types and operators.
Link: https://github.com/alexmurari/Exprelsior