Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get MethodInfo of Count() LINQ extension method on arbitrary IQueryable<T>

I have IQueryable< T> source and i want to dynamically call IQueryable< T>.Count().

So, i need MethodInfo of Count method declared in IQueryable.

this is its signature (in IQueryable<>) from msdn:

public static int Count<TSource>(
    this IQueryable<TSource> source
)

This is how far i got:

Expression expr; //this is expression which holds my IQueryable<T>
MethodInfo mi = expr.Type.GetMethod("Count", BindingFlags.Static | BindingFlags.Public, null, new[] { expr.Type }, null); 

but my mi is always null;

I also tried:

mi = typeof(IQueryable<>).GetMethod("Count", BindingFlags.Static | BindingFlags.Public, null, new[] { expr.Type }, null);

but again null.

My final goal would be:

Expression.Call(mi, expr);

UPDATE: this is how I get Sum Extension method:

MethodInfo sum = typeof(Queryable).GetMethod("Sum", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(IQueryable<decimal>) }, null);

and this works, but this Sum method is not generic. It is though static.

like image 471
Milos Mijatovic Avatar asked Apr 23 '12 08:04

Milos Mijatovic


2 Answers

You need to crack out the generic argument of the IQueryable<T> type and use that; also the type that owns the method is not IQueryable<T>, it is Queryable - if you think about it - interfaces can't have static methods (well, as a commenter has pointed out, in C# that is) :).

Also, because it's a generic method you can't match the parameters in the way you've tried: because you need to pass the generic type definition IQuerable<TSource> - not a generic type IQueryable<int> or whatever the actual expression is.

Instead you can just look for a single-parametered version of a static method called 'Count' on the Queryable type:

Type genericArgument = expr.GetGenericArguments()[0];

MethodInfo countMethod = typeof(Queryable)
              .GetMethods(BindingFlags.Static | BindingFlags.Public)
              //narrow the search before doing 'Single()'
              .Single(mi => mi.Name == "Count"
                         // this check technically not required, but more future proof
                         && mi.IsGenericMethodDefinition
                         && mi.GetParameters().Length == 1)
              .MakeGenericMethod(genericArgument);

//now you can bind to 'countMethod'

Update 7th March 2017 - Clearly, something changed in the framework which stopped the original version of the code example from working - this is an updated version which should work

Going into it a bit further - the signature of the method is:

public static int Count<TSource>(
  this IQueryable<TSource> source
)

So while the parameter type is IQueryable<TSource> it's generic over the TSource type - hence the reason why you need to fish into your IQueryable<TSource> expression and grab it's generic argument. And you should also be able to see what I mean about the parameter here.

like image 191
Andras Zoltan Avatar answered Oct 21 '22 03:10

Andras Zoltan


Let the compiler get the method for you.

Type genericArgument = expr.GetGenericArguments()[0];
var fakeExp = (Expression<Func<IQueryable<int>, int>>)(q => q.Count());
var mi = ((MethodCallExpression)fakeExp.Body).Method.GetGenericMethodDefinition().MakeGenericMethod(genericArgument);
like image 41
MBoros Avatar answered Oct 21 '22 02:10

MBoros