C# LINQ Where Predicate Type Arguments - c#

I have an XElement with values for mock data.
I have an expression to query the xml:
Expression<Func<XElement, bool>> simpleXmlFunction =
b => int.Parse(b.Element("FooId").Value) == 12;
used in:
var simpleXml = xml.Elements("Foo").Where(simpleXmlFunction).First();
The design time error is:
The type arguments for method 'System.Linq.Enumerable.Where(System.Collections.Generic.IEnumerable, System.Func)' cannot be inferred from the usage. Try specifying the type arguments explicitly'
The delegate supplied to Where should take in an XElement and return a bool, marking if the item matches the query, I am not sure how to add anything more to the delegate or the where clause to mark the type.
Also, the parallel method for the real function against the Entity Framework does not have this issue. What is not correct with the LINQ-to-XML version?

Don't make simpleXmlFunction an Expression<Func<XElement, bool>>. Make it a Func<XElement, bool>. That's what's expected as a delegate of .Where.
Func<XElement, bool> simpleXmlFunction =
new Func<XElement, bool>(b => int.Parse(b.Element("FooId").Value) == 12);

I think the full answer includes the previous answer, David Morton's comment, and an updated code snippet:
The .Where implementation for IQueryable is different than the .Where implementation for IEnumerable. IEnumerable.Where expects a:
Func<XElement, bool> predicate
You can compile the function from the expression you have by doing:
Expression<Func<XElement, bool>> simpleXmlExpression =
b => int.Parse(b.Element("FooId").Value) == 12;
Func<XElement, bool> simpleXmlFunction = simpleXmlExpression.Compile();
var simpleXml = xml.Elements("Foo").Where(simpleXmlFunction).First();
This will allow you to look at the expression tree generated and to use the compiled form to query the xml collection.

Related

Dynamically construct where clause with Func<T, string> lambda - linq to entities

(this is for .Net Framework 4.7)
I'm trying to write up some extension methods to aid in creating dynamic where clauses for various entities. I started a few days ago, so there's likely a lot that I don't know and some that I probably misunderstood.
I managed to create one extension method already for filtering by 1 property which works as I expect it to (I did use reflection to get the property, couldn't get it working with an interface - well, without it executing the sql that is).
I can't seem to be able to get this one working for a lambda expression though.
Note, that the solution must not trigger sql execution. Because I was able to write up some variants that "worK', but they will trigger sql execution.
The way I work with this is that once I have the code ready, I start debugging and have the "query" in the watch. And it looks like this (notice the sql code)
Once I step over my FilterString method call, it either turns into a sql result, or I get an exception (with current code), which it shouldn't:
So here's my current code that throws the exception (currently not dealing with the "match" parameter, I am implementing an "equals" call. There will be others like, starts With, like, etc)
The exception is just one of those "type mismatch" having function cannot be passed as param to string Equals or what not.
public static IQueryable<T> FilterString<T>(this IQueryable<T> query, Match match,
string criteriaItem, Expression<Func<T, string>> getItemString)
where T : class
{
if (string.IsNullOrEmpty(criteriaItem))
{
return query;
}
var param = Expression.Parameter(typeof(T), "r");
var selector = Expression.Lambda<Func<T, string>>(getItemString, param);
Expression<Func<string, bool>> prototype = item => item == criteriaItem;
var predicate = Expression.Lambda<Func<T, bool>>(
prototype.Body.ReplaceParameter(prototype.Parameters[0], selector.Body),
selector.Parameters[0]);
return query.Where(predicate);
}
and the one that executes the sql instead of just generating it
public static IQueryable<T> FilterString<T>(this IQueryable<T> query, Match match,
string criteriaItem, Expression<Func<T, string>> getItemString)
where T : class
{
if (string.IsNullOrEmpty(criteriaItem))
{
return query;
}
var param = Expression.Parameter(typeof(T), "r");
//var value = Expression.Constant(getItemString);
var equals = typeof(string).GetMethod("Equals", new Type[] { typeof(string) });
var item = Expression.Invoke(getItemString, param);
var body = Expression.Call(Expression.Constant(criteriaItem),
equals,
item);
return query.Where(Expression.Lambda<Func<T, bool>>(body, param));
}
calling these is done like so
query = query.FilterString(match, criteria_value, (r) => r.SomeProperty.MaybeSomeOtherProp.SomeString);
query = query.FilterString(match, criteria_value, (r) => r.SomeProperty.Name);
This same extension method will be called on any number of different entities, with nay number of different properties and prop names. I guess I could make use of the reflection version I got working and passing in all the property names in some array of some sort, but that is just plain ugly.
So long story short, how can I get this working in the way I explained above, taht is: having the sql generated instead of executed?
Thank you,
Note, the "ReplaceParameter" extension method is the one from here: https://stackoverflow.com/a/39206392/630515
So, you're trying to merge your prototype item => item == criteriaItem. With a passed in string property expression, like (r) => r.SomeProperty.Name to create (r) => r.SomeProperty.Name == criteriaItem.
Expression<Func<string, bool>> prototype = item => item == criteriaItem;
var predicate = Expression.Lambda<Func<T, bool>>(
ReplacingExpressionVisitor.Replace(
prototype.Parameters[0],
getItemString.Body,
prototype.Body),
getItemString.Parameters[0]);
And I think you're trying to do it this way so that criteriaItem is bound to an sql parameter, rather than being inlined as a string constant. But your question was a little hard to follow.

How to create a delegate from Expression<Func< , >>? (pass a parameter to the expression)?

I have the following filter :
Expression<Func<Employee, bool>> fromDateFilterFourDays = z => EntityFunctions.TruncateTime(z.FiringDate) >= EntityFunctions.TruncateTime(DateTime.Now.AddDays(-4));
Expression<Func<Employee, bool>> fromDateFilterSixDays = z => EntityFunctions.TruncateTime(z.FiringDate) >= EntityFunctions.TruncateTime(DateTime.Now.AddDays(-6));
How can I make a delegate out of this filter ?
I don't want to create a variable for each given number , i.e. for four days or six days .
My understanding is that you want to:
Take in two parameters to the delegate, the employee and the number of days.
Compile that expression into a delegate.
The first part can be done by adding the days to the parameter list:
Expression<Func<Employee, int, bool>> fromDateFilter = (z, n) => EntityFunctions.TruncateTime(z.FiringDate) >= EntityFunctions.TruncateTime(DateTime.Now.AddDays(n));
The second by using the Compile method:
var del = fromDateFilter.Compile();
// use it
del(employee, -4);
You can easily turn Expression<Func<...>> to Func<...> by using Compile method.
However, keep in mind that the sample expressions you provided will not work, because they are using Canonical Functions which are just placeholders for mapping the corresponding database SQL functions, and will throw exception if you try to actually evaluate them (which will happen with Func).
From the other side, if the question is actually how to parametrize the sample expressions, it could be like this
static Expression<Func<Employee, bool>> DateFilter(int currentDateOffset)
{
return e => EntityFunctions.TruncateTime(e.FiringDate) >= DateTime.Today.AddDays(currentDateOffset);
}
This is done by calling the Invoke() method:
fromDateFilterFourDays.Invoke(employee);
Or you can Compile() the expression to a func and then call the func:
var fromDateFilterFourDaysFunc = fromDateFilterFourDays.Compile();
fromDateFilterFourDaysFunc(employee);

Passing compiled expressions to where() clause

I'm using calculated properties on my EF object which can't be passed directly to a where() clause:
{"The specified type member 'SomeProp' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported."}
Based on what I read on SO, this could be avoided by passing a Expression<Func<T, bool>> as the argument to where().
Compiling the expression to a Func works fine:
Expression<Func<Foo, bool>> expr = e => f => f.SomeCalculatedProperty == 1;
Func<Foo, bool> compiled = expr.Compile();
Foo result = dbContext.Foo.Where(compiled);
But passing the expression gives me the error above, i.e:
Expression<Func<Foo, bool>> expr = e => f => f.SomeCalculatedProperty == 1;
Foo result = dbContext.Foo.Where(e);
Based on this, shouldn't the last example work fine?
In example one you are calling the extension method Queryable.Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate) this converts the query to sql and applies the filter server side.
In example two you are calling the extension method Enumerable.Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate), this returns all rows from the database then in memory in the program it applies the filter.
For your third example, I will just quote Servy's comment
Your first and third snippets are functionally identical. There must
be some difference that you haven't shown for one to work and another
to not.

Creating, combining and caching lambda expressions

Say I have the following.
foreach (var loop in helper.Loop(x => x.LoopItems))
{
loop.Text(x => x.Name);
loop.Span(x => x.Name);
foreach (var loopItem in loop.Loop(x => x.NestedLoopItems))
{
loopItem.Text(x => x.Age);
}
}
This works just create with my current implementation, however it has to compile the inner lambda expression as many times as there are loop items. Currently this does something like this to create the expression to access a List<T> indexer. eg. x.ListItems[i]
var methodCallExpression = Expression.Call(_expression, ((PropertyInfo) _expression.Member).PropertyType.GetMethod("get_Item"), Expression.Constant(i));
var expression = Expression.Lambda<Func<TModel, T>>(methodCallExpression, _expression.GetParameter<TModel>());
It then does
var newExpression = CombineExpression(listExpression);
var enumerable = newExpression.Compile().Invoke(_htmlHelper.ViewData.Model);
And it is the compile step that seems to be the expensive one.
Would there be any way to cache this given the fact that it needs to create a new one for each loop, such that i in Expression.Constant(i) needs to increment each time modifying the expression.
I don't know what CombineExpression does but if you can change from Func<TModel, T> to Func<TModel, int, T> (assuming the indexer is always an int) if not you could add another generic to the method since you are already passing in "i" to get the Constant in the expression.
Also not entirely sure what type _expression is either so I don't know if this exactly is calling the overloads I think it is.
var parameterExpression = Expression.Parameter(typeof(int), "i");
var methodCallExpression = Expression.Call(_expression, ((PropertyInfo) _expression.Member).PropertyType.GetMethod("get_Item"), parameterExpression);
var expression = Expression.Lambda<Func<TModel, int, T>>(methodCallExpression, _expression.GetParameter<TModel>(), parameterExpression);
Then you could compile expression and get Func<TModel, int, T> and when invoking the Func you would pass in the "i" value.
Again since I don't know what CombineExpression does but if you get a strongly typed Func out of it you can just call it without the invoke.
Another side note why do expressions to get access to a list indexer? Why not just use IEnumerable<> or worst cast if you don't know they type but need the objects cast to IEnumerable (non-generic) and iterate over that?

How to use typeof(object).GetMethod() with lambda parameter

How does one use Type.GetMethod() to get a method with lambda parameters? I'm trying to get the Queryable.Any method for a parameter of something like Func, using this:
typeof(Queryable).GetMethod("Any", new Type[]{typeof(Func<ObjType, bool>)})
but it keeps returning null.
There are four things wrong:
There's no such thing as a "lambda parameter". Lambda expressions are often used to provide arguments to methods, but they're converted into delegates or expression trees
You've missed off the first parameter of Queryable.Any - the IQueryable<T>
You're using Func<ObjType, bool> which is a delegate type, instead of Expression<Func<ObjType, bool>> which is an expression tree type
You can't get at generic methods in quite that way, unfortunately.
You want:
var generic = typeof(Queryable).GetMethods()
.Where(m => m.Name == "Any")
.Where(m => m.GetParameters().Length == 2)
.Single();
var constructed = generic.MakeGenericMethod(typeof(ObjType));
That should get you the relevant Any method. It's not clear what you're then going to do with it.

Categories