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