Expression tree nested wheres - c#

I am trying to dynamically build an expression tree that queries a datasource effectively using an in query. The query i am trying to replicate is
Countries.Where(y => Countries
.Where(x =>
x.CountryLanguage.Any(b => b.CountryID == 73) &&
x.CountryLanguage.Any(b => b.CountryID == 150))
.Select(z => z.ShortCode)
.Contains(y.ShortCode))
I have tried many ways of doing this, but this is my latest attempt:
public void AddContainsWhereClause(IQueryable<T> objectSet, string predicateIdentifier)
{
ParameterExpression pe = Expression.Parameter(typeof(T), predicateIdentifier);
Expression expInner = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { typeof(T) },
objectSet.Expression,
Expression.Lambda<Func<T, bool>>(rootExperession, resultExpression));
Expression expOuter = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { typeof(T) },
objectSet.Expression,
Expression.Lambda<Func<T, bool>>(expInner, pe));
}
NB rootExpression is:
x => x.CountryLanguage.Any(b => b.CountryID == 73) &&
x.CountryLanguage.Any(b => b.CountryID == 150)
But this returns:
[ApplicationFramework.LINQBuilder.tests.Country]' cannot be used for return type 'System.Boolean'
Does anyone know what i am doing wrong?

I'm assuming that what you want is a lambda function to emulate the predicate component of your Where call.
A where clause needs to be of type Func<TSource, bool>, but you're calling Queryable.Where, which actually returns an IEnumerable.
Rather that going down the expression tree path, which can be pretty complex and hard to maintain, is your real problem just that you need to select a list of countries which support the languages of a supplied country list?
int[] requiredCountryIds = {73, 150};
// Select countries which contain all required country IDs in their CountryLanguage set
var resultSet =
countries.Where(
y => requiredCountryIds.All(requiredCountryId => y.CountryLanguage.Any(b => b.CountryId == requiredCountryId)));

Related

Chaining lambda expressions by dot

I have two expressions and I want to chain them so the resulting expression contains both input expressions.
Expression<Func<IQueryable<Material>, object>> expression1 = x => x.Include(m => m.MaterialGroup);
Expression<Func<IQueryable<Material>, object>> expression2 = x => x.Include(m => m.MaterialSomething);
var expression3 = expression1.Update(expression2.Body, expression2.Parameters);
Right now expression3 only contains x => x.Include(m => m.MaterialSomething) so it overrides the second expression. I'd like it to be x => x.Include(m => m.MaterialGroup).Include(m => m.MaterialSomething).
What I intend to achieve is to programatically join multiple include expressions in order to be able to build more efficient system for eager loading in EF Core.
EDIT:
This is not a matter of ANDing, ORing etc. because I want these expressions to be chained (like a dot chaining), not logically joined.
Daniel
Because Include is extension method your expression
x => x.Include(m => m.MaterialGroup);
actually is
x => QueryableExtensions.Include(x, m => m.MaterialGroup);
So to chain your expressions you need to replace first argument of Include with call to another Include
x => QueryableExtensions.Include(
QueryableExtensions.Include(x, m => m.MaterialSomething),
m => m.MaterialGroup);
Next code will do this chaining
public static Expression<Func<IQueryable<T>, object>> Chain<T>(
params Expression<Func<IQueryable<T>, object>>[] expressions)
{
if (expressions.Length == 0)
throw new ArgumentException("Nothing to chain");
if (expressions.Length == 1)
return expressions[0];
Expression body = expressions[0].Body;
var parameter = expressions[0].Parameters[0];
foreach (var expression in expressions.Skip(1))
{
var methodCall = (MethodCallExpression)expression.Body;
var lambda = (UnaryExpression)methodCall.Arguments[1];
body = Expression.Call(typeof(QueryableExtensions),
"Include",
new []{ typeof(T), ((LambdaExpression)lambda.Operand).Body.Type},
body, lambda
);
}
return Expression.Lambda<Func<IQueryable<T>, object>>(body, parameter);
}
Usage:
var expression = Chain(expression1, expression2 /*, expression3 .... */);
You can test it online here
Please note that this code skip expression validation for brevity.
I'd like to add another way to archive chaining lambda expressions:
add the follow static method somewhere easy access
public static Expression<Func<T, bool>> ConcatLambdaExpression<T>(Expression<Func<T, bool>> firstExpression, Expression<Func<T, bool>> secondExpression)
{
var invokedThird = Expression.Invoke(secondExpression, firstExpression.Parameters.Cast<Expression>());
var finalExpression = Expression.Lambda<Func<T, bool>>(Expression.AndAlso(firstExpression.Body, invokedThird), firstExpression.Parameters);
return finalExpression;
}
Then you can use it on this way:
public PersonDTO GetAll()
{
Expression<Func<Person, bool>> expression = x => x != null;
expression = x => x.Name == "John";
Expression<Func<Person, bool>> pred = x => x.LastName == "Doe" || x.LastName == "Wick";
//result of expression would be:
////expression = x.Name == "John" && (x => x.LastName == "Doe" || x.LastName == "Wick")
expression = Utilities.ConcatLambdaExpression(expression, pred);
var result = Context.PersonEntity.Where(expression);
//your code mapping results to PersonDTO
///resultMap...
return resultMap;
}

GroupBy query by Linq.Expressions and Lambdas

What I need is to represent this query via Linq.Expressions:
db.Documents.GroupBy(a => 1).Select(b => b.Sum(c => c.Amount) });
Here is what I have so far:
IQueryable<Document> data = db.Documents;
ParameterExpression pe = Expression.Parameter(typeof(Document), "doc");
Expression groupBy = Expression.Call(
typeof(Queryable),
"GroupBy",
new Type[] { typeof(Document), typeof(int) },
data.Expression,
Expression.Lambda(Expression.Constant(1), pe));
ParameterExpression peg = Expression.Parameter(typeof(IGrouping<int, Document>), "group");
Expression select = Expression.Call(
typeof(Queryable),
"Select",
new Type[] { typeof(IGrouping<int, Document>), typeof(int) },
groupBy,
Expression.Lambda(Expression.Property(peg, "Key"), peg));
foreach (var item in data.Provider.CreateQuery(select)) { ... }
This was implementation of:
db.Documents.GroupBy(a => 1).Select(b => b.Key });
And it works perfectly. Now, I want to aggregate a sum instead of accessing the key of group.
That is where it gets tricky for me. I was thinking something like this:
ParameterExpression pe1 = Expression.Parameter(typeof(Document), "other");
Expression sum = Expression.Call(
typeof(Queryable),
"Sum",
new Type[] { typeof(Document) },
peg,
Expression.Lambda(Expression.Property(pe1, "Amount"), pe1));
Also, for Sum function in
...b.Sum(c => c.Amount)
Intellisense gives signature:
IEnumerable<Document>.Sum<Document>(Func<Document, decimal> selector)
While for:
db.Documents.Sum(a => a.Amount)
I get:
IQueryable<Document>.Sum<Document>(Expression<Func<Document, decimal>> selector)
Selector is Func in one version and Expression in other. I don't know how to handle Func in Linq Expressions. Maybe Intellisense is wrong?
Expression for source of aggregation is my biggest issue. By looking at:
...b.Sum(c => c.Amount)
i would presume that b should be IGrouping (ParameterExpression of 'select'), and that should be the source for Sum, but that won't compile.
I don't know what else to try?
Here is how last select expression should look like:
Expression Select = Expression.Call(
typeof(Queryable),
"Select",
new Type[] { typeof(IGrouping<int, Document>), typeof(decimal?) },
GroupBy,
Expression.Lambda(sum, peg));
But I can't even reach this point, because of the failed 'sum' expression.
Any pointers would be appreciated.
Regards,
The Intellisense is ok. Let see:
db.Documents.GroupBy(a => 1).Select(b => b.Sum(c => c.Amount) });
(1) db.Documents type is IQueryable<Document>
(2) a type is Document
(3) db.Documents.GroupBy(a => 1) type is IQueryable<IGrouping<int, Document>>
(4) b type is IGrouping<int, Document>, which in turn is IEnumerable<Document>
(5) c type is Document
which also means that GroupBy and Select methods are from Queryable while Sum is from Enumerable.
What about how to distinguish between Func<...> and Expression<Func<...>> inside the MethodCall expressions, the rule is simple. In both cases you use Expression.Lambda<Func<...>> to create Expression<Func<...>>, and then if the call requires Func<...> you pass it directly, and if the method expects Expression<Func<...>> then you wrap it with Expression.Quote.
With that being said, let build the sample query expression:
var query = db.Documents.AsQueryable();
// query.GroupBy(a => 1)
var a = Expression.Parameter(typeof(Document), "a");
var groupKeySelector = Expression.Lambda(Expression.Constant(1), a);
var groupByCall = Expression.Call(typeof(Queryable), "GroupBy",
new Type[] { a.Type, groupKeySelector.Body.Type },
query.Expression, Expression.Quote(groupKeySelector));
// c => c.Amount
var c = Expression.Parameter(typeof(Document), "c");
var sumSelector = Expression.Lambda(Expression.PropertyOrField(c, "Amount"), c);
// b => b.Sum(c => c.Amount)
var b = Expression.Parameter(groupByCall.Type.GetGenericArguments().Single(), "b");
var sumCall = Expression.Call(typeof(Enumerable), "Sum",
new Type[] { c.Type },
b, sumSelector);
// query.GroupBy(a => 1).Select(b => b.Sum(c => c.Amount))
var selector = Expression.Lambda(sumCall, b);
var selectCall = Expression.Call(typeof(Queryable), "Select",
new Type[] { b.Type, selector.Body.Type },
groupByCall, Expression.Quote(selector));
// selectCall is our expression, let test it
var result = query.Provider.CreateQuery(selectCall);

C# predicate list passed to Linq Where clause

I have a long Linq Where clause that I would like to populate with a predicate list.
List<Expression<Func<Note, bool>>> filters = new List<Expression<Func<Note, bool>>>();
filters.Add(p => p.Title != null && p.Title.ToLower().Contains(searchString));
filters.Add(p => p.Notes != null && p.Notes.ToLower().Contains(searchString));
filters.Add(GlobalSearchUser((List < User > users = new List<User>() { p.user1, p.user2, p.user3, p.user4 }), searchString));
notes = dataAccess.GetList<Note>(pn => pn.ProjectVersionID == projectVersionID, filterExtensions.ToArray())
.Where(filters.ToArray()).Take(10).ToList();
However I'm getting this error:
cannot convert from 'System.Linq.Expressions.Expression<System.Func<project.Contracts.DTOs.Note,bool>>[]' to 'System.Func<project.Contracts.DTOs.Note,bool>'
Which is an error on the .where clause. Pulling out the .where compiles just fine.
I think great answer from Hogan can be simplified and shorten a bit by use of Any and All Linq methods.
To get items that fulfill all the conditions:
var resultAll = listOfItems.Where(p => filters.All(f => f(p)));
And to get the items that fulfill any condition:
var resultAny = listOfItems.Where(p => filters.Any(f => f(p)));
There are at least two errors in your code:
List<Expression<Func<Note, bool>>> filters = new List<Expression<Func<Note, bool>>>();
change it to
List<Func<Note, bool>> filters = new List<Func<Note, bool>>();
You don't need Expression trees here. You are using IEnumerable<>, not IQueryable<>
notes = dataAccess.GetList<Note>(pn => pn.ProjectVersionID == projectVersionID, filterExtensions.ToArray())
.Where(filters.ToArray()).Take(10).ToList();
There .Where() accepts a single predicate at a time. You could:
notes = dataAccess.GetList<Note>(pn => pn.ProjectVersionID == projectVersionID, filterExtensions.ToArray())
.Where(x => filters.All(x)).Take(10).ToList();
or various other solutions, like:
var notesEnu = dataAccess.GetList<Note>(pn => pn.ProjectVersionID == projectVersionID, filterExtensions.ToArray())
.AsEnumerable();
foreach (var filter in filters)
{
notesEmu = notesEmu.Where(filter);
}
notes = notesEnu.Take(10).ToList();
Because all the .Where() conditions are implicitly in &&.
You have to loop over your filters and run a test on each one.
You can do it with linq like this to return true if any of your filters are true:
.Where(p => { foreach(f in filters) if (f(p) == true) return(true); return(false)})
or like this to to return true if all of your filters are true:
.Where(p => { foreach(f in filters) if (f(p) == false) return(false); return(true)})
You can't just pass an array of predicates to the where method. You need to either iterate over the array and keep calling Where() for each expression in the array, or find a way to merge them all together into one expression and use that. You'll want to use LinqKit if you go the second route.

Dynamic linq query c#

I am trying to create a dynamic linq query on IEnumerable<T>. The following query I want to create dynamically:
.OrderByDescending(n => n.TS)
.GroupBy(n => n.PID)
.Select(n => n.First())
.Where(n => !n.IsD)
What I have tried is as under:
var type = typeof(TSource);
ParameterExpression pe = Expression.Parameter(type, "n");
Expression tsExp = Expression.Property(pe, "TS");
MethodCallExpression orderByCallExpression = Expression.Call(
typeof(IEnumerable),
"OrderByDescending",
new Type[] { type.GetElementType() },
tsExp,
Expression.Lambda<Func<IEnumerable<TSource>>>(pe, new ParameterExpression[] { pe }));
MethodCallExpression groupByCallExpression = Expression.Call(
typeof(IEnumerable),
"GroupBy",
new Type[] { type.GetElementType() },
orderByCallExpression,
Expression.Lambda<Func<IEnumerable<TSource>>>(pe, new ParameterExpression[] { pe }));
//Here how to create Select and Where expressions
//The issue with Select is it contains First() function call. How can i make it?
query = query.Provider.CreateQuery<TSource>(groupByCallExpression);
I don't know how much my first try is correct, can anyone please help me out to create this query?
Instead of going all the way long, you can try this:
var data = ((IEnumerable<object>)collection)
.OrderByDescending(c => c.GetType().GetProperty("TS").GetValue(c))
.GroupBy(c => c.GetType().GetProperty("PId").GetValue(c))
.Select(c => c.First())
.Where(c => !(bool)c.GetType().GetProperty("IsD").GetValue(c));
The validation is not present in the code - i assume that the object must have these properties otherwise you will get null reference exception.
Hope it helps!

PredicateBuilder with multiple (OR) and (AND)

I'm trying to create an expression in order to retrieve a Store object that should be on a list of countries and also that their Store.Id = X.
I'm trying to do that with the following expression, but that returns all Stores stored on the database, I don't know what I'm missing.
public Expression<Func<Store, bool>> CreateExpression(List<Country> countries, long storeId)
{
var predicate = PredicateBuilder.False<Store>();
predicate = countries.Aggregate(predicate, (current, p) =>
current.Or(e => e.Country.Id == p.Id));
predicate = predicate.And(e => e.Id == storeId);
return predicate;
}

Categories

Resources