Dynamically ordering by string - c#

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.

Related

Build Any() with Expression Trees for LINQ Queries

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

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.

Generic Expression.Call for aggregate functions

I am trying to adapt the code below to build generic function which returns expression for aggregate functions such as sum, count average, min, max for list of data
Sum is working but others are not. I have Additional information: Incorrect number of arguments exception. Yes, it is clear that is Expression.Call built incorrectly for others but can not find any doc how to build the right expression for other aggregate functions.
public Expression AggregateFunc(IQueryable source, string member, string aggFunc)
{
// Properties
PropertyInfo property = source.ElementType.GetProperty(member);
FieldInfo field = source.ElementType.GetField(member);
ParameterExpression parameter = Expression.Parameter(source.ElementType, "f");
Expression selector = Expression.Lambda(Expression.MakeMemberAccess(parameter, (MemberInfo)property ?? field), parameter);
// Method
var l = typeof(Queryable).GetMethods().Where(m => m.Name == aggFunc).ToList();
MethodInfo method = typeof(Queryable).GetMethods().First(m => m.Name == aggFunc );
return Expression.Call(
null,
method.MakeGenericMethod(new[] { source.ElementType }),
new[] { source.Expression, Expression.Quote(selector) });
}
Usage:
var list = new List<Int32FormFieldData>()
{
new FormFieldData { Path = "1", Value = 1 },
new FormFieldData { Path = "2", Value = 2 },
new FormFieldData { Path = "3", Value = 3 }
};`
AggregateFunc(list.AsQueryable(), "Value", "Count");
To make it work with Min, Max etc, you need to make some changes (see comments):
public static Expression AggregateFunc(IQueryable source, string member, string aggFunc) {
PropertyInfo property = source.ElementType.GetProperty(member);
FieldInfo field = source.ElementType.GetField(member);
ParameterExpression parameter = Expression.Parameter(source.ElementType, "f");
Expression selector = Expression.Lambda(Expression.MakeMemberAccess(parameter, (MemberInfo) property ?? field), parameter);
// Method
// find correct method with two parameters: IQueryable and selector
MethodInfo method = typeof(Queryable).GetMethods().Where(c => c.GetParameters().Length == 2).First(m => m.Name == aggFunc);
// some aggregates have two generic type arguments (such as min, max, average)
// others like Sum have just one
var genArgs = new List<Type>();
genArgs.Add(source.ElementType);
if (method.GetGenericArguments().Length > 1) {
genArgs.Add(property?.PropertyType ?? field.FieldType);
}
return Expression.Call(
null,
method.MakeGenericMethod(genArgs.ToArray()),
new[] {source.Expression, Expression.Quote(selector)});
}
However, Count is different because for it, selector does not make any sense (you don't call Count(c => c.Value)), so for that it's better to create separate method with different signature (without member).

Dynamically building an expression tree

I'm following this excellent example: Convert Linq to Sql Expression to Expression Tree
In my case I'm trying to build an expression tree where the type to be filtered is only known at run time, and is expressed as a string. In the above example the type Region is already known and typed directly:
ParameterExpression pe = Expression.Parameter(typeof(Region), "region");
In my application I've been able to rewrite this as:
ParameterExpression pe = Expression.Parameter(Type.GetType("mystring"), "listData");
My stumbling block is this from the example:
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { query.ElementType },
query.Expression,
Expression.Lambda<Func<Region, bool>>(e3, new ParameterExpression[] { pe }));
var results = query.Provider.CreateQuery<Region>(whereCallExpression);
In these two lines Region is directly typed. Is there a way of dynamically using a string "Region" to achieve the same thing?
Sure, but you'll have to understand the implications. The type name Region is the compile time type. With it, you can generate a strongly typed query. However, since you don't have the type at compile time, you can still generate a query, but it won't be strongly typed.
You can generate an lambda expression of unknown compile time type by using the non-generic overloads. Likewise with the CreateQuery() method.
Here's two versions of the same query which checks if some property value matches a given value. One is generic and the other is not.
The generic version implicitly takes the type from the type of the query.
public IQueryable<TSource> PropertyEqualsValue<TSource>(IQueryable<TSource> query,
string propertyName, object value)
{
var param = Expression.Parameter(typeof(TSource));
var body = Expression.Equal(
Expression.Property(param, propertyName),
Expression.Constant(value)
);
var expr = Expression.Call(
typeof(Queryable),
"Where",
new[] { typeof(TSource) },
query.Expression,
Expression.Lambda<Func<TSource, bool>>(body, param)
);
return query.Provider.CreateQuery<TSource>(expr);
}
var query = PropertyEqualsValue(SomeTable, "SomeColumn", "SomeValue");
While the other the type is taken from the provided typeName. Note that when the query is created, we cannot provide the type, since we don't know what the type is at compile time.
public IQueryable PropertyEqualsValue(IQueryable query,
Type type, string propertyName, object value)
{
var param = Expression.Parameter(type);
var body = Expression.Equal(
Expression.Property(param, propertyName),
Expression.Constant(value)
);
var expr = Expression.Call(
typeof(Queryable),
"Where",
new[] { type },
query.Expression,
Expression.Lambda(body, param)
);
return query.Provider.CreateQuery(expr);
}
var type = Type.GetType("Some.Type.Name");
var query = PropertyEqualsValue(SomeTable, type, "SomeColumn", "SomeValue");

Expression predicates with several parameters

I use the code gave on Stackoverflow by Marc Gravell here :
http://goo.gl/57nW2
The code :
var param = Expression.Parameter(typeof (Foo));
var pred = Expression.Lambda<Func<Foo, bool>>(
Expression.Call(
Expression.PropertyOrField(param, fieldName),
"StartsWith",null,
Expression.Constant(stringToSearch)), param);
Now, I'd like combine several argument, sample :
public void BuildPredicate(string[] typeSearch, string[] field, string searchValue)
{
//Content
//typeSearch = new string[] {"Contains", "StartsWith", "StartsWith" };
//field = new string[] { "FieldA", "FieldB", "FieldC" };
//FieldA contains searchValue and FieldB startWith searchValue and FieldC startWith searchValue
}
An idea ?
Thanks,
You can simply loop over all operations on all fields and build up a Expression tree containing an OrElse clause for each type/field combination
var expressions = from type in typeSearch
from field in fields
select Expression.Call(
Expression.PropertyOrField(param, field),
type, null,
Expression.Constant(stringToSearch));
Expression body = Expression.Constant(false);
foreach (Expression expression in expressions)
{
body = Expression.OrElse(body, expression);
}
var result = Expression.Lambda<Func<Foo, bool>>(body, param);
And as requested, an example including calls to ToUpper:
var expressions = from type in typeSearch
from field in fields
select Expression.Call(
Expression.Call(
Expression.PropertyOrField(param, field),
"ToUpper", null),
type, null,
Expression.Constant(stringToSearch));

Categories

Resources