Tải bản đầy đủ - 0 (trang)
5 IQueryable and IQueryProvider: LINQ to Amazon advanced edition

5 IQueryable and IQueryProvider: LINQ to Amazon advanced edition

Tải bản đầy đủ - 0trang

IQueryable and IQueryProvider:

LINQ to Amazon advanced edition



475



var query =

from book in new LinqToAmazon.AmazonBookSearch()

where

book.Title.Contains("ajax") &&

(book.Publisher == "Manning") &&

(book.Price <= 25) &&

(book.Condition == BookCondition.New)

select book;



As usual, the key thing the compiler looks at when it’s about to convert such a

query expression into calls to query operators is the type of object the query operates on. In our case, this is an instance of LinqToAmazon.AmazonBookSearch. The

compiler notices that AmazonBookSearch provides an implementation of the

Where operator and so this is what will be invoked when the query is evaluated. Of

course, the real execution only happens when the query is enumerated through a

call to GetEnumerator.

To be able to support richer queries using the same technique, we would have

to implement more operators than just Where. For example, with our first implementation, we get the results in an unspecified order. If we want to sort the

results, we can do it locally using LINQ to Objects. If we want to be able to perform the sort operation on the server, we would have to implement the OrderBy

operator in addition to Where. We would then be able to retrieve the sort information expressed in the query and transmit it as part of the web query. If the server

supports sorting, the results we retrieve would be sorted without having to use

LINQ to Objects afterward on the client.

Another thing that our first implementation doesn’t support is retrieving partial information. If you look at our query’s select clause, you’ll notice that we

return complete information on books. What if we wanted to retrieve only the

titles? It would be more efficient to ask the web server to return only the title of

each book instead of the complete information about it. In order to do this, we

would have to implement the Select operator.

You should start to understand that if we do it this way, the analysis of the query

is scattered in several places: in each operator’s implementation. This tends to

complicate the analysis of the query and makes optimization more difficult.

The IQueryable interface has been designed to help in situations like this. It

allows us to receive all the information contained in a query as one big expression

tree instead of having each operator receive partial information. Once the expression tree is ready, it can be analyzed to do whatever we want in response to the

query. IQueryable defines the pattern for you to gather up a user’s query and

present it to your processing engine as a single expression tree that you can either

transform or interpret.



476



CHAPTER 12



Extending LINQ



When a query works on an object that implements IQueryable, the query

operators that are used are not coming from the System.Linq.Enumerable class,

but from the System.Linq.Queryable class. This class provides all the query operators required by the LINQ query expression pattern implemented using expression trees.

The query operators in the Queryable static class do not actually perform any

querying. Instead, their functionality is to build an expression tree as an instance

of the System.Linq.Expressions.Expression object representing the query to

be performed and then pass that Expression object to the source IQueryable for

further processing.

All the implementations of the query operators provided by the Queryable

class return a new IQueryable that augments that expression tree with a representation of a call to that query operator. Thus, when it comes time to evaluate the

query, typically because the IQueryable is enumerated, the data source can process the expression tree representing the whole query in one batch.

The actual query execution is performed by classes that implement the IQueryable interface, as well as the additional IQueryProvider interface. We’ll now

see how these two types work together and how to implement them.

Getting ready for the implementation

With our first implementation, the queries were applied to an instance of the

LinqToAmazon.AmazonBookSearch type. This type implements IEnumerable. Here is a sample query using the first implementation:

var query =

from book in new LinqToAmazon.AmazonBookSearch()

where

book.Title.Contains("ajax") &&

(book.Publisher == "Manning") &&

(book.Price <= 25) &&

(book.Condition == BookCondition.New)

select book;



In the second implementation, we’re going to create new types that implement

IQueryable and IQueryProvider. The entry point type will be named AmazonBookQueryProvider. This is the class that will implement IQueryProvider. A second

class will provide a generic implementation of IQueryable: the Query class.

Here is how these two classes will allow us to write the same query as earlier

using the second implementation:

var provider = new AmazonBookQueryProvider();

var queryable = new Query(provider);

var query =



IQueryable and IQueryProvider:

LINQ to Amazon advanced edition



477



from book in queryable

where

book.Title.Contains("ajax") &&

(book.Publisher == "Manning") &&

(book.Price <= 25) &&

(book.Condition == BookCondition.New)

select book;



Notice how the query is unchanged. Only the object on which we are performing

the query is different. The use of an implicitly typed local variable through the

var keyword abstracts away the type of the query’s result, but it is different for

each implementation. With the first implementation, the type of the result is

IEnumerable. With the second implementation, the type of the

result is IQueryable.

As we already explained, the difference is that IEnumerable represents an

enumeration, while IQueryable represents a query. An instance of a type that

implements IQueryable contains all the information needed to execute a query.

Think of it as a description of what you want done when the query is enumerated.

Overview of IQueryable and IQueryProvider

Before we move on to the implementation, let’s look at how the IQueryable

and IQueryProvider interfaces are defined.

Here is the declaration of IQueryable:

interface IQueryable : IEnumerable, IQueryable

{

}



This means that we have to implement the members of the following interfaces:

interface IEnumerable : IEnumerable

{

IEnumerator GetEnumerator();

}

interface IEnumerable

{

IEnumerator GetEnumerator();

}

interface IQueryable : IEnumerable

{

Type ElementType { get; }

Expression Expression { get; }

IQueryProvider Provider { get; }

}



478



CHAPTER 12



Extending LINQ



The main element you should pay attention to in the interfaces is the Expression

property of the IQueryable interface. It gives you the expression that corresponds

to the query. The actual query underneath the hood of an IQueryable is an

expression tree of LINQ query operators/method calls. This is the part of the

IQueryable that your provider must comprehend in order to do anything useful.

Note that the IQueryable interface implements IEnumerable so that

the results of the query it encompasses can be enumerated. Enumeration should

force the execution of the expression tree associated with an IQueryable object.

At this time, we’ll translate the expression tree into an Amazon web query and

make the call to Amazon’s web services. This is what the IQueryProvider referenced by an IQueryable instance will do.

We have to implement the members of the IQueryProvider interface in order

to handle the execution of the queries. Here is how it is declared:

public interface IQueryProvider

{

IQueryable CreateQuery(Expression expr);

IQueryable CreateQuery(Expression expr);

object Execute(Expression expr);

TResult Execute(Expression expr);

}



As you can see, the IQueryProvider interface contains two groups of methods, one

for the creation of queries and another of the execution of queries. Each group

contains both generic and nongeneric overloads. Implementing IQueryProvider

may look like a lot of work. Don’t worry. You only really need to worry about the

Execute method. It is the entry point into your provider for executing query

expressions. This is the quintessence of your LINQ provider implementation.

Now that you’ve seen what needs to be implemented to create a complete

LINQ provider, you may start to wonder if it’s not something difficult. Well, it is!

You should never consider the creation of a LINQ provider to be an easy task.

However, things should be a bit easier after you’ve taken a look at our sample

implementation and you’ve been able to see how the mechanics work. The LINQ

to Amazon sample is here to help you make your first steps with IQueryable

without too much difficulty. It contains the bases required for every implementation of a LINQ provider.

Let’s now see how the LINQ to Amazon provider implements IQueryable and

IQueryProvider.



IQueryable and IQueryProvider:

LINQ to Amazon advanced edition



479



12.5.2 Implementation

To implement LINQ to Amazon’s query provider, we reused code provided by

Matt Warren from Microsoft on his blog.2 The code we reuse consists of a generic

implementation of IQueryable (the Query class in the Query.cs file) and a

base implementation of IQueryProvider (the QueryProvider class in the QueryProvider.cs file).

Once you have these classes at hand, what’s left is to create a class that inherits

from QueryProvider and provides an implementation for the Execute method,

and optionally one for the GetQueryText method. Of course, implementing Execute is the most difficult part, precisely because what a LINQ provider does is execute queries!

In our case, this is not so difficult, as you can see. Here is how we implemented

the AmazonBookQueryProvider class:

public class AmazonBookQueryProvider : QueryProvider

{

public override String GetQueryText(Expression expr)

{

AmazonBookQueryCriteria criteria;

var visitor = new AmazonBookExpressionVisitor();

criteria = visitor.ProcessExpression(expr);



Retrieve

criteria



String url = AmazonHelper.BuildUrl(criteria);



Generate

URL



return url;

}

public override object Execute(Expression expr)

{

String url = GetQueryText(expr);

IEnumerable results =

AmazonHelper.PerformWebQuery(url);

return results;

}

}



You can see that the work is greatly simplified because we’d already created the

useful helper classes, AmazonBookExpressionVisitor and AmazonHelper, in the

previous section.



2



Matt Warren provides an introduction to the implementation of an IQueryable provider, as well as

sample source code in his blog. The series of posts is available at the following address: http://

blogs.msdn.com/mattwar/archive/2007/08/09/linq-building-an-iqueryable-provider-part-i.aspx



480



CHAPTER 12



Extending LINQ



If we were to rewrite LINQ to SQL, the Execute method would convert the

entire expression tree it receives as an argument into an equivalent SQL query

and send that query to a database for execution. The LINQ to Amazon implementation instead needs to convert the expression tree into a web request and execute

that request.

We won’t give more details about the implementation here because it would be

too long. You should look at the source code accompanying this book to learn

more. We recommend that you also refer to Matt Warren’s blog posts to fully

understand how to implement a complete LINQ provider.

Before closing this chapter, we think it may be useful to review the execution

of a sample query step by step to help you better understand how an implementation of IQueryable works.



12.5.3 What happens exactly

You may wonder how the mechanism enabled by IQueryable works. We’ll now

quickly depict this mechanism to satisfy your curiosity.

Let’s consider the following sample query that works with an AmazonBookQueryProvider:

var provider = new AmazonBookQueryProvider();

var queryable = new Query(provider);

var query =

from book in queryable

where

book.Title.Contains("ajax") &&

(book.Publisher == "Manning") &&

(book.Price <= 25) &&

(book.Condition == BookCondition.New)

select book;



Each time a query such as this one is written, the compiler generates the following

kind of code:

var provider = new AmazonBookQueryProvider();

var queryable = new Query(provider);

IQueryable query =

Queryable.Where(queryable, );



Queryable.Where is a static method that takes as arguments an IQueryable

followed by an expression tree. The Queryable.Where method returns the result

of a call to the provider’s CreateQuery method.

In our case, the source IQueryable is an instance of the Query

class. The implementation of CreateQuery provided by the base QueryProvider



Summary



481



class creates a new Query instance that keeps track of the expression

tree. We don’t support complex queries, so CreateQuery is called only once in our

case, but in richer implementations CreateQuery could be invoked several times in

cascade to create a deep expression tree.

The next operation is the enumeration of the query. Typically, this happens in

a foreach loop in which you process the results. Enumerating the query invokes

the GetEnumerator method of the Query object.

In response to a call to the GetEnumerator method, the Execute method of the

provider is invoked. This is where we parse the expression tree, generate the corresponding web query, call Amazon, and build a list of AmazonBook objects based on

the response we get. Finally, we return the list of books as the result of the Execute

method, and that becomes the result of the GetEnumerator method. The query

execution is then complete and the list of books is now ready to be processed.

That’s all for our LINQ to Amazon example. Implementing IQueryable

enables powerful scenarios that integrate LINQ with a lot of different data sources.

This powerful extensibility option is not easy to implement, which is why we recommend you take a look at other implementations to make sure you fully understand

how IQueryable works if you plan on creating your own implementation.



12.6 Summary

Summary



In this chapter, we presented options available to extend LINQ and adapt it to

your needs. The sample extensions we demonstrated here are simple. It will be

interesting to see how many real-life alternate implementations and extensions

are released as people find flaws or shortcomings in the default set.

LINQ’s extensibility is what allows it to offer support for several data sources.

It’s also what will allow wide adoption of LINQ by developers in all layers of applications. As LINQ gets adopted, we are likely to see more and more framework providers adding LINQ support to their products to offer their users the benefits of

strongly typed and standard querying capabilities.



LINQ in every layer



This chapter covers:





The LinqBooks application







N-tier architecture







Third-party LINQ providers







The future of LINQ



482



Overview of the LinqBooks application



483



Congratulations! You’ve reached the last chapter of this book. You should now

have a good grasp of LINQ’s capabilities and should now be able to put the skills

you’ve acquired into practice in your projects. There is still one last subject we’d

like to cover: the place of LINQ in your applications.

As you know, LINQ is not only a generic querying technology but a complete set

of tools you can use to deal with relational databases, XML, DataSets, in-memory

object collections, and many more data sources thanks to LINQ’s extensibility. This

means that from now on, LINQ is likely to become pervasive in your code.

In this chapter, we will look at a sample application we’ve created using LINQ.

This application is the LinqBooks running example that we introduced in chapter 4 and that we used over this book’s chapters as the base for the code samples.

You’ll be able to find the complete source code in the downloadable package

that accompanies this book. By looking at the LinqBooks sample, you’ll be able

to identify where and how each LINQ flavor is used. Our goal is to help you

decide whether you need to use LINQ in your application layers, as well as see

what impact it can have on your applications’ architecture.

We’ll start by describing the LinqBooks application. We’ll then focus on the

place LINQ to SQL has in this application. LINQ to SQL is likely to influence your

application’s architecture, so it’s important to spend time thinking about how to

use it. Once we’re done with LINQ to SQL, we’ll analyze where LINQ to XML and

LINQ to DataSet can be useful. Finally, we’ll use LINQ’s extensibility through custom query operators as well as our LINQ to Amazon implementation.



13.1 Overview of the LinqBooks application

We already introduced our sample application in chapter 4. Let’s present it again,

but with an accent on the architecture and the use of LINQ. The LinqBooks application allows its users to manage a personal book catalog.



13.1.1 Features

Here are the main features of the LinqBooks application:





Tracking books users own







Storing what users think about them







Retrieving more information about books from external sources







Publishing the list of books and review information to make it available to

others



484



CHAPTER 13



LINQ in every layer



The technical features implemented include





Adding books, publishers, and authors to the catalog







Attaching books to publishers and authors







Adding reviews to books







Querying/inserting/updating data in a local SQL Server database







Searching over the local catalog







Searching over Amazon’s catalog







Importing data about books from Amazon







Importing and persisting some data from/as XML documents







Creating RSS feeds for the books you recommend



Let’s now get an idea of the UI that exposes these features.



13.1.2 Overview of the UI

We decided to implement LinqBooks as a web application. It also comes with a

utility for importing data from Amazon that is implemented as a Windows Forms

application.

Let’s see some screenshots of the application. Figure 13.1 shows the page that

displays the list of books from the database.



Figure 13.1

Web page that displays the

list of books in a grid as

well as some statistics



Overview of the LinqBooks application



485



The page that displays the details for a book can be seen in figure 13.2.

Figure 13.3 shows the page that displays the list of publishers.

There are more pages in the LinqBooks application, of course. You’ll discover

some in the rest of this chapter, and all of them if you look at the source code and

run the application.

Let’s now give you an overview of the data model used by the sample application.



Figure 13.2

Web page that displays the

details about a book and allows

us to add authors and reviews



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

5 IQueryable and IQueryProvider: LINQ to Amazon advanced edition

Tải bản đầy đủ ngay(0 tr)

×