Creating, combining and caching lambda expressions - c#

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?

Related

How do I use an array of values in a LINQ Expression builder?

I want to dynamically build a LINQ query so I can do something like
var list = n.Elements().Where(getQuery("a", "b"));
instead of
var list = n.Elements().Where(e => e.Name = new "a" || e.Name == "c");
(Most of the time, I need to pass XNames with namespaces, not just localnames...)
My problem is in accessing the array elements:
private static Func<XElement, bool> getQuery(XName[] names)
{
var param = Expression.Parameter(typeof(XElement), "e");
Expression exp = Expression.Constant(false);
for (int i = 0; i < names.Length; i++)
{
Expression eq = Expression.Equal(
Expression.Property(param, typeof(XElement).GetProperty("Name")!.Name),
/*--->*/ Expression.Variable(names[i].GetType(), "names[i]")
);
}
var lambda = Expression.Lambda<Func<XElement, bool>>(exp, param);
return lambda.Compile();
}
Obviously the Variable expression is wrong, but I'm having difficulty building an expression capable of accessing the array values.
Do you need to create an expression and compile it? Unless I'm missing some nuance to this, all you need is a function that returns a Func<XElement, bool>.
private Func<XElement, bool> GetQuery(params string[] names)
{
return element => names.Any(n => element.Name == n);
}
This takes an array of strings and returns a Func<XElement>. That function returns true if the element name matches any of the arguments.
You can then use that as you described:
var list = n.Elements.Where(GetQuery("a", "b"));
There are plenty of ways to do something like this. For increased readability an extension like this might be better:
public static class XElementExtensions
{
public static IEnumerable<XElement> WhereNamesMatch(
this IEnumerable<XElement> elements,
params string[] names)
{
return elements.Where(element =>
names.Any(n => element.Name == n));
}
}
Then the code that uses it becomes
var list = n.Elements.WhereNamesMatch("a", "b");
That's especially helpful when we have other filters in our LINQ query. All the Where and other methods can become hard to read. But if we isolate them into their own functions with clear names then the usage is easier to read, and we can re-use the extension in different queries.
If you want to write it as Expression you can do it like so:
public static Expression<Func<Person, bool>> GetQuery(Person[] names)
{
var parameter = Expression.Parameter(typeof(Person), "e");
var propertyInfo = typeof(Person).GetProperty("Name");
var expression = names.Aggregate(
(Expression)Expression.Constant(false),
(acc, next) => Expression.MakeBinary(
ExpressionType.Or,
acc,
Expression.Equal(
Expression.Constant(propertyInfo.GetValue(next)),
Expression.Property(parameter, propertyInfo))));
return Expression.Lambda<Func<Person, bool>>(expression, parameter);
}
Whether or not you compile the expression is determined by the means you want to achieve. If you want to pass the expression to a query provider (cf. Queryable.Where) and have e.g. the database filter your values, then you may not compile the expression.
If you want to filter a collection in memory, i.e. you enumerate all elements (cf. Enumerable.Where) and apply the predicate to all the elements, then you have to compile the expression. In this case you should probably not use the Expression api as this adds complexity to your code and you are then more vulnerable to runtime errors.

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.

Extending LINQ Expressions

Newbie LINQ Expressions question-
Expression<Func<TLookupModel, TValue>> idSelector;
IEnumerable<TLookupModel> source;
TValue id;
I'm trying to do (pseudo-code):
source.AsQueryable().FirstOrDefault(x => idSelector == id)
My feeble attempt thus far is along the lines of:
var expressionParam = idSelector.Parameters.First();
ConstantExpression selectedValueConstant = Expression.Constant(id, typeof(TValue));
var idEqualExpression = Expression.Equal(idSelector, selectedValueConstant);
var lambda = Expression.Lambda<Func<TLookupModel, bool>>(idEqualExpression, expressionParam);
var selectedSourceItem = source.AsQueryable().FirstOrDefault(lambda);
I think that gives you a guess as to how I've been thinking so far. I've tried with and without the parameters, different combinations of Expression method calls, trying to get the "parameter" to come from the FirstOrDefault() call, but after reading lots of tutorials I can't get my head around how to extend a "member" expression to equal a constant in this way.
You got really close.
Your idExpression is an Expression in the form of x => x.Property. However, you're passing the whole expression to the Equal expression. Change that to pass only the body:
var idEqualExpression = Expression.Equal(idSelector.Body, selectedValueConstant);
Then you can compile the lambda and pass it to FirstOrDefault without casting to a queryable:
var selectedSourceItem = source.FirstOrDefault(lambda.Compile());

Call Any inside Expression and pass the delegate

I have a scenario here where I need to hit a dynamic query using linq (with nhibernate). The final query should look like this:
long[] values = { ... };
var result = Queryable<Entity>.Where(x => x.Documents.Any(d => values.Contains(d.Id)))
.ToList();
The generic Entity and the property Documents can change and it will be defined by some user configurations. The type of collection Documents is ICollection<T> where T is Document type. I am trying to create an Expression tree to define these statements dynamically but I am getting some issues. Look the code and comments bellow of what I have tried.
I have create this function to return the delagate I want to use inside the Any method:
public static Func<T, bool> GetFunc<T>(long[] values)
where T : Entity
{
return x => values.Contains(x.Id);
}
And I am using the Expression class to make the expression like this (see code and comments):
// define my parameter of expression
var parameter = Expression.Parameter(typeof(T), "x");
// I get an array of IDs (long) as argument and transform it on an Expression
var valuesExpression = Expression.Constant(values);
// define the access to my collection property. propertyFilter is propertyinfo for the `Documents` of the sample above.
// I get an expression to represent: x.Documents
var collectionPropertyExpression = Expression.Property(parameter, propertyFilter);
// get the T generic type of the ICollection<T> from propertyFilter. I get the `Documents` of sample above.
var entityFilterType = propertyFilter.PropertyType.GetGenericArguments()[0];
// get the definition of `Any` extension method from `Enumerable` class to make the expression
var anyMethod = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
.First(x => x.Name == "Any" && x.GetParameters().Length == 2)
.MakeGenericMethod(entityFilterType);
// get a methodBase for GetFunc to get the delagete to use inside the Any
// using the `Document` generic type
var collectionBody = typeof(LookUpHelper).GetMethod("GetFunc", BindingFlags.Public | BindingFlags.Static)
.MakeGenericMethod(entityFilterType);
// call the any passing the collection I need and convert it to a Delegate
// I get something like: x => values.Contains(x.Id) ... where x if the `Document`
var func = (Delegate)collectionBody.Invoke(null, new object[] { values });
// get the func as an expression .. maybe the problem is here
var funcExpression = Expression.Constant(func);
// call the any passing the collection and my delagate as arguments
var f = Expression.Call(anyMethod, collectionPropertyExpression, funcExpression);
// I already have an expression and concatenate it using `AndAlso` operator.
body = Expression.AndAlso(body, f);
// finally, I built up to lambda expression and apply it on my queryable
var filterExpression = Expression.Lambda<Func<T, bool>>(body, parameter);
var result = Queryable.Where(filterExpression).ToList();
It executes until the query be executed by ToList method. I am getting the following error:
Could not parse expression
'x.Documents.Any(value(System.Func`2[Project.Document,System.Boolean]))':
The object of type 'System.Linq.Expressions.ConstantExpression' cannot
be converted to type 'System.Linq.Expressions.LambdaExpression'. If
you tried to pass a delegate instead of a LambdaExpression, this is
not supported because delegates are not parsable expressions.
I am not sure what I am doing wrong. Someone can help me?
Thank you.
You are passing a Func where an Expression<Func> is expected. The former is a delegate and the latter is an expression.
public static Expression<Func<T, bool>> GetFunc<T>(long[] values)
where T : Entity
{
return x => values.Contains(x.Id);
}
Now you forego needing to build the expression manually with your expression helper class since you already have the expression.

Complex LINQ sorting using Lambda Expressions

Does anyone have/know of an IQueryable.OrderBy extension that takes an Expression (retrieved, for example, by Reflection)? I believe the function would look something like this:
public static IQueryable<TEntity> OrderBy<TEntity>
(this IQueryable<TEntity> source, Expression sortExpression)
Expression would be assumed to be an Expression<Func<TEntity, T>> where TEntity is the same object being sorted on, and T is a type that needs to be determined in order to create the new IQueryable.
I've found many examples of extensions that take a string, including Dynamic Linq, like this:
public static IQueryable<TEntity> OrderBy<TEntity>(
this IQueryable<TEntity> source, string sortExpression)
If it's possible to take the string and use Reflection to look up the type from the object in question, it should also be possible to take the Expression, and get the value type which is right there IN the Expression.
Following is a detailed explanation of why I'd like to have this, which you may or may not need.
I have a rather large list of complex records to sort. Because the list is so long, I prefer to have the sorting done on the database side. To handle more complex properties, I've created Expressions that provide the sorting functionality, like so:
if (model.sortExpression == "PlannedValue")
{
Expression<Func<BDopp, decimal>> sorter = BDopp.PlannedValueSorter;
if (model.sortDirection == "DESC")
opps = opps.OrderByDescending(sorter).AsQueryable();
else
opps = opps.OrderBy(sorter).AsQueryable();
}
BDOpp.PlannedValueSorter retrieves a static expression from the object which allows sorting to be done without opps are still of type IQueryable:
public static Expression<Func<BDopp, decimal>> PlannedValueSorter
{
get
{
return z => z.BudgetSchedules
.Where(s => s.Type == 1)
.Sum(s => s.Value * s.Workshare * z.valueFactor / 100 / 100);
}
}
Sorting for simple properties is done with Extension methods that use Reflection to build an expression based on the name of the property passed as a string.
This works well, but for the complex types, I still need branching logic, and I'd rather not do that. What I'd rather do is check for a static property containing the expression, and then simply apply it. I can get the expression like this:
PropertyInfo info = typeof(BDopp).GetProperty(model.sortExpression + "Sorter",
BindingFlags.Static | BindingFlags.Public);
Expression expr = (Expression)info.GetValue(null, null);
For the PlannedValue property, this gets me the expression sorted in PlannedValueSorter, which I already know works.
Update:
Various digging around has gotten me what I think might be some progress:
public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source,
Expression<Func<TEntity, dynamic>> sortExpression)
{
var unary = sortExpression.Body as UnaryExpression;
Type actualExpressionType = unary.Operand.Type;
actualExpressionType is in fact the return type of the Expression (for this particular property, it's decimal).
Unfortunately I'm mostly just working by trial and error, since I haven't yet wrapped my brain around how all this works, so my attempt to update the query like so is not working:
MethodCallExpression resultExp = Expression.Call(typeof(Queryable),
"OrderBy",
new Type[] { typeof(TEntity), actualExpressionType },
source.Expression, sortExpression);
return source.Provider.CreateQuery<TEntity>(resultExp);
It compiles okay, but the following error is thrown at runtime:
No generic method 'OrderBy' on type 'System.Linq.Queryable' is
compatible with the supplied type arguments and arguments. No type
arguments should be provided if the method is non-generic.
As I understand it you have an Expression and want to order/filter by it. It might surprise you how simple it is:
Expression<Func<TEntity, T>> sortExpr = ...; //you already have this
var ordered = myQuery.OrderBy(sortExpr);
OrderBy always uses an Expression so you can directly use it. If you only have a variable of type Expression (which points to an object of type Expression<Func<TEntity, T>>) and don't know statically what T is, you can do this:
Expression sortExpr = ...; //you already have this
var ordered = myQuery.OrderBy((dynamic)sortExpr);
dynamic will figure this out at runtime using reflection. No need to do reflection yourself. All that is needed here is overload resolution (performed by the C# runtime binder).
Okay, I've got a solution:
public static IQueryable<TEntity> OrderBy<TEntity>(
this IQueryable<TEntity> source,
Expression<Func<TEntity, dynamic>> sortExpression,
bool descending)
{
var unary = sortExpression.Body as UnaryExpression;
var operand = unary.Operand;
Type actualExpressionType = operand.Type;
MethodCallExpression resultExp =
Expression.Call(typeof(Queryable),
descending? "OrderByDescending" : "OrderBy",
new Type[] { typeof(TEntity), actualExpressionType },
source.Expression,
Expression.Lambda(operand, sortExpression.Parameters));
return source.Provider.CreateQuery<TEntity>(resultExp);
}
The bool descending is to allow for the standard OrderBy and OrderByDescending overloads. Two major breakthroughs here, at least for me:
Getting the Operand out of the expression.
Using Expression.Call and Expression.Lambda to create the new expression - this allows me to use an actual "Type" variable, whereas the Expression<Func<TEntity, T>> syntax requires you to use a type that's known at compile time.
Try this:
public static IEnumerable<T> OrderBy<T>(
this IEnumerable<T> collection,
string columnName
//, SortDirection direction
)
{
ParameterExpression param = Expression.Parameter(typeof(T), "x"); // x
Expression property = Expression.Property(param, columnName); // x.ColumnName
Func<T, object> lambda = Expression.Lambda<Func<T, object>>( // x => x.ColumnName
Expression.Convert(property, typeof(object)),
param)
.Compile();
Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>> expression = (c, f) => c.OrderBy(f); // here you can use OrderByDescending basing on SortDirection
IEnumerable<T> sorted = expression(collection, lambda);
return sorted;
}
Usage:
IEnumerable<T> collection = ...
IEnumerable<T> ordered = collection.OrderBy("PropName");
See Code Project and sandbox.

Categories