GroupBy query by Linq.Expressions and Lambdas - c#

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);

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;
}

Iterating over properties and use each one in a lambda expression

I am trying to simplify my code a bit by iterating over the columns in a table and use each column to map to a value instead of having multiple lines of code with a single different value.
So going from this:
foreach(var n in groupedResults){
SetCellForData("Column1", n.Sum(x => x.ColumnName1FromTable));
SetCellForData("Column2", n.Sum(x => x.ColumnName2FromTable));
...
SetCellForData("Column10", n.Sum(x => x.ColumnName10FromTable));
}
To something like this:
var columnProperties = typeof(TableClass).GetProperties().Select(t => t);
foreach(var n in groupedResults){
foreach(var columnProperty in columnProperties ){
SetCellForData(columnProperty.Name, n.Sum(x => x.????);
}
}
Where the ???? part uses the columnProperty to Sum the column in the n grouped result.
Since arekzyla decided to delete his answer before I could confirm that it was what I needed then I have decided to answer my own question with arekzyla's answer.
This method creates the function:
private static Func<T, U> GetPropertyFunc<T, U>(string propertyName)
{
var parameter = Expression.Parameter(typeof(T));
var body = Expression.PropertyOrField(parameter, propertyName);
var lambda = Expression.Lambda<Func<T, U>>(body, parameter);
return lambda.Compile();
}
This code iterates over the properties in the database type and creates a function for each of them returning an integer.
var columnProperties = typeof(DatabaseType).GetProperties()
.Where(x => x.PropertyType == typeof(int))
.Select(t => new { t.Name, GetPropertyFunc = GetPropertyFunc<DatabaseType, int>(t.Name) })
.ToList();
Then we iterate over some grouped list:
foreach (var n in groupedList)
{
foreach (var property in columnProperties)
{
var sumOfElementsInGrouping = n.Sum(x => property.GetPropertyFunc(x));
}
}
Then we have the sum of the specific column based on the property.

Chaining IQueryable expression trees

I want to be able to add ordering to my queries dynamically:
Expression<Func<IQueryable<MyEntity>, IOrderedQueryable<MyEntity>>> order1 = e => e.OrderBy(x => x.Weight);
Expression<Func<IQueryable<MyEntity>, IOrderedQueryable<MyEntity>>> order2 = e => e.OrderByDescending(x => x.Weight).ThenBy(x => x.Price);
Expression<Func<IQueryable<MyEntity>, IOrderedQueryable<MyEntity>>> order3 = e => e.OrderBy(x => x.Category).ThenBy(x => x.Price);
IQueryable<MyEntity> query = any EF query;
var transformedQuery = query.Transform(order1/order2/order3);
How do I implement Transform() ?
public static IQueryable<T> Transform<T>(this IQueryable<T> query, Expression<Func<IQueryable<T>, IOrderedQueryable<T>>> orderExpr)
{
// ??????????????????
}
My problem is that I don't want to have 2 overloads for ascending/descending ordering. I need to apply whatever OrderBy()/OrderByDescending() expression or their combination is passed.
This is one case where you don't need an expression tree. Or even a helper method.
Func<IQueryable<MyEntity>, IOrderedQueryable<MyEntity>> order = e => e.OrderBy(x => x.Weight);
IQueryable<MyEntity> query = any EF query;
var transformedQuery = order(query);
This works because when order is passed in a query, it can itself call the appropriate Queryable.OrderBy overload that takes an expression tree.

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!

Expression tree nested wheres

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)));

Categories

Resources