C# Expression Casting as Derived Type - c#

Using
IQueryable<T> query - where T : BaseEntity
I have the following code, used in a generic method - which uses reflection to call the .Where() method (this is working):
var predicate = Expression.Lambda(body, item);
MethodInfo whereCall = (typeof(Queryable).GetMethods().First(mi => mi.Name == "Where" && mi.GetParameters().Length == 2).MakeGenericMethod(query.ElementType));
MethodCallExpression call = Expression.Call(whereCall, new Expression[] { query.Expression, predicate });
query = query.Provider.CreateQuery<T>(call);
I would like to use something like this (and avoid reflection):
var predicate = Expression.Lambda<Func<T, bool>>(body, item);
query = query.Where(predicate);
But the problem with this code is that T is used as the base-type, and not the derived-type at run-time.
How can I cast T as query.ElementType (the derived type) ?

Your second piece of code is indeed better than the first one. You will need to invoke the Expression.Lambda method using reflection somehow. A convenient way to do that is this:
static IQueryable<T> CreateQuery<T>(IQueryable<T> query, ...) {
var predicate = Expression.Lambda<Func<T, bool>>(body, item);
query = query.Where(predicate);
return query;
}
Invoke this method using T as the derived type. You can perform that call using MakeGenericMethod. Depending on your scenario it might be enough to say:
CreateQuery((dynamic)query);

Related

Func<T, bool> on Any() IEnumerable

i try to query (linq to entities EF Core) a navigation properties collection, so i use any() like this :
var query = context.MyTable.Where(x => x.mycollectionproperties.Any(p => p.myprop == myvar );
It's work perfectly but now i want to construct the predicate and not defined it directly in the query.
so i do :
Func<T, bool> mypredicate = (p => p.myprop == myvar);
var query = context.MyTable.Where(x => x.mycollectionproperties.Any(mypredicate);
(I have replace T by my entity name)
but this generate an error : Object of type 'System.Linq.Expressions.TypedParameterExpression' cannot be converted to type 'System.Linq.Expressions.LambdaExpression'.
How can i construct my predicate to use it on Any() collection ?
Thank's
This line for example:
var query = context.MyTable.Where(x => x.mycollectionproperties.Any(p => p.myprop == 1));
When compiled will be compiled to something like this:
var xParameter = Expression.Parameter(typeof(Entity1), "x");
var pParameter = Expression.Parameter(typeof(Entity2), "p");
var anyMethod =
typeof(Enumerable)
.GetMethods()
.Single(x => x.Name == "Any" && x.GetParameters().Length == 2)
.MakeGenericMethod(typeof(Entity2));
var anyCondition = Expression.Lambda<Func<Entity2, bool>>(
Expression.Equal(
Expression.Property(
pParameter,
typeof(Entity2).GetProperty("myprop").GetMethod),
Expression.Constant(1, typeof(int))),
pParameter);
var query = context.MyTable.Where(
Expression.Lambda<Func<Entity1, bool>>(
Expression.Call(
null,
anyMethod,
new Expression[] {
Expression.Property(
xParameter,
typeof(Entity1).GetProperty("mycollectionproperties").GetMethod),
anyCondition
}),
xParameter));
This is called an expression tree. See this reference for more details:
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/expression-trees/
Although the Any method takes a Func, when constructing the expression tree, notice that an expression (Expression<Func<Entity2, bool>>) is given to the Any method.
There doesn't seem to be a way from C# to give the Any method an expression instead of a Func even if the whole thing is an expression tree (I mean in a parameterized way like you want to achieve).
The most obvious way to achieve what you want is to use the code from this post and replace the anyCondition variable with whatever expression you want to use for the condition inside Any.
Another way is to construct part of the expression tree "normally" and pass null to the Any method and then use an expression visitor to replace the null with your expression. Here is how such visitor would look like:
public class AnyMethodArgumentReplacingVisitor : ExpressionVisitor
{
private readonly Expression expression;
public AnyMethodArgumentReplacingVisitor(Expression expression)
{
this.expression = expression;
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.Name == "Any")
{
return Expression.Call(node.Object, node.Method, node.Arguments[0], expression);
}
return base.VisitMethodCall(node);
}
}
Here is how you would use it:
Expression<Func<Entity2, bool>> predicate =
a => a.myprop == 2;
Expression<Func<Entity1, bool>> expression =
b => b.mycollectionproperties.Any(null);
var expression2 =
(Expression<Func<Entity1, bool>>)
new AnyMethodArgumentReplacingVisitor(predicate).Visit(expression);
Please note that such visitor would replace the call to any Any method. It also assumes that only the overload of Any that takes a predicate is used. There is another overload of Any that does not take a predicate. If you need to use that, you need to adjust the code.
It looks to me your problem is in your definition of
Func<T, bool> mypredicate = (p => p.myprop == myvar);
You should not use T, you should use the type of mycollectionproperties
Assuming the property mycollectionproperties is defined as something like this
....
public IQueryable<YourType> mycollectionproperties { get; set; }
....
Then you should declare mypredicate as
Func<YourType, bool> mypredicate = (p => p.myprop == myvar);
You can see a working sample on .NetFiddle

Sorting by Reflection using GetProperty

I have a generic class with a method that needs to sort a generic entity.
However, an error occurs saying that it does not recognize the Reflection GetProperty method, since lambda can not translate.
How can I do this sort ordering logic?
public IEnumerable<TEntity> GetAll()
{
var obj = _repository.GetAll()
.OrderByDescending(x => x.GetType().GetProperty(typeof(TEntity).Name + "Id"));
return obj.Pagination();
}
Here is the error image:
The error says it all.
Linq to Entities doesn't know how to translate x.GetType().GetProperty(typeof(TEntity).Name + "Id") into SQL.
You can materialize the results first, so it'll be linq to objects:
_repository.GetAll().ToList()
.OrderByDescending(x => x.GetType().GetProperty(typeof(TEntity).Name + "Id"));
You can use _repository.GetAll().Queryable().OrderByDescending(x => x.GetType().GetProperty(typeof(TEntity).Name + "Id"));
To build a LINQ query dynamically, use Expression Trees. This is how your method may look like:
public IEnumerable<TEntity> GetAll()
{
IQueryable<TEntity> obj = _repository.GetAll();
PropertyInfo keyProperty = typeof(TEntity).GetProperty(string.Concat(typeof(TEntity).Name, "Id"));
Expression parameter = Expression.Parameter(typeof(TEntity));
Expression predicate = Expression.Lambda(Expression.Property(parameter, keyProperty), parameter);
Expression queryExpression = Expression.Call(typeof(Queryable), "OrderByDescending", new Type[] { typeof(TEntity), keyProperty.PropertyType }, obj, predicate);
obj = obj.Provider.CreateQuery<TEntity>(queryExpression);
return obj.Pagination();
}

How to build a simple property selector expression in ef6

How can I create a property selector for entity framework like this?
public static List<T> StandardSearchAlgorithm<T>(this IQueryable<T> queryable, Func<T, string> property, string query)
{
return queryable.Where(e => property(e).ToLower().IndexOf(query) > -1).ToList();
}
I want the calling code to be able to be clean and simple like this:
var usernameResults = _db.Users.StandardSearchAlgorithm(u => u.Username, query);
I get a "The LINQ expression node type 'Invoke' is not supported in LINQ to Entities." error. I cannot work out how to get the expression built.
UPDATE:
Based on the answer by MBoros here is the code I ended up with. It works great.
The key to expression trees is to understand expression trees are all about breaking up what you normally write in code (like "e => e.Username.IndexOf(query)") into a series of objects: "e" gets its own object, "Username" its own object, "IndexOf()" its own object, the "query" constant its own object, and so on. The second key is to know that you can use a series of static methods on the Expression class to create various kinds of these objects, as shown below.
PropertyInfo pinfo = (PropertyInfo)((MemberExpression)property.Body).Member;
ParameterExpression parameter = Expression.Parameter(typeof(T), "e");
MemberExpression accessor = Expression.Property(parameter, pinfo);
ConstantExpression queryString = Expression.Constant(query, typeof(string));
ConstantExpression minusOne = Expression.Constant(-1, typeof(int));
MethodInfo indexOfInfo = typeof(string).GetMethod("IndexOf", new[] { typeof(string) }); // easiest way to do this
Expression indexOf = Expression.Call(accessor, indexOfInfo, queryString);
Expression expression = Expression.GreaterThan(indexOf, minusOne);
Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(expression, parameter);
//return predicate.Body.ToString(); // returns "e => e.Username.IndexOf(query) > -1" which is exactly what we want.
var results = queryable.Where(predicate).ToList();
return results;
Now I have a real problem, but I will ask it in a separate question. My real query looks like this:
public static List<T> StandardSearchAlgorithm<T>(this IQueryable<T> queryable, Func<T, string> property, string query)
{
return queryable.Where(e => property(e).IndexOf(query) > -1).Select(e=> new { Priority = property(e).IndexOf(query), Entity = e } ).ToList();
}
So I need to build an expression that returns an Anonymous Type!! Or even if I create a class to help, I need to write an expression that returns a new object. But I will include this in a separate question.
You cannot invoke CLR delegates so simply in sql. But you can pass in the property selector as an Expression tree., so your signature would be:
public static List<T> StandardSearchAlgorithm<T>(this IQueryable<T> queryable, Expression<Func<T, string>> property, string query)
Calling would look the same. But now that you have an expression in your hand, you can have a look at this answer:
Pass expression parameter as argument to another expression
It gives you the tools to simply put an expression tree inside another one. In your case it would look like:
Expression<Func<T, bool>> predicate = e => property.AsQuote()(e).Contains(query);
predicate = predicate.ResolveQuotes();
return queryable.Where(predicate).ToList();
Once you are there, you still have the .ToLower().Contains() calls (use .Contains instead of the .IndexOf()> 1). This is actually tricky. Normally the db uses its default collation, so if it set to CI (case insensitive), then it will do the compare that way. If you don't have any constraints, and can adjust the db collation, I would go for that. In this case you can omit the .ToLower() call.
Otherwise check out this anser: https://stackoverflow.com/a/2433217/280562

Build expression tree for LINQ using List<T>.Contains method

Problem
I'm working on refactoring some LINQ queries for several reports in our web application, and I'm attempting to move some duplicate query predicates into their own IQueryable exension methods so we can reuse them for these reports, and reports in the future. As you can probably infer, I've already refactored the predicate for groups, but the predicate for codes is giving me problems. This is an example of one of the report methods I have so far:
DAL method:
public List<Entities.QueryView> GetQueryView(Filter filter)
{
using (var context = CreateObjectContext())
{
return (from o in context.QueryViews
where (!filter.FromDate.HasValue || o.RepairDate >= EntityFunctions.TruncateTime(filter.FromDate))
&& (!filter.ToDate.HasValue || o.RepairDate <= EntityFunctions.TruncateTime(filter.ToDate))
select o)
.WithCode(filter)
.InGroup(filter)
.ToList();
}
}
IQueryable Extension:
public static IQueryable<T> WithCode<T>(this IQueryable<T> query, Filter filter)
{
List<string> codes = DAL.GetCodesByCategory(filter.CodeCategories);
if (codes.Count > 0)
return query.Where(Predicates.FilterByCode<T>(codes));
return query;
}
Predicate:
public static Expression<Func<T, List<string>, bool>> FilterByCode<T>(List<string> codes)
{
// Method info for List<string>.Contains(code).
var methodInfo = typeof(List<string>).GetMethod("Contains", new Type[] { typeof(string) });
// List of codes to call .Contains() against.
var instance = Expression.Variable(typeof(List<string>), "codes");
var param = Expression.Parameter(typeof(T), "j");
var left = Expression.Property(param, "Code");
var expr = Expression.Call(instance, methodInfo, Expression.Property(param, "Code"));
// j => codes.Contains(j.Code)
return Expression.Lambda<Func<T, List<string>, bool>>(expr, new ParameterExpression[] { param, instance });
}
The problem I'm having is that Queryable.Where doesn't accept a type of Expression<Func<T, List<string>, bool>. The only way I can think of creating this predicate dynamically is to use two parameters, which is the part that is really stumping me.
What I'm not comprehending is the following method works. I can pass the exact lambda expression I am trying to create dynamically, and it correctly filters my data.
public List<Entities.QueryView> GetQueryView(Filter filter)
{
// Get the codes here.
List<string> codes = DAL.GetCodesByCategory(filter.CodeCategories);
using (var context = CreateObjectContext())
{
return (from o in context.QueryViews
where (!filter.FromDate.HasValue || o.RepairDate >= EntityFunctions.TruncateTime(filter.FromDate))
&& (!filter.ToDate.HasValue || o.RepairDate <= EntityFunctions.TruncateTime(filter.ToDate))
select o)
.Where(p => codes.Contains(p.Code)) // This works fine.
//.WithCode(filter)
.InGroup(filter)
.ToList();
}
}
Questions
Can I implement my own Queryable.Where overload? If so, is it even feasible?
If an overload isn't feasible, is there a way to dynamically construct the predicate p => codes.Contains(p.Code) without using two parameters?
Is there an easier way to do this? I feel like I'm missing something.
You can create your own extension method, name it Where, accept an IQueryable<T>, return an IQueryable<T>, and otherwise make it emulate the form of LINQ methods. It wouldn't be a LINQ method, but it would look like one. I would discourage you from writing such a method simply because it would likely confuse others; even if you want to make a new extension method, use a name not used in LINQ to avoid confusion. In short, do what you're doing now, create new extensions without actually naming them Where. If you really wanted to name one Where though nothing's stopping you.
Sure, just use a lambda:
public static Expression<Func<T, bool>> FilterByCode<T>(List<string> codes)
where T : ICoded //some interface with a `Code` field
{
return p => codes.Contains(p.Code);
}
If you really cannot have your entities implement an interface (hint: you almost certainly can), then the code would look identical to the code that you have, but using the list that you pass in as a constant rather than a new parameter:
public static Expression<Func<T, bool>> FilterByCode<T>(List<string> codes)
{
var methodInfo = typeof(List<string>).GetMethod("Contains",
new Type[] { typeof(string) });
var list = Expression.Constant(codes);
var param = Expression.Parameter(typeof(T), "j");
var value = Expression.Property(param, "Code");
var body = Expression.Call(list, methodInfo, value);
// j => codes.Contains(j.Code)
return Expression.Lambda<Func<T, bool>>(body, param);
}
I would strongly encourage use of the former method; this method loses static type safety, and is more complex and as such harder to maintain.
Another note, the comment you have in your code: // j => codes.Contains(j.Code) isn't accurate. What that lambda actually looks like is: (j, codes) => codes.Contains(j.Code); which is actually noticeably different.
See the first half of #2.

Add second group by key field to existing Lambda Expression

I have a Group by expression that I am dynamically creating for use in a LINQ query. Currently, to construct the expression, I use the following code:
var arg = Expression.Parameter(typeof(T), helper.getName());
var prop = Expression.Property(arg, "customerType");
var body = Expression.Convert(prop, typeof(object));
var lambda = Expression.Lambda<Func<Contact, object>>(body, arg);
var keySelector = lambda.Compile();
I then use the keySelector in the GroupBy for my LINQ query. My question is, if I wanted to add a second grouping criteria to this expression, say "salesStage", how would I add that to this existing expression?
You have a problem, because what the compiler does on a regular GroupBy call is generate a new anonymous type with the properties you define. If the type doesn't exist, we cannot create an expression creating an object of the type.
However, given that you are using this for LINQ-to-Objects, we can use the Tuple<> type to generate the grouping key. Hopefully you do not need to group on more than 8 parameters.
Here is a generic function to generate the grouping function:
static Func<T, object> BuildGrouper<T>(IEnumerable<string> properties) {
var arg = Expression.Parameter(typeof(T), helper.getName());
// This is the list of property accesses we will be using
var parameters = properties.Select(propName => Expression.Property(arg, propName)).ToList();
// Find the correct overload of Tuple.Create.
// This will throw if the number of parameters is more than 8!
var method = typeof(Tuple).GetMethods().Where(m => m.Name == "Create" && m.GetParameters().Length == parameters.Count).Single();
// But it is a generic method, we need to specify the types of each of the arguments
var paramTypes = parameters.Select(p => p.Type).ToArray();
method = method.MakeGenericMethod(paramTypes);
// Invoke the Tuple.Create method and return the Func
var call = Expression.Call(null, method, parameters);
var lambda = Expression.Lambda<Func<T, object>>(call, arg);
return lambda.Compile();
}

Categories

Resources