How to set the context of a Dynamic Expression - c#

I am trying to build dynamic expression which I don't know its structure at compile time but I know how to parse it
var x = Expression.Parameter(typeof(T), "x");
var lambda = DynamicExpressionParser.ParseLambda(new[] { x }, typeof(bool), expression);
Where expression is a runtime string that can be for example
x.Id > 1
Now I have a more complex task I want to achieve
x => ListOfIds.Contains(x.Id)
I want to bind a value to the ListOfIds which I don't know how or where it will be available in the expression since it's dynamic and the function will only accept one argument (basically I want to feed the expression to a Linq where method).

Are you using System.Linq.Dynamic.Core?
It seems that you are looking for their Substitution Values functionality, in order to provide an object as a constant expression to the lambda that is generated.
The following code should work for you:
var x = Expression.Parameter(typeof(T), "x");
var constantExpressions = new Dictionary<string, object>
{
{"ListOfIds", ListOfIds}
};
var lambda = DynamicExpressionParser.ParseLambda(new[] { x }, typeof(bool), "ListOfIds.Contains(x.Id)", constantExpressions);

You can use this from Github https://github.com/PoweredSoft/DynamicLinq
Or Nuget https://www.nuget.org/packages/PoweredSoft.DynamicLinq/
This class library allows you to do
var ageGroup = new List<int>() { 28, 27, 50 };
Persons.AsQueryable().Query(t => t.In("Age", ageGroup));
You can take a look at https://github.com/PoweredSoft/DynamicLinq/blob/master/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs#L234 if you wish to see the source code taking care of it.
Hope this can help

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.

Linq extending Expressions

I'm trying to write a generic wildcard Search for the ServiceStack.OrmLite.SqlExpressionVisitor that has the following signature:
public static SqlExpressionVisitor<T> WhereWildcardSearch<T> (this SqlExpressionVisitor<T> ev, Expression<Func<T,string>> field, string search)
where ev is the rest of the filter, field is the getter for the field to search by and search is the entered term.
Normally (non-generic) I would write the following:
if(search.StartsWith('*') && search.EndsWith('*'))
ev = ev.Where(x => x.foo.Contains(search.Trim('*')));
and of course also variants for x.foo.StartsWith or EndsWith.
Now I am searching for something like (pseudocode:)
ev = ev.Where(x => field(x).Contains(search.Trim('*')));
Of course I can't compile and call the expression directly, as this should be translated to Sql using Linq2Sql.
This is my code so far:
var getFieldExpression = Expression.Invoke (field, Expression.Parameter (typeof (T), "getFieldParam"));
var searchConstant = Expression.Constant (search.Trim('*'));
var inExp = Expression.Call (getFieldExpression, typeof(String).GetMethod("Contains"), searchConstant);
var param = Expression.Parameter (typeof (T), "object");
var exp = Expression.Lambda<Func<T, bool>> (inExp, param);
ev = ev.Where (exp);
Please don't tell me that I should directly write SQL with $"LIKE %search%" or something - I know that there are other ways, but solving this would help my understanding of Linq and Expressions in general and it bugs me when I can't solve it.
Here is how it can be done (I think it will be clear for you without much additional explanations what you did wrong, but if not - feel free to request a clarification):
// extract property name from passed expression
var propertyName = ((MemberExpression)field.Body).Member.Name;
var param = Expression.Parameter(typeof(T), "object");
var searchConstant = Expression.Constant(search.Trim('*'));
var contains = typeof(String).GetMethod("Contains");
// object.FieldName.Contains(searchConstant)
var inExp = Expression.Call(Expression.PropertyOrField(param, propertyName), contains, searchConstant);
// object => object.FieldName.Contains(searchConstant)
var exp = Expression.Lambda<Func<T, bool>>(inExp, param);
In response to comment. You have two expression trees: one is being passed to you and another one which you are building (exp). In this simple case they both use the same number of parameters and those parameters are of the same type (T). In this case you can reuse parameter from field expression tree, like this:
// use the same parameter
var param = field.Parameters[0];
var searchConstant = Expression.Constant(search.Trim('*'));
var contains = typeof(String).GetMethod("Contains");
// note field.Body here. Your `field` expression is "parameter => parameter.Something"
// but we need just "parameter.Something" expression here
var inExp = Expression.Call(field.Body, contains, searchConstant);
// pass the same parameter to new tree
var exp = Expression.Lambda<Func<T, bool>>(inExp, param);
In more complicated cases you might need to use ExpressionVisitor to replace parameters in one expression tree to reference to parameters from another (final) expression tree.

LINQ Dynamic Query using Expression Tree

I am trying to familiarize myself with Expression Trees, and I'm hitting a wall. I want to be able to dynamically create LINQ to XML queries, so I am trying to familiarize myself with Expression Trees. I started with a simple LINQ to XML statement that I want to be able to generate dynamically:
// sample data
var sampleData = new XElement("Items",
new XElement("Item", new XAttribute("ID", 1)),
new XElement("Item", new XAttribute("ID", 2)),
new XElement("Item", new XAttribute("ID", 3))
);
// simple example using LINQ to XML (hard-coded)
var resultsStatic = from item in sampleData.Elements("Item")
where item.Attribute("ID").Value == "2"
select item;
// trying to recreate the above dynamically using expression trees
IQueryable<XElement> queryableData = sampleData.Elements("Item").AsQueryable<XElement>();
ParameterExpression alias = Expression.Parameter(typeof(XElement), "item");
MethodInfo attributeMethod = typeof(XElement).GetMethod("Attribute", new Type[] { typeof(XName) });
PropertyInfo valueProperty = typeof(XAttribute).GetProperty("Value");
ParameterExpression attributeParam = Expression.Parameter(typeof(XName), "ID");
Expression methodCall = Expression.Call(alias, attributeMethod, new Expression[] { attributeParam });
Expression propertyAccessor = Expression.Property(methodCall, valueProperty);
Expression right = Expression.Constant("2");
Expression equalityComparison = Expression.Equal(propertyAccessor, right);
var resultsDynamic = queryableData.Provider.CreateQuery(equalityComparison);
The error that I get when calling CreateQuery is 'Argument expression is not valid'. The debug view for equalityComparison shows '(.Call $item.Attribute($ID)).Value == "2"'. Can someone identify what I am doing incorrectly?
To better understand what's going on, always start with method syntax of the desired query. In your case it is as follows (I'm specifically including the types although normally I would use var):
IQueryable<XElement> queryableData = sampleData.Elements("Item").AsQueryable();
IQueryable<XElement> queryStatic = queryableData
.Where((XElement item) => item.Attribute("ID").Value == "2");
Now let see what you have.
First, the attributeParam variable
ParameterExpression attributeParam = Expression.Parameter(typeof(XName), "ID");
As you can see from the static query, there is no lambda parameter for attribute name - the only supported (and required) parameter is the item (which in your code is represented by the alias variable). So this should be ConstantExpression of type XName with value "ID":
var attributeParam = Expression.Constant((XName)"ID");
Second, the equalityComparison variable. All it contains is the item.Attribute("ID").Value == "2" expression. But Where method requires Expression<Func<XElement, bool>>, so you have to create such using the equalityComparison as body and alias as parameter:
var predicate = Expression.Lambda<Func<XElement, bool>>(equalityComparison, alias);
Finally you have to call Where method. You can do that directly:
var queryDynamic = queryableData.Where(predicate);
or dynamically:
var whereCall = Expression.Call(
typeof(Queryable), "Where", new Type[] { queryableData.ElementType },
queryableData.Expression, Expression.Quote(predicate));
var queryDynamic = queryableData.Provider.CreateQuery(whereCall);
You can take a look at the used Expression methods documentation for further details what they do.
Linq to XML works in memory, meaning you do not nead Expression trees, just use the Enumerable extension methods. Makes your code much simplier and easier to read!!!

LINQ: Why does Expression.Call() reference the Db when CreateQuery() does as well?

So I'm trying to optimize a query that does a text search on 1000s of rows and displays 100s of them. To do so, I'm trying to understand some code from Microsoft's 101 Sample Queries (found here).
Here is the code I wish to understand:
[Category("Advanced")]
[Title("Dynamic query - Where")]
[Description("This sample builds a query dynamically to filter for Customers in London.")]
public void LinqToSqlAdvanced02()
{
IQueryable<Customer> custs = db.Customers;
ParameterExpression param = Expression.Parameter(typeof(Customer), "c");
Expression right = Expression.Constant("London");
Expression left = Expression.Property(param, typeof(Customer).GetProperty("City"));
Expression filter = Expression.Equal(left, right);
Expression pred = Expression.Lambda(filter, param);
Expression expr = Expression.Call(typeof(Queryable), "Where", new Type[] { typeof(Customer) }, Expression.Constant(custs), pred);
IQueryable<Customer> query = db.Customers.AsQueryable().Provider.CreateQuery<Customer>(expr);
ObjectDumper.Write(query);
}
So...I realize this is esoteric, but why does Expression.Call() need the DbSet reference it's passed to CreateQuery as <T>?
I believe what you're asking is why the third parameter (array of types) is needed in Expression.Call because if you did it through code, you'd only have to do this:
custs.Where(pred);
The reason why the code works without the generic parameter is because of implicit typing, where the compiler translates that automatically to:
custs.Where<Customer>(pred);
After it translates it, the actual bytecode contains the call with the generics specified. When you build the Expression, you don't have all the niceties like implicit typing, so you have to specify exactly what gets called.

Create fully dynamic where clause with expression tree and execute on IQueryable

At point (3) in my code I have defined a query called query1 in which I defined a .Where lambda expression. This query is in some way dynamic but still contains static elements, it always refers to the Type Employee and its (int) property ClientID.
Now I very much like to make the refering to the type and its property dynamic, based on the method parameters which by example are shown below point (1).
What I tried to so far is making the static part of the query defined under point (3) fully dynamic by replacing it with a more elaborate expression tree as written down in (4), (5) & (6). But when I try to add everything together it says I call .Where with wrong parameters. I don't know how to call .Where with the right parameters in order to create a fully dynamic select.
Does someone know to solve this problem? I have spent a day searching and haven't found a solution so far.
dsMain domainService = new dsMain();
//(1)i want to rewrite the following four variables to method-parameters
Type entityType = typeof(Employee);
String targetProperty = "ClientID";
Type entityProperty = typeof(Employee).GetProperty(targetProperty).PropertyType;
int idToDelete = 5;
//(2)create expression-function: idToDelete == entityType.targetProperty (in this case: Employee.ClientID)
ParameterExpression numParam = Expression.Parameter(entityProperty, targetProperty.Substring(0, 3));
ConstantExpression equalTarget = Expression.Constant(idToDelete, idToDelete.GetType());
BinaryExpression intEqualsID = Expression.Equal(numParam, equalTarget);
Expression<Func<int, bool>> lambda1 =
Expression.Lambda<Func<int, bool>>(
intEqualsID,
new ParameterExpression[] { numParam });
//(3)I want to create query1 fully dynamic, so defining Employee or an other type and its property at run time
WhereClause = lambda1.Compile();
IQueryable<Employee> employees = domainService.GetEmployees();
var query1 = employees.Where<Employee>(C => WhereClause.Invoke(C.ClientID)).Expression;
//(4)create the operand body {value(ASP.test_aspx).WhereClause.Invoke(E.ClientID)}
var operandbodyMethod = WhereClause.GetType().GetMethod("Invoke");
var operandbodyType = typeof(System.Boolean);
var operandbodyArgs1Expression = Expression.Parameter(entityType, entityType.Name.Substring(0, 1));
var operandbodyArgs1 = Expression.MakeMemberAccess(operandbodyArgs1Expression, entityType.GetMember(targetProperty)[0]);
var operandBodyObjectExp = Expression.Constant(this, this.GetType());
var operandbodyObject = Expression.MakeMemberAccess(operandBodyObjectExp, this.GetType().GetMember("WhereClause")[0]);
//(5)create the operand {E => value(ASP.test_aspx).WhereClause.Invoke(E.ClientID)}
var operandbody = Expression.Call(operandbodyObject, operandbodyMethod, operandbodyArgs1);
var operandParameter = Expression.Parameter(entityType, entityType.Name.Substring(0, 1));
var operandType = typeof(Func<,>).MakeGenericType(entityType, typeof(System.Boolean));
//(6)
var operand = Expression.Lambda(operandType, operandbody, new ParameterExpression[] { operandParameter });
var expressionType = typeof(Expression<>).MakeGenericType(operandType);
var completeWhereExpression = Expression.MakeUnary(ExpressionType.Quote, operand, expressionType);
//(7)the line below does not work
var query2 = employees.Where<Employee>(completeWhereExpression).Expression;
Thank you very much for reading my question!
If you have questions about my question, please ask them:)
This is quite hard to look at in isolation, but the first thing that occurs is that Compile looks out of place for IQueryable - that will rarely work (LINQ-to-Objects being the exception).
An equivalent to WhereClause.Invoke(C.ClientID) is to use Expression.Invoke to call a sub-expression, but even this is flakey: LINQ-to-SQL will support it, EF (in 3.5 at least) doesn't (maybe "didn't"; I haven't re-checked in 4.0). Ultimately, it would be more robust to create lambda1 as an Expression<Func<Employee,bool>> if possible:
ParameterExpression empParam = Expression.Parameter(typeof(Employee),"emp");
ConstantExpression equalTarget = Expression.Constant(idToDelete, idToDelete.GetType());
BinaryExpression intEqualsID = Expression.Equal(
Expression.PropertyOrField(empParam, targetProperty), equalTarget);
Expression<Func<Exmployee, bool>> lambda1 =
Expression.Lambda<Func<int, bool>>(
intEqualsID,
empParam);
Then pass this to Where:
var query1 = employees.Where(lambda1);

Categories