0

At first some background for understanding why I try to create a custom IQueryProvider:

I'm looking for a solution for a generic OData client that supports IQueryable. I don't want to use the OData client code generator (because I do not want to update the client or create new clients whenever I change the server). I have an implementation for default Web API calls based on WebClient and I want to reuse it for OData. My service is already supporting OData and is working fine. On client side, I now need an IQueryable for assigning it to the DevExpress' ODataInstantFeedbackSource.

My problem is that I need to execute the HTTP-Request for getting the IQueryable. This results in an OData query without any restrictions, leading into loading large amount of data. Therefore my idea is to create some kind of a "delegate queryable" that takes a Func delegate in it's constructor, which defines the call to the Web API and can also process the expression tree.

My attempt on that solution (sample project):

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace IQueryableTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var x = new FuncQueryable<int>((expr) =>
            {
                return new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }
                    .AsQueryable()
                    .Provider
                    .CreateQuery<int>(expr);
            });

            var y = x.Where(i => i % 2 == 0);
            var z = y.ToList();
        }
    }

    public class FuncQueryable<T> : IOrderedQueryable<T>
    {
        public FuncQueryable(Func<Expression, IQueryable<T>> source)
        {
            Provider = new ActionQueryProvider<T>(source);
        }

        public FuncQueryable(IQueryProvider provider, Expression expression)
        {
            Provider = provider;
            Expression = expression;
        }

        public Expression Expression { get; set; }

        public Type ElementType => typeof(T);

        public IQueryProvider Provider { private set; get; }

        public IEnumerator<T> GetEnumerator()
        {
            return (Provider.Execute<IEnumerable<T>>(Expression)).GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return (Provider.Execute<IEnumerable>(Expression)).GetEnumerator();
        }
    }

    public class ActionQueryProvider<T> : IQueryProvider
    {
        private readonly Func<Expression, IQueryable<T>> _source;

        public ActionQueryProvider(Func<Expression, IQueryable<T>> source)
        {
            _source = source;
        }

        public IQueryable CreateQuery(Expression expression)
        {
            return new FuncQueryable<T>(this, expression);
        }

        public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
        {
            return new FuncQueryable<TElement>(this, expression);
        }

        public object Execute(Expression expression)
        {
            return _source.Invoke(expression);
        }

        public TResult Execute<TResult>(Expression expression)
        {
            return (TResult)Execute(expression);
        }
    }
}

I get the following Exception:

Der Wert darf nicht NULL sein. (ArgumentNullException)
Parametername: arguments

bei System.Linq.Expressions.Expression.RequiresCanRead(Expression expression, String paramName)
bei System.Linq.Expressions.Expression.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arg, ParameterInfo pi)
bei System.Linq.Expressions.Expression.ValidateArgumentTypes(MethodBase method, ExpressionType nodeKind, ReadOnlyCollection`1& arguments)
bei System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, IEnumerable`1 arguments)
bei System.Linq.Queryable.Where[TSource](IQueryable`1 source, Expression`1 predicate)
bei IQueryableTest.Program.Main(String[] args)

in line var y = x.Where(i => i % 2 == 0);.

dymanoid
  • 14,771
  • 4
  • 36
  • 64
  • Is it something to do with you creating `FuncQueryable` with `Func` argument and the ctor which sets Expression not being called. Now `Expression` in your implementation of `IOrderedQueryable` will be null. – Phil Cooper Dec 06 '18 at 13:34
  • If you look at the stack trace, `RequiresCanRead` is called with Expression and throws the `ArgumentNullException` so it must be that. – Phil Cooper Dec 06 '18 at 13:35
  • https://dotnetfiddle.net/Z1dc0R - using empty expression. – Phil Cooper Dec 06 '18 at 13:37
  • 1
    Ok, I added "Expression = Expression.Constant(this);" to the ctor of FuncQueryable. That solved the issue. But now I get a StackOverflowException. GetEnumerator() is somehow calling itself... – eight_ball Dec 06 '18 at 15:42
  • It never rains... new ticket? ;) – Phil Cooper Dec 06 '18 at 16:02
  • @eight_ball, can you provide your comment as an answer? – Stef Geysels Dec 21 '22 at 09:48

0 Answers0