Dynamic linq query c# - 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!

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

Create a lambda expression containing a new expression with an anonymous type

How to build a lambda expression for an Entity Framework query dynamically (e.g. at API level) that contains a NEW expression with an anonymous type. The members of the anonymous type (Item0, Item1, ...) are only known at runtime?
An example:
Orders
.GroupBy(param0 => param0.Products)
.Select(param0 => new
{
Item0 = param0.Where(param1 => param1.Id == 10).Select(param1 => param1.Value),
Item1 = param0.Where(param1 => param1.Id == 11).Select(param1 => param1.Description)
}
)

StackOverflowException using Linq(Kit) for nested data

I'm trying to build a nested query using Linq/LinqKit. In theory this seems to be easy. But I am stuck with the practical part.
In my database I have a table which has a self-reference to its parent. In my linq-query I now want to select all parents of a given element (and the parents of this one and so on).
In my code I have the following expression in partial class of MyTable:
public static Expression<Func<MyTable, IEnumerable<MyTable>>> Parents => (entity) => entity.ParentId != null ? new[]{entity.ParentEntity}.Union(Parents.Invoke(entity.ParentEntity) : new MyEntity[]{};
which should select the parent of a given entity and those parents when the ParentId is set.
The query itself (simplified):
dbContext
.MyTable
.AsExpandable()
.Where(x => x.Id == myId)
.Select(x => new
{
Parents = MyTable.Parents.Invoke(x, dbContext)
});
Running this code ends up in an StackOverflowException as the stop-condition is not hit and therefore the Parents-call is nested endlessly until the stack is full.
Any ideas how this can be done or is this not possible? Or is there an other way for fetching nested data using Linq/LinqKit within one query?
I already tried passing the context to the expression in order to create a sub-query (also not working):
public static Expression<Func<MyTable, MyContext, IEnumerable<MyTable>>> Parents => (entity, dbContext) => entity.ParentId != null ? new[]{entity.ParentEntity}.Union(Parents.Invoke(dbContext.MyTable.FirstOrDefault(x => x.Id == entity.ParentId), dbContext) : new MyEntity[]{};
As mentioned in comments, currently it's not possible to create a recursive expandable (i.e. non invokable) expression.
However, if you can limit the maximum depth, one possible solution would be to build expression like this (utilizing the EF navigation property):
Parents = new MyTable [] { x.Parent, x.Parent.Parent, x.Parent.Parent.Parent, ...}
.Where(e => e != null)
dynamically:
static Expression<Func<MyTable, IEnumerable<MyTable>>> ParentsSelector(int maxLevels)
{
var parameter = Expression.Parameter(typeof(MyTable), "x");
var parents = new Expression[maxLevels];
for (int i = 0; i < parents.Length; i++)
parents[i] = Expression.Property(i > 0 ? parents[i - 1] : parameter, "Parent");
Expression<Func<MyTable, bool>> predicate = x => x != null;
var result = Expression.Call(
typeof(Enumerable), "Where", new[] { parameter.Type },
Expression.NewArrayInit(parameter.Type, parents), predicate);
return Expression.Lambda<Func<MyTable, IEnumerable<MyTable>>>(result, parameter);
}
and use it as follows:
var parents = ParentsSelector(10);
var query = dbContext.MyTable
.AsExpandable()
.Where(x => x.Id == myId)
.Select(x => new
{
Parents = parents.Invoke(x)
});

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

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