Expression tree, listFoo.Where(lambda) throws invalid argument error - c#

Why the below code throws '... has some invalid arguments at'
var qry = listFoo.Where(lambda);
How should i write my listFoo inorder to call the Where on it by passing the predicate delegate. My entire code follow below,
List<Foo> listFoo = new List<Foo>();
ParameterExpression foo = Expression.Parameter(typeof(Foo), "foo");
MemberExpression bar = Expression.PropertyOrField(foo, "Bar");
ConstantExpression abc = Expression.Constant("abc", typeof(string));
BinaryExpression test = Expression.Equal(bar, abc);
Expression<Func<Foo, bool>> lambda = Expression.Lambda<Func<Foo, bool>>(test, foo);
var qry = listFoo.Where(lambda);

Because your list is not IQueryable. Why don't you just do this?
var qry = listFoo.Where(foo => foo.Bar == "abc");
If you can't do that, do this:
var qry = listFoo.Where(lambda.Compile()); //warning, slow performance

The LINQ extension methods on IEnumerable take delegate instances (either Action<> or Func<> objects). Lambdas are just delegate literals, so in this case it won't take an Expression<Func<>> because you're giving it a PLAN, not a delegate. That's all an Expression is, is a plan for how to handle some data. IQueryable takes Expressions (and the compiler treats the delegate literals as Expressions) so that it can take the plans and consolidate (if they can be reduced).
For IEnumerable, you have to call Compile() on the Expression<Func<>> before you pass it to the Where method. However, you CAN do this:
var qry = listFoo.AsQueryable().Where(lambda);
However, this will give you an IQueryable<Foo> result, so you'd still need to do AsEnumerable, ToList, ToArray, etc.

Related

How to read the values of a property in expression tree?

I'd like to read the value of some of the properties in the expression tree some I can proceed to some calculations.
var products = db.Products
.Where(GetPredicate())
.ToList();
private Expression<Func<Product, bool>> GetPredicate()
{
ParameterExpression pe = Expression.Parameter(typeof(Product), "p");
Expression exp0 = Expression.Property(pe, "Price");
//I'd like to know the value of the 'Price'
// so I can do some calculation, then check whether
//this particular product meet the criteria...
Expression body = Expression.Constant(Result); //result is a boolean
var expr = Expression.Lambda<Func<Product, bool>>(body, new ParameterExpression[] { pe });
return expr;
}
You seem to be using a database query provider (LINQ2SQL, EF, etc.)
Before you try to use expressions to solve your problem you need to make sure the expression is understood by the query provider. In your case many of the Math methods can be converted to T-SQL valid statements. Also, if you are using Entity Framework you might want to leverage the System.Data.Objects.SqlClient.SqlFunctions class to create an expression and execute your logic on the SQL Server side against native T-SQL functions.
Now, something to understand on expression trees, is that values cannot be obtained from constructing expressions unless this is a LambdaExpression, once compiled you invoke it, in your case you can obtain the bool value.
If you need to work with the price value, you need to create more expressions representing the call to the other logic, in your example the expressions calling the static Sqrt method of the Math class.
private Expression<Func<Product, bool>> GetPredicate()
{
var pe = Expression.Parameter(typeof(Product), "p");
var price = Expression.Property(pe, "Price");
var priceDouble = Expression.Convert(price, typeof(double));
var sqrtMethod = typeof(Math).GetMethod("Sqrt");
var sqrtCall = Expression.Call(sqrtMethod, priceDouble);
var constant = Expression.Constant(4d);
var gtThan = Expression.GreaterThan(sqrtCall, constant);
var lambda = Expression.Lambda<Func<Product, bool>>(gtThan, pe);
return lambda;
}
As you can see all the logic is an expression and the provider can consume the whole expression and convert it to syntax that can be understood by the target process. The prior expression generates p => Math.Sqrt((double)p.Price) > 4d

C# Expression Casting as Derived Type

Using
IQueryable<T> query - where T : BaseEntity
I have the following code, used in a generic method - which uses reflection to call the .Where() method (this is working):
var predicate = Expression.Lambda(body, item);
MethodInfo whereCall = (typeof(Queryable).GetMethods().First(mi => mi.Name == "Where" && mi.GetParameters().Length == 2).MakeGenericMethod(query.ElementType));
MethodCallExpression call = Expression.Call(whereCall, new Expression[] { query.Expression, predicate });
query = query.Provider.CreateQuery<T>(call);
I would like to use something like this (and avoid reflection):
var predicate = Expression.Lambda<Func<T, bool>>(body, item);
query = query.Where(predicate);
But the problem with this code is that T is used as the base-type, and not the derived-type at run-time.
How can I cast T as query.ElementType (the derived type) ?
Your second piece of code is indeed better than the first one. You will need to invoke the Expression.Lambda method using reflection somehow. A convenient way to do that is this:
static IQueryable<T> CreateQuery<T>(IQueryable<T> query, ...) {
var predicate = Expression.Lambda<Func<T, bool>>(body, item);
query = query.Where(predicate);
return query;
}
Invoke this method using T as the derived type. You can perform that call using MakeGenericMethod. Depending on your scenario it might be enough to say:
CreateQuery((dynamic)query);

How to build a simple property selector expression in ef6

How can I create a property selector for entity framework like this?
public static List<T> StandardSearchAlgorithm<T>(this IQueryable<T> queryable, Func<T, string> property, string query)
{
return queryable.Where(e => property(e).ToLower().IndexOf(query) > -1).ToList();
}
I want the calling code to be able to be clean and simple like this:
var usernameResults = _db.Users.StandardSearchAlgorithm(u => u.Username, query);
I get a "The LINQ expression node type 'Invoke' is not supported in LINQ to Entities." error. I cannot work out how to get the expression built.
UPDATE:
Based on the answer by MBoros here is the code I ended up with. It works great.
The key to expression trees is to understand expression trees are all about breaking up what you normally write in code (like "e => e.Username.IndexOf(query)") into a series of objects: "e" gets its own object, "Username" its own object, "IndexOf()" its own object, the "query" constant its own object, and so on. The second key is to know that you can use a series of static methods on the Expression class to create various kinds of these objects, as shown below.
PropertyInfo pinfo = (PropertyInfo)((MemberExpression)property.Body).Member;
ParameterExpression parameter = Expression.Parameter(typeof(T), "e");
MemberExpression accessor = Expression.Property(parameter, pinfo);
ConstantExpression queryString = Expression.Constant(query, typeof(string));
ConstantExpression minusOne = Expression.Constant(-1, typeof(int));
MethodInfo indexOfInfo = typeof(string).GetMethod("IndexOf", new[] { typeof(string) }); // easiest way to do this
Expression indexOf = Expression.Call(accessor, indexOfInfo, queryString);
Expression expression = Expression.GreaterThan(indexOf, minusOne);
Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(expression, parameter);
//return predicate.Body.ToString(); // returns "e => e.Username.IndexOf(query) > -1" which is exactly what we want.
var results = queryable.Where(predicate).ToList();
return results;
Now I have a real problem, but I will ask it in a separate question. My real query looks like this:
public static List<T> StandardSearchAlgorithm<T>(this IQueryable<T> queryable, Func<T, string> property, string query)
{
return queryable.Where(e => property(e).IndexOf(query) > -1).Select(e=> new { Priority = property(e).IndexOf(query), Entity = e } ).ToList();
}
So I need to build an expression that returns an Anonymous Type!! Or even if I create a class to help, I need to write an expression that returns a new object. But I will include this in a separate question.
You cannot invoke CLR delegates so simply in sql. But you can pass in the property selector as an Expression tree., so your signature would be:
public static List<T> StandardSearchAlgorithm<T>(this IQueryable<T> queryable, Expression<Func<T, string>> property, string query)
Calling would look the same. But now that you have an expression in your hand, you can have a look at this answer:
Pass expression parameter as argument to another expression
It gives you the tools to simply put an expression tree inside another one. In your case it would look like:
Expression<Func<T, bool>> predicate = e => property.AsQuote()(e).Contains(query);
predicate = predicate.ResolveQuotes();
return queryable.Where(predicate).ToList();
Once you are there, you still have the .ToLower().Contains() calls (use .Contains instead of the .IndexOf()> 1). This is actually tricky. Normally the db uses its default collation, so if it set to CI (case insensitive), then it will do the compare that way. If you don't have any constraints, and can adjust the db collation, I would go for that. In this case you can omit the .ToLower() call.
Otherwise check out this anser: https://stackoverflow.com/a/2433217/280562

Maybe a Really simple, Dynamic Linq To Entities Select statement?

What I want basically is to be able to to the following (following is psuedo code)
string SelectField = cb1.Name.Substring(2);
MyContext.Items.Select(x=>x.SelectField )
I tried the following:
string SelectField = cb1.Name.Substring(2);
ParameterExpression pe = Expression.Parameter(typeof(Item), "p");
var expr = Expression.Lambda(Expression.Property(pe, SelectField), pe);
query = MyContext.Items.Select(p=>expr)
but it gave me the error:
The LINQ expression node type 'Lambda' is not supported in LINQ to Entities.
Is this possible? I just want to be able to select a single entity property based on the selection of my combobox.
I was able to get my desired results using the following code (not much different then my original attempt)
string SelectField = cb1.Name.Substring(2);
ParameterExpression pe = Expression.Parameter(typeof(Item), "p");
Expression expr = Expression.Lambda(Expression.Property(pe, SelectField), pe);
Expression<Func<Item, string>> expression = (Expression<Func<Item, string>>)expr;
var query = MyContext.Items.Select(expression);
All I was missing was the Func line. It now works as I wanted.
Ok, so with this type of solution you're likely going to have to do a bunch of reflection to get it to work dynamically.
string SelectField = cb1.Name.Substring(2);
ParameterExpression pe = Expression.Parameter(typeof(Item), "p");
First you have to invoke the lambda method without knowing the complete return type. The return type is Expression<Func<Item, ?>> (with the question mark being a placeholder for the property type.
var propertyType = typeof(Item).GetProperty(SelectField).GetGetMethod().ReturnType;
var lambdaMethodParamType = typeof(Func<,>).MakeGenericType(typeof(Item), propertyType);
var lambdaMethod = typeof(Expression).GetMethods().First(x => x.Name == "Lambda" && x.IsGenericMethod).MakeGenericMethod(lambdaMethodParamType);
var expr = lambdaMethod.Invoke(null, new object[] { Expression.Property(pe, SelectField), new ParameterExpression[] { pe } });
Then we have to invoke the select method in a similar fashion because the property type isn't know until runtime.
var selectMethod = typeof(Queryable).GetMethods().First(x => x.Name == "Select").MakeGenericMethod(typeof(Item), propertyType);
var query = (IQueryable)selectMethod.Invoke(null, new object[] { MyContext.Items, expr });
I don't know what you're doing with the query variable so I can't provide any further guidance with that at this time. Again, in this example we don't know what type will be contained in the collection until runtime because it is a collection of the property values.
FYI: My method for selecting the correct lambda and select was a total hack. You really should create a more robust search method examing the signature more completely to ensure you've found the correct method you are needing. I can provide a better example later if you need it when I have more time.

Add second group by key field to existing Lambda Expression

I have a Group by expression that I am dynamically creating for use in a LINQ query. Currently, to construct the expression, I use the following code:
var arg = Expression.Parameter(typeof(T), helper.getName());
var prop = Expression.Property(arg, "customerType");
var body = Expression.Convert(prop, typeof(object));
var lambda = Expression.Lambda<Func<Contact, object>>(body, arg);
var keySelector = lambda.Compile();
I then use the keySelector in the GroupBy for my LINQ query. My question is, if I wanted to add a second grouping criteria to this expression, say "salesStage", how would I add that to this existing expression?
You have a problem, because what the compiler does on a regular GroupBy call is generate a new anonymous type with the properties you define. If the type doesn't exist, we cannot create an expression creating an object of the type.
However, given that you are using this for LINQ-to-Objects, we can use the Tuple<> type to generate the grouping key. Hopefully you do not need to group on more than 8 parameters.
Here is a generic function to generate the grouping function:
static Func<T, object> BuildGrouper<T>(IEnumerable<string> properties) {
var arg = Expression.Parameter(typeof(T), helper.getName());
// This is the list of property accesses we will be using
var parameters = properties.Select(propName => Expression.Property(arg, propName)).ToList();
// Find the correct overload of Tuple.Create.
// This will throw if the number of parameters is more than 8!
var method = typeof(Tuple).GetMethods().Where(m => m.Name == "Create" && m.GetParameters().Length == parameters.Count).Single();
// But it is a generic method, we need to specify the types of each of the arguments
var paramTypes = parameters.Select(p => p.Type).ToArray();
method = method.MakeGenericMethod(paramTypes);
// Invoke the Tuple.Create method and return the Func
var call = Expression.Call(null, method, parameters);
var lambda = Expression.Lambda<Func<T, object>>(call, arg);
return lambda.Compile();
}

Categories

Resources