Build your own LINQ Provider (medium): IQueryable and IQueryProvider

Source: Internet
Author: User
Overview

In. NET Framework 3.5, with the support of LINQ, has won the favor of developers with its powerful and elegant programming methods, and various LINQ providers are even more powerful, such as LINQ to nhib.pdf and LINQ to Google. As well as good scalability, we can easily write our own LINQ Provider.
This article is the second article in the series for creating your own LINQ Provider. It mainly introduces the two most important interfaces IQueryable and IQueryProvider in custom LINQ Provider.

IEnumerable <T> Interface

At the end of the previous article "build your own LINQ Provider (on): Expression Tree secrets", I said the following sentence: it should be noted that no specific LINQ Provider is required for the LINQ to Objects, because it is not translated as the expression directory tree. With this problem, let's take a look at the following code, the query result is IEnumerable <String> type:

static void Main(string[] args){    List<String> myList = new List<String>() { "a", "ab", "cd", "bd" };    IEnumerable<String> query = from s in myList                where s.StartsWith("a")                select s;    foreach (String s in query)    {        Console.WriteLine(s);    }    Console.Read();}

Two results are returned, as shown in:

Here is a question: why is the returned IEnumerable <T> type of data rather than IQueryable <T> In LINQ to Objects? The answer is at the beginning of this article. In LINQ to Objects, the query expression or Lambda expression is not translated as the expression directory tree, because the data queried by LINQ to Objects implements the IEnmerable <T> interface, the query expression or Lambda expression can be directly converted.. NET Code, which does not need to be converted to the expression directory. This is also a special place for LINQ to Objects. It does not need a specific LINQ Provider. Let's take a look at the implementation of the IEnumerable <T> interface, which does not have the attributes such as Expression and Provider, as shown in:

All the standard query operators in LINQ to Objects are implemented through extension methods, which are defined in the abstract class Enumerable. The Where extension method in it is shown in the following code:

public static class Enumerable{    public static IEnumerable<TSource> Where<TSource>(        this IEnumerable<TSource> source,         Func<TSource, bool> predicate)    {        if (source == null)        {            throw Error.ArgumentNull("source");        }        if (predicate == null)        {            throw Error.ArgumentNull("predicate");        }        return WhereIterator<TSource>(source, predicate);    }    public static IEnumerable<TSource> Where<TSource>(        this IEnumerable<TSource> source,         Func<TSource, int, bool> predicate)    {        if (source == null)        {            throw Error.ArgumentNull("source");        }        if (predicate == null)        {            throw Error.ArgumentNull("predicate");        }        return WhereIterator<TSource>(source, predicate);    }}

Note that the parameter Func <TSource> series Delegate of the method, instead of Expression <Func <TSource>. After this article, you will see the data of the IQueryable interface, the parameters of these extension methods are Expression <Func <TSource>. I have already mentioned the differences between them in the previous article. Also, in IEnumerable <T>, a set of extension methods AsQueryable () are provided (), the following code converts an IEnumerable <T> type of data to an IQueryable <T> type:

static void Main(string[] args){    var myList = new List<String>()                 { "a", "ab", "cd", "bd" }.AsQueryable<String>();    IQueryable<String> query = from s in myList                where s.StartsWith("a")                select s;    foreach (String s in query)    {        Console.WriteLine(s);    }    Console.Read();} 

Run this code. Although the output result is exactly the same as the preceding example, the query mechanism is completely different:

IQueryable <T> Interface

In. NET, IQueryable <T> inherits from IEnumerable <T> and IQueryable interfaces, as shown in:

There are two important attributes: Expression and Provider, which respectively indicate getting the Expression directory tree associated with the IQueryable instance and obtaining the query Provider associated with the data source, all the method calls or Lambda expressions defined in the query Expression will be expressed by the Expression attribute, and the Provider will eventually translate it into the query language of the corresponding data source, this data source may be a database, XML file, or WebService. This interface is very important. This interface must be implemented in custom LINQ Provider. Similarly, standard query operations for IQueryable are implemented by the extension method in Queryable, as shown in the following code:

public static class Queryable{    public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source,             Expression<Func<TSource, bool>> predicate)    {        if (source == null)        {            throw Error.ArgumentNull("source");        }        if (predicate == null)        {            throw Error.ArgumentNull("predicate");        }        return source.Provider.CreateQuery<TSource>(            Expression.Call(null, ((MethodInfo) MethodBase.GetCurrentMethod())            .MakeGenericMethod(new Type[] { typeof(TSource) }),             new Expression[] { source.Expression, Expression.Quote(predicate) }));    }    public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source,        Expression<Func<TSource, int, bool>> predicate)    {        if (source == null)        {            throw Error.ArgumentNull("source");        }        if (predicate == null)        {            throw Error.ArgumentNull("predicate");        }        return source.Provider.CreateQuery<TSource>(            Expression.Call(null, ((MethodInfo) MethodBase.GetCurrentMethod())            .MakeGenericMethod(new Type[] { typeof(TSource) }),             new Expression[] { source.Expression, Expression.Quote(predicate) }));    }}

Finally, if the defined query must support operations such as Orderby, you must also implement the IOrderedQueryable <T> interface, which inherits from IQueryable <T>, as shown in:

 

IQueryProvider Interface

After understanding the IQueryable interface, let's take a look at another very important interface IQueryProvider in the Custom LINQ Provider. Its definition is shown in:

We can see the parameters of the two sets of methods here. As you can see, the Provider is responsible for executing the expression directory tree and returning results. If it is the Provider of LINQ to SQL, it is responsible for translating the expression directory tree into a T-SQL statement and passing it to the database server, and returning the final execution result; if it is a Web Service Provider, it is responsible for translating the expression directory tree, calling the Web Service, and finally returning results.

Here, the four methods are actually two operations: CreateQuery and Execute (both generic and non-generic). The CreateQuery method is used to construct an IQueryable <T> object, this object can calculate the query represented by the directory tree of the specified expression. The returned result is of an enumerated type. Execute Executes the query represented by the directory tree of the specified expression, the returned result is a single value. To customize the simplest LINQ Provider, you must at least implement the IQueryable <T> and IQueryProvider interfaces. In the next article, you will see a comprehensive instance.

Two Methods for extended LINQ

Through the above explanation, we can think of two methods for the extension of LINQ: first, using the help of LINQ to Objects, if the query we make is directly in.. NET code, you can implement the IEnumerable <T> interface, without having to implement IQueryable and write custom LINQ providers, such. NET built-in List <T>. For example, you can write a simple custom code:

Public class MyData <T>: IEnumerable <T> where T: class {public IEnumerator <T> GetEnumerator () {return null;} IEnumerator IEnumerable. getEnumerator () {return null;} // other members}

The second method to extend the LINQ is, of course, to customize the LINQ Provider. We need to implement the IQueryable <T> and IQueryProvider interfaces. The following provides a simple sample code, in the next article, we will implement a complete LINQ Provider. The following code is used:

public class QueryableData<TData> : IQueryable<TData>{    public QueryableData()    {        Provider = new TerryQueryProvider();        Expression = Expression.Constant(this);    }    public QueryableData(TerryQueryProvider provider,         Expression expression)    {        if (provider == null)        {            throw new ArgumentNullException("provider");        }        if (expression == null)        {            throw new ArgumentNullException("expression");        }        if (!typeof(IQueryable<TData>).IsAssignableFrom(expression.Type))        {            throw new ArgumentOutOfRangeException("expression");        }        Provider = provider;        Expression = expression;    }    public IQueryProvider Provider { get; private set; }    public Expression Expression { get; private set; }    public Type ElementType    {        get { return typeof(TData); }    }    public IEnumerator<TData> GetEnumerator()    {        return (Provider.Execute<IEnumerable<TData>>(Expression)).GetEnumerator();    }    IEnumerator IEnumerable.GetEnumerator()    {        return (Provider.Execute<IEnumerable>(Expression)).GetEnumerator();    }}public class TerryQueryProvider : IQueryProvider{    public IQueryable CreateQuery(Expression expression)    {        Type elementType = TypeSystem.GetElementType(expression.Type);        try        {            return (IQueryable)Activator.CreateInstance(                typeof(QueryableData<>).MakeGenericType(elementType),                new object[] { this, expression });        }        catch        {            throw new Exception();        }    }    public IQueryable<TResult> CreateQuery<TResult>(Expression expression)    {        return new QueryableData<TResult>(this, expression);    }    public object Execute(Expression expression)    {        // ......    }    public TResult Execute<TResult>(Expression expression)    {        // ......    }}

The above two interfaces are not completed. Here is just the schematic code. If we implement these two interfaces, we can use them as follows (of course, such use is meaningless, here is only for demonstration ):

static void Main(string[] args){    QueryableData<String> mydata = new QueryableData<String> {         "TerryLee",        "Cnblogs",        "Dingxue"    };    var result = from d in mydata                 select d;    foreach (String item in result)    {        Console.WriteLine(item);    }}

Now let's analyze the execution process. The first step is to instantiate QueryableData <String> and also instantiate TerryQueryProvider. When the query expression is executed, the CreateQuery method in TerryQueryProvider is called, to construct the expression directory tree. At this time, the query will not be actually executed (that is, delayed loading). Only when we call the GetEnumerator method, the foreach method in the previous example, will call the Execute method in TerryQueryProvider, the query is actually executed, as shown in:

 

Summary

This article describes the two most important interfaces IQueryable and IQueryProvider in the Custom LINQ Provider. I hope to help you. In the next article, we will develop a complete custom LINQ Provider.

Related Articles: build your own LINQ Provider (on): Revealing the Expression Tree

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.