Expression Tree for a Collection - c#

I can do the following manually but need to build it up using System.Linq.Expressions.
.Where(x => x.OrganizationPersonRoles.Any(o => o.OrganizationId == value))
I can get it built up to the .Any and know I need to do an Expression.Call for the Any method but don't know how to build up the inner lambda (o => o.OrganizationId == value).
Expression exp = Expression.Parameter(typeof(T), "x");
Type type = typeof(T);
PropertyInfo pi = type.GetProperty("OrganizationPersonRoles");
exp = Expression.Property(exp, pi);
var lambda = Expression.Lambda(exp, arg);
Edit:
It's the ".Any" part I can't figure out how to build the expression. "OrganizationPersonRoles" is a collection on "Person". Something like:
var anyMethod = typeof(Queryable).GetMethods()
.Where(m => m.Name == "Any")
.Single(m => m.GetParameters().Length == 2)
.MakeGenericMethod(typeof(string));
var body = Expression.Call(exp, anyMethod, "Expression For Inner Lambda");
var lambda = Expression.Lambda(body, arg);

You can construct the lambda o => o.OrganizationId == value as follows.
int value = 5;
var xParameter = Expression.Parameter(typeof(T), "x");
var oParameter = Expression.Parameter(typeof(Organization), "o");
var expression =
Expression.Lambda(
Expression.Call(
typeof(Queryable), "Any", new[] { typeof(Organization) },
Expression.Property(xParameter, "OrganizationPersonRoles"),
Expression.Constant(
Expression.Lambda(
Expression.Equal(
Expression.Property(oParameter, "OrganizationId"),
Expression.Constant(value, typeof(int))),
oParameter),
typeof(Expression<Func<Organization, bool>>))),
xParameter);

Related

ArgumentException when trying to translate efcore expression to expression trees

I am trying to build following query
_dbContext.Models.Where(m => m.Children.Any(c => c.Name.Contains("Foobar")))
using expression trees. I am not very familar with expression trees yet, quite frankly I find them challenging. What I've tried:
var toCompare = "Foobar";
var param = Expression.Parameter(typeof(T));
// Properties
var propertyLeft = Expression.Property(param, "Children");
// Trying to retrieve Name property of the Child type
var propertyRight = Expression.Property(Expression.Parameter(propertyLeft.Type.GetGenericArguments().Single()), "Name");
// Methods
var contains = typeof(string).GetMethod("Contains", new[] { typeof(string) })!;
var any = typeof(Queryable).GetMethods()
.Where(m => m.Name == "Any")
.First(m => m.GetParameters().Length == 2)
.MakeGenericMethod(typeof(string));
// Build c => c.Name.Contains(toCompare)
var expContains = Expression.Call(propertyRight, contains, Expression.Constant(toCompare, propertyRight.Type));
var expContainsLambda = Expression.Lambda<Func<T, bool>>(expContains, parameterExpression);
// Build m => m.Children.Any(...)
var expAny = Expression.Call(any, Expression.Constant(toCompare), expContainsLambda);
var expAnyLambda = Expression.Lambda<Func<Expression<Func<T, string>>, bool>>(expAny, parameterExpression);
// Build Models.Where(m => ...)
var expWhere = Expression.Lambda<Func<T, bool>>(expAnyLambda, param);
I am getting following error at expAny
System.ArgumentException: Expression of type 'System.String' cannot be used for parameter of type 'System.Linq.IQueryable`1[System.String]' of method 'Boolean Any[String](System.Linq.IQueryable`1[System.String], System.Linq.Expressions.Expression`1[System.Func`2[System.String,System.Boolean]])' (Parameter 'arg0')
Any ideas why? What changes are necessary to make it work?
Try the following:
var toCompare = "Foobar";
var param = Expression.Parameter(typeof(T), "m");
// m.Children
var propertyLeft = Expression.Property(param, "Children");
var childType = propertyLeft.Type.GetGenericArguments()[0];
var childParam = Expression.Parameter(childType, "c");
// c.Name
var propertyRight = Expression.Property(childParam, "Name");
// c => c.Name.Contains("Foobar")
var anyPredicate =
Expression.Lambda(
Expression.Call(propertyRight, nameof(string.Contains), Type.EmptyTypes, Expression.Constant(toCompare)),
childParam
)
// m.Children.Any(c => c.Name.Contains("Foobar"))
var anyCall = Expression.Call(typeof(Enumerable), nameof(Enumerable.Any), new [] { childType }, propertyLeft, anyPredicate);
// m => m.Children.Any(c => c.Name.Contains("Foobar"))
var expWhere = Expression.Lambda<Func<T, bool>>(anyCall, param);
(Disclaimer: I am the author of the library in question.)
I've written a library that produces various string representations from expression trees, such as C# or VB code, or Dynamic LINQ. One of the representations is the factory methods needed to create an expression tree. For example, this:
Expression<Func<Model, bool>> expr = m => m.Children.Any(c => c.Name.Contains("Foobar"));
Console.WriteLine(
expr.ToString("Factory methods", "C#")
);
produces this (using simulated types Model and ModelChild:
// using static System.Linq.Expressions.Expression
var c = Parameter(
typeof(ModelChild),
"c"
);
var m = Parameter(
typeof(Model),
"m"
);
Lambda(
Call(
typeof(Queryable).GetMethod("Any", new[] { typeof(IQueryable<ModelChild>), typeof(Expression<Func<ModelChild, bool>>) }),
MakeMemberAccess(m,
typeof(Model).GetProperty("Children")
),
Quote(
Lambda(
Call(
MakeMemberAccess(c,
typeof(ModelChild).GetProperty("Name")
),
typeof(string).GetMethod("Contains", new[] { typeof(string) }),
Constant("Foobar")
),
c
)
)
),
m
)
which you can then use to customize for your own needs.

Is there any way to apply type casting in Queryable sql using Expression Tree

How can I use type casting in Queryable collection which contains a Sql query in .net core. My expression is following which is returning just string value right now but I want to return value according to the datatype passed to it.
I have tried Expression.Convert type for Expression.Property but this is not supported in Sql.
Following is my expression where TSource is source which contains the property containing string value and TDataType is datatype to be converted into.
public static Expression<Func<TSource, TDataType>> CreateMapFromExpression<TSource, TDataType>(string fieldName, TypeCode dataType)
{
var parameterExpression = Expression.Parameter(typeof(TSource));
var collectionParameter = Expression.Property(parameterExpression, "CustomFieldValues");
var childType = collectionParameter.Type.GetGenericArguments()[0];
var propertyParameter = Expression.Parameter(childType, childType.Name);
var left = Expression.Property(propertyParameter, "Name");
var right = Expression.Constant(fieldName);
var innerLambda = Expression.Equal(left, right);
var innerFunction = Expression.Lambda(innerLambda, propertyParameter);
var method = typeof(Enumerable).GetMethods().Where(m => m.Name == "FirstOrDefault" && m.GetParameters().Length == 2).FirstOrDefault().MakeGenericMethod(typeof(CustomFieldValue));
var outerLambda = Expression.Call(method, Expression.Property(parameterExpression, collectionParameter.Member as System.Reflection.PropertyInfo), innerFunction);
var propertyGetter = Expression.Property(outerLambda, "Value");
var result = Expression.Lambda<Func<TSource, TDataType>>(propertyGetter, new ParameterExpression[] { parameterExpression });
return result;
}

Generic predicate to implement searching by any string property

Is there possibility to apply searching by any string property of entity? I need to build predicate to use it in LINQ query to database.
var stringPropertyNames = typeof(T)
.GetProperties()
.Where(pi => pi.PropertyType == typeof(string) && pi.GetGetMethod() != null)
.Select(pi => pi.Name);
foreach (var item in stringPropertyNames)
{
// here some code to build Predicate corresponding to sql LIKE statement
}
UPD:
here is working code:
public static IEnumerable<T> ContainsInAnyString<T>(IQueryable<T> collection, string query)
{
var stringPropertyNames = typeof(T)
.GetProperties()
.Where(pi => pi.PropertyType == typeof(string) && pi.GetGetMethod() != null)
.Select(pi => pi.Name);
var item = Expression.Parameter(typeof(T), "item");
var searchArgument = Expression.Constant(query);
var method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
foreach (var name in stringPropertyNames)
{
var currentProp = Expression.Property(item, name);
var startsWithDishExpr = Expression.Call(currentProp, method, searchArgument);
var lambda = Expression.Lambda<Func<T, bool>>(startsWithDishExpr, item);
collection = collection.Where(lambda);
}
return collection;
}
Hope it would be helpful for someone.
It really is rather simple:
var entityParameter = Expression.Parameter(typeof(T), "entity");
return
Expression.Lambda<Func<T, bool>>
(
Expression.Equal
(
Expression.MakeMemberAccess(entityParameter, propertyInfo),
Expression.Constant(valueToSearchBy)
),
entityParameter
);
Of course, this does A == B. If you want A like '%' + B + '%' instead, you'll have to change the Expression.Equal to string.Contains. I'll leave that as an excercise, since you haven't shown any of your work yet :)

Createing Expression<Func<IQueryable<T>, IOrderedQueryable<T>>>?

I wanna create an Expression<Func<IQueryable<T>, IOrderedQueryable<T>>>, I have the following codes :
Expression selector = q => q.RegistrationDate
MethodInfo orderByMethodInfo = typeof(Queryable).GetMethods().First(method => method.Name == "OrderBy" && method.GetParameters().Count() == 2).MakeGenericMethod(argumentTypes);
MethodInfo orderByDescMethodInfo = typeof(Queryable).GetMethods().First(method => method.Name == "OrderByDescending" && method.GetParameters().Count() == 2).MakeGenericMethod(argumentTypes);
I'm gonna create c => c.OrderBy(q => q.RegistrationDate) or c => c.OrderByDescending(q => q.RegistrationDate) or generally something like c => c.OrderByDescending(q => q.RegistrationDate).ThenBy(q=>q.Name) from above codes.
Could you please guide how I can do it?
var paramExpr = Expression.Parameter(typeof(IQueryable<T>))
var orderByExpr = Expression.Call(orderByMethodInfo, paramExpr, selector);
var expr = Expression.Lambda<Func<IQueryable<T>, IOrderedQueryable<T>>>(orderByExpr, paramExpr);
Where T is the type with the RegistrationDate property in your selector expression.
You can get the queryable type from the argument type using MakeGenericType:
Type argType = typeof(DateTime);
Type queryableType = typeof(IQueryable<>).MakeGenericType(argType);

LINQ Expression Tree [duplicate]

This question already has answers here:
LINQ Expression Tree Any() inside Where()
(2 answers)
Closed 9 years ago.
I want to generate expression trees using the API for the following:
var managers = dataContext.Employees.Where(e => e.Subordinates.Any());
Additionally, how do I then generate the expression tree to do this:
var managedEmployees = managers.ToDictionary(key => key.Manager, value => value.Subordinates.Select(s => s.FullName));
I've come up with the following so far for the .Where(), but it errors because it doesn't like the type parameters in new Type[] { typeof(Func<Employee, IEnumerable<Employee>>) }.
ParameterExpression employeesParameter = Expression.Parameter(typeof(Employee), "e");
MemberExpression subordinatesProperty = Expression.Property(employeesParameter, typeof(Employee).GetProperty("Subordinates"));
MethodCallExpression hasSubordinates = Expression.Call(typeof(Enumerable),
"Any",
new Type[] { typeof(Employee) },
subordinatesProperty);
LambdaExpression whereLambda = Expression.Lambda(hasSubordinates, employeesParameter);
MethodCallExpression whereExpression = Expression.Call(typeof(Queryable),
"Where",
new Type[] { typeof(Func<Employee, IEnumerable<Employee>>) },
dataContext.Employees.AsQueryable(),
whereLambda);
I got this. The type parameters on Any and Where need to be Employee, not IQueryable<Employee> or IEnumerable<Employee> because it's just looking for the type parameters, not the actual types. I believe you also need an Expression.Constant(dataContext.Employees) instead of straight dataContext.Employees.
ParameterExpression employeesParameter = Expression.Parameter(typeof(Employee), "e");
MemberExpression subordinatesProperty = Expression.Property(employeesParameter, typeof(Employee).GetProperty("Subordinates"));
MethodCallExpression hasSubordinates = Expression.Call(typeof(Enumerable),
"Any",
new Type[] { typeof(Employee) },
subordinatesProperty);
LambdaExpression whereLambda = Expression.Lambda(hasSubordinates, employeesParameter);
MethodCallExpression whereExpression = Expression.Call(typeof(Queryable),
"Where",
new Type[] { typeof(Employee) },
Expression.Constant(dataContext.Employees),
whereLambda);
To call Any for your MemberExpression you should do this
ParameterExpression employeesParameter = Expression.Parameter(typeof(Employee), "e");
MemberExpression subordinatesProperty = Expression.Property(employeesParameter, typeof(Employee).GetProperty("Subordinates"));
var mi = typeof(Enumerable).GetMethods().First(x => x.Name == "Any" && x.GetParameters().Length == 1);
mi = mi.MakeGenericMethod(typeof (bool));
var hasSubordinates = Expression.Call(mi, subordinatesProperty);
and Where
var lambda = Expression.Lambda<Func<Employee, bool>>(hasSubordinates, employeesParameter);
var res = i.Where(lambda.Compile());

Categories