How to get methods and values used in an IQueryable?

Viewed 260

I'm trying to parse an IQueryable and I want to get the methods and parameters called on the IQueryable.

e.g.

var query = Queryable.Skip(10).Take(100);

bool hasCalledSkipMethod = HasSkipMethod(query);
var skipValue = GetSkipMethodParameterValueSomeHow(query);

bool hasCalledTakeMethod = HasTakeMethod(query);
var takeValue = GetTakeMethodParameterValueSomeHow(query);

Or some generic method that receives an IQueryable and returns all the methods name and parameters.

How can I get the methods and it's parameters that was applied in an IQueryable?

1 Answers

Not sure if it is the best possible approach, but it might work for what you want or at least guide you in the right direction.

Each IQueryable has an Expression that represents the query to be executed as a tree data structure where each node is in itself also an Expression. So whenever you do .Take(int) in a IQueryable what's happening behind the scenes is that a new Node of type MethodCallExpression for the method Take(int) is added to the Expression tree of that IQueryable.

From the docs:

The Take(IQueryable, Int32) method generates a MethodCallExpression that represents calling Take(IQueryable, Int32) itself as a constructed generic method. It then passes the MethodCallExpression to the CreateQuery(Expression) method of the IQueryProvider represented by the Provider property of the source parameter.

Therefore, what you want to do is to traverse (visit each node in the tree) and see if there is any Node of type MethodCallExpression where the method name of the Expression is, in this case, "Take".

To traverse the expression tree you can create an ExpressionVisitor that overrides the method VisitMethodCall(MethodCallExpression). This custom visitor could accept a method's name to be checked as a constructor parameter and, when visiting the nodes, store in an internal property if the method name was found or not.

Once you have your custom visitor, you could call the Visit(Expression) where expression will be queryable.Expression.

Here is a gist of it.

using System.Linq;
using System.Linq.Expressions;
                    
public class Program
{
    public static void Main()
    {       
        IQueryable<int> queryable = new []{ 78, 92, 100, 37, 81 }
            .AsQueryable()
            .Skip(1)
            .Take(2);

        Expression expression = queryable.Expression;
        
        var hasTakeMethodVisitor = new HasMethodVisitor("Take");
        var hasSkipMethodVisitor = new HasMethodVisitor("Skip");
        var hasWhereMethodVisitor = new HasMethodVisitor("Where");
        
        hasTakeMethodVisitor.Visit(expression);
        hasSkipMethodVisitor.Visit(expression);
        hasWhereMethodVisitor.Visit(expression);
        
        System.Console.WriteLine("Has Take Method? {0}", hasTakeMethodVisitor.HasMethod);
        System.Console.WriteLine("Has Skip Method? {0}", hasSkipMethodVisitor.HasMethod);
        System.Console.WriteLine("Has Where Method? {0}", hasWhereMethodVisitor.HasMethod);
    }
    
    internal class HasMethodVisitor : ExpressionVisitor {
        
        private readonly string _methodToFind;
        
        public HasMethodVisitor(string methodName) {
            _methodToFind = methodName;
        }
        
        public bool HasMethod { get; private set; }
        
        protected override Expression VisitMethodCall(MethodCallExpression node) {
            HasMethod |= node.Method.Name == _methodToFind;
        
            return base.VisitMethodCall(node);
        }
    }
}

Output

Has Take Method? True
Has Skip Method? True
Has Where Method? False
Related