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).
Related
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.
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.
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) });
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!
Consider this:
var propertyinfo = typeof(Customer).GetProperty(sortExpressionStr);
Type orderType = propertyinfo.PropertyType;
now I want to declare
Func<int,orderType>
I know its not possible directly since ordertype is at runtime but is there is any workaround ?
this is exactly what I want to do :
var propertyinfo = typeof(T).GetProperty(sortExpressionStr);
Type orderType = propertyinfo.PropertyType;
var param = Expression.Parameter(typeof(T), "x");
var sortExpression = (Expression.Lambda<Func<T, orderType>>
(Expression.Convert(Expression.Property(param, sortExpressionStr), typeof(orderType)), param));
all this because I want to convert:
Expression<Func<T,object>> to Expression<Func<T,orderType>>
or if its not possible then I want to create it from the first place with the right type , the case is as following:
I'm inside a method which have a type(Customer) and a property name of that type I want to order by it , I want to create a sort expression tree to pass it to Orderby (here).
You can do this by using an open generic type definition, and then making the specific type from that:
typeof(Func<,>).MakeGenericType(typeof(int), orderType);
However, what you're trying to do (calling Lambda<TDelegate>) is not directly possible. You must call Lambda without a type parameter:
var propertyinfo = typeof(T).GetProperty(sortExpressionStr);
Type orderType = propertyinfo.PropertyType;
var param = Expression.Parameter(typeof(T), "x");
var sortExpression = Expression.Lambda(
Expression.Convert(Expression.Property(param, sortExpressionStr),
orderType),
param));
This will create the proper Func<,> for you behind the scenes. If you want to compile the expression and use the delegate, you can only do this dynamically with
sortExpression.Compile().DynamicInvoke(param);
If you want to call the OrderBy extension method on Queryable, things get a little more complicated:
var propertyInfo = typeof(T).GetProperty(sortExpressionStr);
Type orderType = propertyInfo.PropertyType;
// first find the OrderBy method with no types specified
MethodInfo method = typeof(Queryable).GetMethods()
.Where(m => m.Name == "OrderBy" && m.GetParameters().Length == 2)
.Single();
// then make the right version by supplying the right types
MethodInfo concreteMethod = method.MakeGenericMethod(typeof(T), orderType);
var param = Expression.Parameter(typeof(T), "x");
// the key selector for the OrderBy method
Expression orderBy =
Expression.Lambda(
Expression.Property(orderParam, propertyInfo),
orderParam);
// how to use:
var sequence = new T[0].AsQueryable(); // sample IQueryable
// because no types are known in advance, we need to call Invoke
// through relection here
IQueryable result = (IQueryable) concreteMethod.Invoke(
null, // = static
new object[] { sequence, orderBy });
You can use the Type.MakeGenericType Method:
Type result = typeof(Func<,>).MakeGenericType(typeof(int), orderType);
This should work:
public static IQueryable<T> OrderByField<T>(
IQueryable<T> q, string sortfield, bool ascending)
{
var p = Expression.Parameter(typeof(T), "p");
var x = Expression.Lambda(Expression.Property(p, sortfield), p);
return q.Provider.CreateQuery<T>(
Expression.Call(typeof(Queryable),
ascending ? "OrderBy" : "OrderByDescending",
new Type[] { q.ElementType, x.Body.Type },
q.Expression,
x));
}
From here.
linqClass.OrderBy(GetSortExpression(sortstr));
public static Expression<Func<T,object>> GetSortExpression<T>(string sortExpressionStr)
{
var param = Expression.Parameter(typeof(T), "x");
var sortExpression = Expression.Lambda<Func<T, object>>(Expression.Property(param, sortExpressionStr), param);
return sortExpression;
}
this worked my problem was i used to pass extra parameter Typeof(Object) and orderby used to tell me it cant sort by Object type. thanks all
thanks dtb i will check if your answer work too and i will accept it if it works if not i will accept thsi one.
You want to use Dynamic Linq, a part of the Visual Studio sample code.
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
See if my solution dynamic enough.
public class Product
{
public long ID { get; set; }
public string Name { get; set; }
public DateTime Date { get; set; }
}
static void Main(string[] args)
{
List<Product> products = (from i in Enumerable.Range(1, 10)
select new Product { ID = i, Name = "product " + i, Date = DateTime.Now.AddDays(-i) }).ToList(); //the test case
const string SortBy = "Date"; // to test you can change to "ID"/"Name"
Type sortType = typeof(Product).GetProperty(SortBy).PropertyType; // DateTime
ParameterExpression sortParamExp = Expression.Parameter(typeof(Product), "p"); // {p}
Expression sortBodyExp = Expression.PropertyOrField(sortParamExp, SortBy); // {p.DateTime}
LambdaExpression sortExp = Expression.Lambda(sortBodyExp, sortParamExp); // {p=>p.DateTime}
var OrderByMethod = typeof(Enumerable).GetMethods().Where(m => m.Name.Equals("OrderBy") && m.GetParameters().Count() == 2).FirstOrDefault().MakeGenericMethod(typeof(Product), sortType);
var result = OrderByMethod.Invoke(products, new object[] { products, sortExp.Compile() });
}
Base on above, it's not difficult to change Product to T to make it generic.
You can get the Type associated with Func<int,orderType> in case you wanted to e.g. pass it into CreateDelegate.
But what are you ultimately wanting to do with it? There may be a more straightforward approach.