[question]
Could you tell me how to create the appropriate expression tree?
[detail]
I created following query statically, and result is as expected.
// Datasource
string[] dataSource = { "John", "Ken", "Mark", "Mary", "Tom" };
// Keyword for Search
string[] keywords = { "o", "Mark", "Tom" };
//LINQ Query (Static)
var query = dataSource.AsQueryable().
Where(item =>
(item.ToLower().Contains(keywords[0].ToLower()) ||
item.ToLower().Contains(keywords[1].ToLower())) &&
item.ToLower().Contains(keywords[2].ToLower())).
OrderByDescending(item => item);
//Result
// "Tom"
condition A || condition B && condition C
but I do not know how to code the following condition with expression tree
(condition A || condition B) && condition C
Does anyone tell me how to use the parethesis in the expression tree?
So far what I created the body for lambda expression is as follows which does not work well.
public static Expression GetContainsExpression(Expression parameter, string keyword, Expression curBody)
{
var keywordValue = Expression.Constant(keyword, typeof(string));
var newBody =
Expression.Equal( Expression.Call(parameter, ToLower), Expression.Call(keywordValue, ToLower) );
///Connect curBody Expression and newBody Expression with "Or" e.s. ||
if (curBody != null)
{
if (keyword == "Tom")
{
return Expression.AndAlso(curBody, newBody);
}
else
return Expression.OrElse(curBody, newBody);
}
return newBody;
}
The parethesis are created automaticaly. You can't avoid it. Expression.OrElse or Expression.AndAlso takes an other expression for left and right and if they are combined expressions as BinaryExpression is they are wrapped in parethesis automatically.
Have a look at this code:
var paramX = Expression.Parameter(typeof(bool), "x");
var paramY = Expression.Parameter(typeof(bool), "y");
var paramZ = Expression.Parameter(typeof(bool), "z");
var expr = Expression.AndAlso(Expression.OrElse(paramX, paramY), paramZ);
If you call expr.ToString() you'll get "((x OrElse y) AndAlso z)". Even the outer AndAlso-Expression is wrapped into parethesis. There is no way to remove them (as I know so far).
A small hint: You can call ToString() on every expression and it will return the created code. Knowing this makes it easier to create dynamic expressions because you've a small ability to see what you get.
Related
I'm using .Net Core and expression trees.
I have a Product class, where LstProp property contains list of values separated by ';', like "val1;val2;val3;":
public class Product
{
// actually contains list of values separated by ';'
public string LstProp{get; set;}
}
And I want to filter Products by this property and contains any condition using expression trees.
I've try this, but it doesn't work.
var value="val1;val2"
var productItem = Expression.Parameter(typeof(Product), "product");
var prop = Expression.Property(productItem, "LstProp");
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var values = value.ToString().Split(';');
Expression predicate = null;
foreach (var val in values)
{
var listPropExpression = Expression.Constant(val);
var containsExpresion=Expression.Call(listPropExpression, method, property);
predicate = predicate != null ? Expression.Or(predicate, containsExpresion) : containsExpresion;
}
So I'm trying to combine call of Contains function for each value in the list, but getting error about "No conversion between BinaryExpression and MethodCallExpression".
How could I combine multiple method calls with expression trees?
I have found solution.
I got two problems:
Incorrect order of parameters.
Should be Expression.Call(property, method, listPropExpression); instead Expression.Call(listPropExpression, method, property);
Main problem was solved with simple cast:
predicate = predicate != null ? (Expression) Expression.Or(predicate, containsExpresion) : containsExpresion;
As a Result I get expression like product=>product.LstProp.Contains("val1") Or product.LstProp.Contains("val2")
I have a request query using linq. The query has multiple where clause say return list of items matching name and city.
Below is the piece of code I used for multiple where clause, but it returns empty set of items.
wherefield contains list of field names like name;city
wherefieldValue contains list of field values like james;delhi
var where = FilterLinq<T>.GetWherePredicate(wherefield, wherefieldvalue).Compile();
items = items.Where(where).OrderByDescending(a => a.GetType().GetProperty(field).GetValue(a, null)).Skip
public class FilterLinq<T>
{
public static Expression<Func<T, Boolean>> GetWherePredicate(string whereFieldList, string whereFieldValues)
{
//the 'IN' parameter for expression ie T=> condition
ParameterExpression pe = Expression.Parameter(typeof(T), typeof(T).Name);
//combine them with and 1=1 Like no expression
Expression combined = null;
if (whereFieldList != null)
{
string[] field = whereFieldList.Split(';');
string[] fieldValue = whereFieldValues.Split(';');
for (int i = 0; i < field.Count(); i++)
{
//Expression for accessing Fields name property
Expression columnNameProperty = Expression.Property(pe, field[i]);
//the name constant to match
Expression columnValue = Expression.Constant(fieldValue[i]);
//the first expression: PatientantLastName = ?
Expression e1 = Expression.Equal(columnNameProperty, columnValue);
if (combined == null)
{
combined = e1;
}
else
{
combined = Expression.And(combined, e1);
}
}
}
//create and return the predicate
return Expression.Lambda<Func<T, Boolean>>(combined, new ParameterExpression[] { pe });
}
}
Your example works. Sure it works only for string properties, but i assume, that is your use case.
Perhaps it doesn't do what you want to achieve, becasue you combine your clause with and, but actually you do want to do it with Or, jus change this code line :
combined = Expression.And(combined, e1);
To
combined = Expression.Or(combined, e1);
Given x => x.LastName,
How do I convert something like .Where({"Doe", "Don", "Donna"}.Contains(x.LastName))?
I need to convert this .Contains Expression into
.Where(x => x.LastName == "Doe" || x.LastName == "Don" || x.LastName == "Donna")
So basically given an array {"Doe", "Don", "Donna"} and a Member Expression x.LastName, how do I dynamically build a valid BinaryExpression as above?
Okay so a little background, I am trying to build a LINQ interface to a NoSQL database that has no idea how to handle an Enumerable.Contains MemberCallExpression. So I am trying to translate that Enumerable.Contains into a simple OrElse Expression that the Database can handle.
I can get x.LastName from the MemberCallExpression's Arguments[0], and I have figured out how to get an Enumerable of Constants from the Expression, that I have been able to build a List<BinaryExpression> out of, by enumerating the Constants and saying
Expressions.Add(Expression.Equal(node.Arguements[0], Expression.Constant(item)));
How do I take that list of BinaryExpressions and build a valid BinaryExpression of the form Expressions[0] OrElse Expressions[1] OrElse Expressions[2].
I tried:
BinaryExpression expression = Expressions[0];
for (var idx = 1; idx < Expressions.Count - 1; idx++)
{
expression += Expression.OrElse(Expressions[idx], Expressions[idx +1]);
}
However += is not valid on a BinaryExpression. And I am unsure how to actually append another Binary Expression onto an existing BinaryExpression...
I'll leave my previous answer for future reference, because I think Expression construction is not easy so that example may be useful for someone. As to the problem, chaining expressions is quite simple. you should use the Expression.OrElse method for that:
BinaryExpression expression = Expressions[0];
for (var idx = 1; idx < Expressions.Count - 1; idx++)
{
expression = Expression.OrElse(expression, Expressions[idx]);
}
string[] arr = {"Doe", "Don", "Donna"};
BinaryExpression exp = null;
MemberExpression member = ... //get the "x.LastName" expression
foreach (String name in arr) {
BinaryExpression eq = Expression.Equal(member, name);
if (exp == null) {
exp = eq;
}
else {
exp = Expression.OrElse(exp, eq);
}
}
Type delegateType = typeof(Func<T, bool>); //T is your entity type, e.g. Person
ParameterExpression arg = ... //the "x" in "x.LastName"
//construct the lambda expression x => [expr].
LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);
then, you can feed that to .Where():
.Where(lambda);
I wrote an extension method that makes an expression based on condition, but when the condition is groupby, the result is order by !!!
what am I wrong?
here is my method:
public static IQueryable<T> NewWhere<T, U>(this IQueryable<T> source, string prop, U value, string condition)
{
MethodInfo method;
Expression<Func<T, bool>> lambda = null;
Expression body = null;
string groupSelector = null;
var type = typeof(T);
var parameter = Expression.Parameter(type, "p");
var property = Expression.Property(parameter, prop);
var constant = Expression.Constant(value, typeof(U));
if (condition == "GreaterThan")
body = Expression.GreaterThan(property, constant);
else if (condition == "LessThan")
body = Expression.LessThan(property, constant);
else if (condition == "Equals")
body = Expression.Equal(property, constant);
//For implement sql like command we need them
else if (condition == "StartsWith") {
method = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
body = Expression.Call(property, method, constant);
}
else if (condition == "EndsWith")
{
method = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });
body = Expression.Call(property, method, constant);
}
else if (condition == "Contains")
{
method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
body = Expression.Call(property, method, constant);
}
//For implement sql like command we need them
//group by only one field
if (condition == "GroupBy")
groupSelector = prop;
else
lambda = Expression.Lambda<Func<T, bool>>(body, new[] { parameter });
//return the grouped by result or not grouped by
if (groupSelector != null)
{
var selectorExp = Expression.Lambda<Func<T, U>>(property, new ParameterExpression[] { parameter });
source = source.GroupBy(selectorExp).SelectMany(g => g);
//source.GroupBy(e => e.GetType().GetProperty(groupSelector)).SelectMany(gr => gr);
}
else
source = source.Where(lambda);
return source;
}
but when i run the mthod with GroupBy condition, the result is:
SELECT [e].[Id], [e].[Year]
FROM [org].[Data] AS [e]
ORDER BY [e].[Year]
i don't know why its happened?
TL;DR;: Entitity Framework uses the query because it is the most efficient way to get what you want.
What Entity Framework does is to translate your LINQ query into SQL. Check this line of your code:
source = source.GroupBy(selectorExp).SelectMany(g => g);
You are grouping (probably by year) and then you select all the items of the group. You are actually not requesting a grouped result set, you are expecting all items in all groups in a single flat result set. If EF would first request the groups and then request the group items, it would first have to select all groups:
SELECT [e].[Year]
FROM [org].[Data] AS [e]
GROUP BY [e].[Year]
Then it would have to get the group items in one query for each group:
SELECT [e].[Id]
FROM [org].[Data] AS [e]
WHERE [e].[Year] = --Value
That of course would be very inefficient (especially since you flatten the list anyway with SelectMany), so EF will just get the values ordered by your grouping predicate and group them during query execution (or in this case not group them at all, since you are requesting a flat list). Once the query is executed, EF can just start at the top of the result set and start a new group every time it encounters a new year.
When you are using EF queries, you have to accept that you won't have control over your SQL. If you want that, create stored procedures and run them from EF.
I want to accept a string array of where conditions from the client like field == value.
It would be really nice to create a specification object that could accept the string in the constructor and output a lambda expression to represent the Where clause. For example, I could do the following:
var myCondition = new Specification<Product>( myStringArrayOfConditions);
var myProducts = DB.Products.Where( myCondition);
How could you turn "name == Jujyfruits" into DB.Products.Where(p => p.name == "JujyFruits")?
You can use
Reflection to get the Property Product.name from the string name and
the LINQ Expression class to manually create a lambda expression.
Note that the following code example will only work for Equals (==) operations. However, it is easy to generalize to other operations as well (split on whitespace, parse the operator and choose the appropriate Expression instead of Expression.Equal).
var condition = "name == Jujyfruits";
// Parse the condition
var c = condition.Split(new string[] { "==" }, StringSplitOptions.None);
var propertyName = c[0].Trim();
var value = c[1].Trim();
// Create the lambda
var arg = Expression.Parameter(typeof(Product), "p");
var property = typeof(Product).GetProperty(propertyName);
var comparison = Expression.Equal(
Expression.MakeMemberAccess(arg, property),
Expression.Constant(value));
var lambda = Expression.Lambda<Func<Product, bool>>(comparison, arg).Compile();
// Test
var prod1 = new Product() { name = "Test" };
var prod2 = new Product() { name = "Jujyfruits" };
Console.WriteLine(lambda(prod1)); // outputs False
Console.WriteLine(lambda(prod2)); // outputs True
About the constructor thing: Since Func<T, TResult> is sealed, you cannot derive from it. However, you could create an implicit conversion operator that translates Specification<T> into Func<T, bool>.
We've recently discovered the Dynamic LINQ library from the VS2008 sample projects. Works perfectly to turn string based "Where" clauses into expressions.
This link will get you there.
You need to turn your search term into a predicate. Try something like the following:
string searchString = "JujyFruits";
Func<Product, bool> search = new Func<Product,bool>(p => p.name == searchString);
return DB.Products.Where(search);