Build Any() with Expression Trees for LINQ Queries - c#

I'm building a SQL "Any" clause dynamically using the System.Linq.Expressions.Expression class
I can do it like this
Expression<Func<User, Lead, bool>> predicate = (user, lead) => user.UserRoleSubProducts.Any(x => x.SubProductID == lead.SubProductID);
But I am not able to achieve this using Expression Tree.
I have tried below
var param1 = Expression.Parameter(typeof(User), "user");
var property1 = Expression.Property(param1, "UserRoleSubProducts");
var exp1 = Expression.Lambda(property1, new[] { param1 });
var param2 = Expression.Parameter(typeof(Lead), "lead");
var property2 = Expression.Property(param2, "SubProductID");
var exp2 = Expression.Lambda(property2, new[] { param2 });
var param3 = Expression.Parameter(property1.Type.GetProperty("Item").PropertyType, "x");
var property3 = Expression.Property(param3, "SubProductID");
var exp3 = Expression.Lambda(property3, new[] { param3 });
var equality = Expression.Equal(property2, property3);
var any = typeof(Queryable).GetMethods().Where(m => m.Name == "Any").Single(m => m.GetParameters().Length == 2).MakeGenericMethod(property1.Type);
var expression = Expression.Call(null, any, property1, equality);
But getting
Expression of type
'Microsoft.OData.Client.DataServiceCollection1[Api.Models.UserRoleSubProduct]' cannot be used for parameter of type System.Linq.IQueryable1[Microsoft.OData.Client.DataServiceCollection1[Api.Models.UserRoleSubProduct]]' of method 'Boolean Any[DataServiceCollection1](System.Linq.IQueryable1[Microsoft.OData.Client.DataServiceCollection1[Api.Models.UserRoleSubProduct]],
System.Linq.Expressions.Expression1[System.Func2[Microsoft.OData.Client.DataServiceCollection`1[Api.Models.UserRoleSubProduct],System.Boolean]])'
I think I am close enough. Any help is appreciated

Ignoring the redundant unused lambda expressions, the problem is in the last 2 lines.
First, you are using a wrong generic type (MakeGenericMethod(property1.Type)), while the correct type is basically the type of the parameter x here
.Any(x => x.SubProductID == lead.SubProductID)
=>
.Any<T>((T x) => ...)
which maps to param3.Type in your code.
Second, the second argument of the Any must be lambda expression (not simply equality as in the code).
Third, since user.UserRoleSubProducts most likely is a collection type, you should emit call to Enumerable.Any rather than Queryable.Any.
The Expression.Call method has overload which is very handy for "calling" static generic extension methods:
public static MethodCallExpression Call(
Type type,
string methodName,
Type[] typeArguments,
params Expression[] arguments
)
So the last two lines can be replaced with:
var anyCall = Expression.Call(
typeof(Enumerable), nameof(Enumerable.Any), new Type[] { param3.Type },
property1, Expression.Lambda(equality, param3)
);

Related

How to use a variable when making a lambda expression using Expression.Lambda<Func<>>()

I have a linq query which is as follows:
var query = _appDbContext.Persons
.Where(p => p.Id == 123)
.Select(lambda)
.ToList();
I use lambda because i have a variable in my select statement. The lambda:
var wantedData = "Adress";
var x = Expression.Parameter(typeof(Person), "x");
var body = Expression.PropertyOrField(x, wantedData);
var lambda = Expression.Lambda<Func<Person, Adress>>(body, x);
The query will return the addresses for person with id 123. Let's say instead of the addresses I would like to recieve all subscriptions this person has. Only setting wantedData = "Subscription" will not work, because of the "Adress" in the lambda statement. So I'm looking for a way to use a variable to change "Adress" in the lambda statement.
I tried the following, which obviously does not work.
var lambda = Expression.Lambda<Func<Person, wantedData>>(body, x);
Is there a way to do this?
Ok, I managed to create a generic query method that takes an Expression as parameter.
EDIT: This will receive a string value and will try to match it with an existing property on your Person class.
private IEnumerable<TResult> GetPersonData<TResult>(Expression<Func<Person, TResult>> selectExpression)
{
return _dbContext.Persons
// .Where(filter)
.Select(selectExpression)
.ToList();
}
public IEnumerable<object> GetData(string dataPropertyName)
{
switch(dataPropertyName) {
case nameof(Person.Address): return GetPersonData(p => p.Address);
case nameof(Person.Subscription): return GetPersonData(p => p.Subscription);
// other cases
default: throw new InvalidArgumentException("Invalid property name");
}
}
note that this code is just an example written on the spot and it might not work directly with copy-paste
I've made something similar to an OrderBy and tweak it a little for your select. This only works if you know the return type.
You can create an extension method with the following code:
public static IQueryable<TResult> Select<T,TResult>(this IQueryable<T> source, string propertyName)
{
var prop = typeof(T).GetProperties()
.FirstOrDefault(x => x.Name.ToUpper() == propertyName.ToUpper());
ParameterExpression pe = Expression.Parameter(typeof(T), "x");
MemberExpression body = Expression.Property(pe, prop.Name);
LambdaExpression lambda = Expression.Lambda(body, new ParameterExpression[] { pe });
MethodCallExpression selectCallExpression = Expression.Call(
typeof(Queryable),
"Select",
new Type[] { source.ElementType, prop.PropertyType },
source.Expression,
lambda);
return source.Provider.CreateQuery<TResult>(selectCallExpression);
}
That way, you can use it like:
var query = _appDbContext.Persons
.Where(p => p.Id == 123)
.Select<Person,string>(wantedData)
.ToList();

Dynamically ordering by string

I've created an extension on IQueryable as I would like to order by nullable datetimes first then order by the datetime itself using just the string of the property i.e "activeTo". I've created the code below:
public static IQueryable<T> Sort<T>(this IQueryable<T> source, string sortBy)
{
//create the expression tree that represents the generic parameter to the predicate
var param = Expression.Parameter(typeof(T), "p");
//create an expression tree that represents the expression p=>p.SortField.HasValue
var prop = Expression.Property(param, sortBy);
var target = Expression.Constant(null, prop.Type);
var bin = Expression.Equal(prop, Expression.Convert(target, prop.Type));
var exp = Expression.Lambda(bin, param);
string method = "OrderBy";
Type[] types = new Type[] { source.ElementType, exp.Body.Type };
var orderByCallExpression = Expression.Call(typeof(Queryable), method, types, source.Expression, exp);
//now do the ThenBy bit,sending in the above expression to the Expression.Call
exp = Expression.Lambda(prop, param);
types = new Type[] { source.ElementType, exp.Body.Type };
method = "ThenBy";
var ThenByCallExpression = Expression.Call(typeof(Queryable), method, types, orderByCallExpression, exp);
return source.Provider.CreateQuery<T>(ThenByCallExpression);
}
This extension is called by:
query.Sort("activeTo");
Which then gives the below the response:
{
"title": "test 5",
"activeFrom": "2019-06-08T21:26:50.2833333",
"activeTo": "2019-06-08T21:26:50.2833333",
},
{
"title": "test 2",
"activeFrom": "2019-06-08T21:28:45.65",
"activeTo": null,
}
I'd expect the record with activeTo as null to be first however, this isn't the case.
Does anyone know what I'm doing wrong?
From the comments the goal seems to be to dynamically generate an expression which sorts null values to the front.
The current code produces the following expression OrderBy(p => p.activeTo == null).ThenBy(p => p.activeTo == null). This has two flaws:
It sorts null values to the front as the bools sort order is false, true (as their ordinal values are 0 and 1, respectively). Therefore a comparison to null first collects the false cases, and then the truecases.
The ThenBy repeats the OrderBy, but was actually intended to emit ThenBy(p => p.ActiveTo).
The first can be solved by either using Expression.NotEqual instead of Expression.Equal for p => p != p.activeTo or by using OrderByDescending instead of OrderBy.
In total the code should be:
public static IQueryable<T> Sort<T>(IQueryable<T> source, string sortBy)
{
//create the expression tree that represents the generic parameter to the predicate
var param = Expression.Parameter(typeof(T), "p");
//create an expression tree that represents the expression p=>p.SortField.HasValue
var prop = Expression.Property(param, sortBy);
var target = Expression.Constant(null, prop.Type);
// NotEqual, to sort nulls before not-nulls
var bin = Expression.NotEqual(prop, Expression.Convert(target, prop.Type));
var exp = Expression.Lambda(bin, param);
// OrderBy with the null comparison expression
string method = nameof(Queryable.OrderBy);
Type[] types = new Type[] { source.ElementType, exp.Body.Type };
var orderByCallExpression = Expression.Call(typeof(Queryable), method, types, source.Expression, exp);
// ThenBy with the property expression
exp = Expression.Lambda(prop, param);
types = new Type[] { source.ElementType, exp.Body.Type };
method = nameof(Queryable.ThenBy);
var ThenByCallExpression = Expression.Call(typeof(Queryable), method, types, orderByCallExpression, exp);
return source.Provider.CreateQuery<T>(ThenByCallExpression);
}
This yields the following expression:
OrderBy(p => p.activeTo != null).ThenBy(p => p.activeTo).
Remarks: It should be noted that usually OrderBy(p => p.activeTo) would already sort null values to the front, as this is the default sort order for strings, nullables, and so on. However, this behavior could be overwritten depending by the specific type and depend on the query source. Therefore, I left it like the OP has.

Expression Trees with subquery

My objective is to create a subquery expression tree for a dynamic Full Text Search. In SQL it would be the equivalent of
SELECT *
FROM MV
WHERE MV.ID IN (SELECT ID
FROM MVF
WHERE title = "foo" OR Description = "foo")
So the basic idea is to create the FTS subquery, get the ids from that and use those for the In predicate. My issue is with the second part of that.
// Get subquery for FTS tables
ParameterExpression ftsParam = Expression.Parameter(typeof(MVF), "mvfts");
var wphrase = Expression.Constant("foo");
var methodInfo = typeof(string).GetMethod("Equals", new Type[] { typeof(string) });
var ftsID = Expression.Property(ftsParam, "ID");
var ftsTitle = Expression.Property(ftsParam, "Title");
var ftsDescrip = Expression.Property(ftsParam, "Description");
var texp = Expression.Call(ftsTitle, methodInfo, wphrase);
var dexp = Expression.Call(ftsDescrip, methodInfo, wphrase);
var ftsExp = Expression.Or(texp, dexp);
// Now get ids from the above fts resultset
// THE ASSIGNMENT BELOW THROWS
var selectExp = Expression.Call(typeof(IEnumerable<MVF>), "Select", new Type[]
{
typeof(long)
},
ftsExp,
Expression.Lambda<Func<MFV, long>>(
ftsID,
ftsParam
)
);
// Now set up MV table reference
ParameterExpression vParam = Expression.Parameter(typeof(MV), "mv");
var mvID = Expression.Property(vParam, "MVID");
var containsInfo = typeof(IEnumerable<long>).GetMethod("Contains", new Type[] { typeof(long) });
// Now combine expression to get those mvs with ids in the result set of fts query
var containsExp = Expression.Call(selectExp, containsInfo, mvID);
return Expression.Lambda<Func<MV, bool>>(containsExp, vParam);
Exception is:
No generic method 'Select' on type
'System.Collections.Generic.IEnumerable`1[MVF]' is compatible with the
supplied type arguments and arguments. No type arguments should be
provided if the method is non-generic.
Both methods needed by the expression in question are static generic extension methods (with the most important being static and generic) of the Enumerable class:
Enumerable.Select
public static IEnumerable<TResult> Select<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, TResult> selector
)
Enumerable.Contains
public static bool Contains<TSource>(
this IEnumerable<TSource> source,
TSource value
)
The most convenient way of "calling" such methods is the following Expression.Call method overload:
public static MethodCallExpression Call(
Type type,
string methodName,
Type[] typeArguments,
params Expression[] arguments
)
The Type type argument is the type of the class defining the method being called (typeof(Enumerable) in this case) and the Type[] typeArguments is the array with the types of the generic type arguments (empty for non generic methods, should be { typeof(TSource), typeof(TResult) } for Select and { typeof(TSource) } for Contains).
Applying it to your scenario:
var selectExp = Expression.Call(
typeof(Enumerable),
"Select",
new { typeof(MFV), typeof(long) },
ftsExp,
Expression.Lambda<Func<MFV, long>>(ftsID, ftsParam)
);
and
var containsExp = Expression.Call(
typeof(Enumerable),
"Contains",
new [] { typeof(long) },
selectExp,
mvID
);
typeof(IEnumerable<long>) does not define a select method. The Linq Select Methods are Extension Methods not visible directly via reflection. The class that defines them is Enumerable. However typeof(Enumerable) will not work either as the method is generic. You have to get the generic method from the class enumerable first and then use MethodInfo.MakeGenericMethod to create a method which takes a long parameter.
var method = typeof(Enumerable).GetMethods().First(m => m.Name == "Select" &&
m.GetParameters().Last().ParameterType.GetGenericArguments().Length == 2);
var genericSelectFromLongToLong = method.MakeGenericMethod(new Type[] {typeof(long), typeof(long)});
Note that it would probably be better to check the generic arguments instead of the argument count for a match.

Creating an expression tree for string-concatinating two objects

I'm just learning about Expression and their expression-trees to use them with IronPython (but that's irrelevant for now).
What I'm trying to do is, creating an expression tree like the following lambda:
Func<T, int, string> func = (s,t) => s + t;
My current function is this:
public static Expression<Func<T, int, string>> StringConcatSelector<T>()
{
var parameterParam = Expression.Parameter(typeof(T), "x");
var paramToString = typeof(T).GetMethods().FirstOrDefault(s=>s.Name=="ToString");
var parameter = Expression.Call(parameterParam, paramToString);
var intParameterParam = Expression.Parameter(typeof(int), "s");
var intParameterToString = typeof(int).GetMethods().FirstOrDefault(s => s.Name == "ToString");
var intParameter = Expression.Call(intParameterParam, intParameterToString);
var stringConcat = typeof(string).GetMethods().FirstOrDefault(s => s.Name == "Concat");
var result = Expression.Call(stringConcat, parameter, intParameter);
return Expression.Lambda<Func<T, int, string>>
(result, parameterParam, intParameterParam);
}
the Expression.Callof String.Concat won't work this way, because of invalid parameter-count.
So I think I need something like:
create a List<string>-variable-expression
add both values to the list
use String.Concatwith the list-expression.
Am I right?
If yes, how can I create a List-variable (or an Array), add both values to take it as parameter for my String.Concat?
String.Concat method has 11 (!) overloads, and you are taking a random one.
The most appropriate for your case is
public static String Concat(String str0, String str1)
which you can get by using the following Type.GetMethod overload
public MethodInfo GetMethod(string name, Type[] types)
where the types array represents the type of the method arguments:
var stringConcat = typeof(string).GetMethod("Concat",
new[] { typeof(string), typeof(string) });

Enumerable Select by Expression Tree

I'm studying "Expression Tree" but I'm not managing to perform these expressions:
// first case
someList.Select(p => p.SomeProperty);
and
// second case
someList.Select(p => new OtherClass
{
SomeProperty = p.SomeProperty
})
To the "first case" I tried do this:
var someList = new List<SomeClass>();
someList.Add(new SomeClass { SomeProperty = "Hello" });
var someParam = Expression.Parameter(typeof(SomeClass), "p");
var someProperty = Expression.Property(someParam, "SomeProperty");
Expression.Call(
typeof(Enumerable),
"Select",
new Type[]
{
typeof(SomeClass),
typeof(string)
},
Expression.Lambda(
someProperty,
someParam
)
).Dump();
But I get this error:
InvalidOperationException: No generic method 'Select' on type 'System.Linq.Enumerable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.
About the "second case", I don't have ideia how to proceed.
Can anyone guide me here?
Some examples of what you could do:
Given
public class SomeClass
{
public string SomeProperty { get; set; }
}
and
var someList = new List<SomeClass>();
someList.Add(new SomeClass { SomeProperty = "Hello" });
var someParam = Expression.Parameter(typeof(SomeClass), "p");
var someProperty = Expression.Property(someParam, "SomeProperty");
Expression<Func<SomeClass, string>> lambda = Expression.Lambda<Func<SomeClass, string>>(someProperty, someParam); // p => p.SomeProperty
Using an IEnumerable<SomeClass>... Note the .Compile()
Func<SomeClass, string> compiled = lambda.Compile();
IEnumerable<string> q1 = someList.Select(compiled);
You shouldn't ever use AsQueryable() but in unit tests and "experimentation" programs (like this one). Just to make #Peter happy, I'll add another possible condition: if you really know what it does (not think you know what it does, really!), then you can use it. But if it the first time you use it, I still suggest you ask on SO if you are right in using it.
IQueryable<SomeClass> queryable = someList.AsQueryable();
Directly using the Queryable.Select()
IQueryable<string> q2 = queryable.Select(lambda);
Building a Select and using the CreateQuery (this is very similar to what internally the Queryable.Select does) to "inject" it in the query.
MethodInfo select = (from x in typeof(Queryable).GetMethods()
where x.Name == "Select" && x.IsGenericMethod
let gens = x.GetGenericArguments()
where gens.Length == 2
let pars = x.GetParameters()
where pars.Length == 2 &&
pars[0].ParameterType == typeof(IQueryable<>).MakeGenericType(gens[0]) &&
pars[1].ParameterType == typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(gens))
select x).Single().MakeGenericMethod(typeof(SomeClass), typeof(string));
MethodCallExpression select2 = Expression.Call(null, select, Expression.Constant(queryable), lambda);
IQueryProvider provider = queryable.Provider;
IQueryable<string> q3 = provider.CreateQuery<string>(select2);
Calm down folks, after some research I found what was missing in my code...
On the fist case:
Expression.Call(
typeof(Enumerable),
"Select",
new Type[]
{
typeof(SomeClass),
typeof(string)
},
Expression.Constant(someList), // <---------------- HERE IT IS
Expression.Lambda(
someProperty,
someParam
)
);
To the second case, I created the "new" expression through the code below:
var bind = Expression.Bind(typeof(OtherClass).GetProperty("SomeProperty"), someProperty);
var otherClassNew = Expression.New(typeof(OtherClass));
var otherClassInit = Expression.MemberInit(otherClassNew, bind);
Anyway, Thank you all for your help!

Categories

Resources