To create a custom LINQ provider we need to implement the interfaces, IQueryable or IOrderedQueryable and IQueryProvider.
This is a summary of several posts/blogs I’ve read on the subject and source code available on github etc. See References below.
IQueryable<T>
To implement a minimal LINQ provider we need to implement IQueryable<>. This interface inherits from IEnumerable. The actual IQueryable methods are minimal
- ElementType
- Expression
- Provider
IOrderedQueryable<T>
If we want to support the sorting query operators, then we need to implement IOrderedQueryable<T>.
IOrderedQueryable inherits from IEnumerable, IEnumerable<T>, IOrderedQueryable, IQueryable, and IQueryable<T>.
We can implement an IOrderedQueryable that’s reusable for most situations, as follows
public class Queryable<T> : IOrderedQueryable<T> { public Queryable(IQueryContext queryContext) { Initialize(new QueryProvider(queryContext), null); } public Queryable(IQueryProvider provider) { Initialize(provider, null); } internal Queryable(IQueryProvider provider, Expression expression) { Initialize(provider, expression); } private void Initialize(IQueryProvider provider, Expression expression) { if (provider == null) throw new ArgumentNullException("provider"); if (expression != null && !typeof(IQueryable<T>). IsAssignableFrom(expression.Type)) throw new ArgumentException( String.Format("Not assignable from {0}", expression.Type), "expression"); Provider = provider; Expression = expression ?? Expression.Constant(this); } public IEnumerator<T> GetEnumerator() { return (Provider.Execute<IEnumerable<T>>(Expression)).GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return (Provider.Execute<System.Collections.IEnumerable>(Expression)).GetEnumerator(); } public Type ElementType { get { return typeof(T); } } public Expression Expression { get; private set; } public IQueryProvider Provider { get; private set; } }
Note: The IQueryContext is not part of the LINQ interfaces but one I’ve created to allow me to abstract the actual “custom” part of the code and allow maximum reuse of default functionality.
IQueryProvider
The IQueryProvider basically requires two methods (a generic and non-generic version of both) to be implemented. Obviously the generic versions are for strongly typed and the non-generic are loosely typed.
CreateQuery
The CreateQuery method is used to create a new instance of an IQueryable based upon the supplied expression tree.
Execute
The Execute method is used to actually execute a query expression, i.e. in a custom provider we will get the data from the datasource, for example a webservice or database etc. and it’s in the Execute method that we both get the data and do any conversion to, say SQL or the likes (if the LINQ query ultimately queries some other system.
An example of an implementation for an IQueryProvider might look like this…
public class QueryProvider : IQueryProvider { private readonly IQueryContext queryContext; public QueryProvider(IQueryContext queryContext) { this.queryContext = queryContext; } public virtual IQueryable CreateQuery(Expression expression) { Type elementType = TypeSystem.GetElementType(expression.Type); try { return (IQueryable)Activator.CreateInstance(typeof(Queryable<>). MakeGenericType(elementType), new object[] { this, expression }); } catch (TargetInvocationException e) { throw e.InnerException; } } public virtual IQueryable<T> CreateQuery<T>(Expression expression) { return new Queryable<T>(this, expression); } object IQueryProvider.Execute(Expression expression) { return queryContext.Execute(expression, false); } T IQueryProvider.Execute<T>(Expression expression) { return (T)queryContext.Execute(expression, (typeof(T).Name == "IEnumerable`1")); } }
Note: Again, the IQueryContext is not part of the LINQ interfaces but one I’ve created to allow me to abstract the actual “custom” part of the code and allow maximum reuse of default functionality.
The TypeSystem class is taken from the post Walkthrough: Creating an IQueryable LINQ Provider
IQueryContext
The above shows some LINQ interfaces and some sample implementations. I’ve pointed out that IQueryContext is not part of LINQ but is instead something I’ve created (based upon reading various other implementations) to allow me to abstract the actual LINQ provider code specific to my provider’s implementation. Ofcourse we could have derived from QueryProvider, but for now we “plug-in” the data context instead. To change the implementation to derive from QueryProvider simple remove the IQueryContext (also from the Queryable implementation) and override the Execute methods.
For now I’ll continue this post using the IQueryContext, so here’s the interface
public interface IQueryContext { object Execute(Expression expression, bool isEnumerable); }
Implementing the IQueryContext
Whether the implementation of the actual Execute code (on the IQueryProvider) is abstracted into the IQueryContext or implemented within an alternate implementation of IQueryProvider. This is where the fun of actually “running” the LINQ query against your custom provider takes place.
When writing something like this…
var ctx = new Queryable<string>(new CustomContext()) var query = from s in ctx where s.StartsWith("T") select s;
what we are really doing is creating the query. Hence the CreateQuery methods on the IQueryProvider are called, but the actual data source or whatever supplies the data for your custom LINQ provider should not be called until we reach the execution phase, this is known as deferred execution. The execution phase takes place when we enumerate over the query or call methods, such as Count() etc. against a query.
foreach (var q in query) { Console.WriteLine(q); }
So at the point that we GetEnumerator implicitly via the foreach loop, that’s when the Execute method is called.
Executing the query
So, the Execute method is called and we will have an expression tree defined by LINQ supplied as an argument. We now need to translate that query into something useful, i.e. turn it into an SQL query for a custom database LINQ provider and then get the data for this query, or get data from a webservice and allow the query to be executed against the returned data etc.
As the actual decoding of the Expression is a fairly large subject in itself, I’ll leave that for another post, but suffice to say, there’s a lot we need to implement to duplicate some if not all of the “standard” LINQ query operators etc.
References
Walkthrough: Creating an IQueryable LINQ Provider
LINQ: Building an IQueryable provider series
LINQ and Deferred Execution