LINQ in Action - LINQ Book & News

Linq to Amazon implementation fore steps

On Monday, I have announced an implementation of Linq for Amazon Web Services, that allows to query for books using the following syntax:

var query =
  from book in new Amazon.BookSearch()
  where
    book.Title.Contains("ajax") &&
    (book.Publisher == "Manning") &&
    (book.Price <= 25) &&
    (book.Condition == BookCondition.New)
  select book;


Before getting to the details of the implementation code, I'd like to describe what we need to do in order to be able to use such code.
First, as you can see we work with book objects. So, let's defined a Book class:

public class Book
{
  public IList<String>  Authors;
  public BookCondition  Condition;
  public String         Publisher;
  public Decimal        Price;
  public String         Title;
  public UInt32         Year;
}

Here I use public fields for the sake of simplicity, but properties and private fields would be better.
You can see that this class defines the members we use in our query: Title, Publisher, Price and Condition, as well as others we'll use later for display. Condition is of type BookCondition, which is just an enumeration defined like this:

public enum BookCondition {All, New, Used, Refurbished, Collectible}

The next and main thing we have to do is define this BookSearch class we use to perform the query. This class implements System.Query.IQueryable<T> to receive and process query expressions. IQueryable<T> is defined like this:

interface IQueryable<T> : IEnumerable<T>, IQueryable

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

interface IEnumerable<T> : IEnumerable
{
  IEnumerator<T> GetEnumerator();
}

interface IEnumerable
{
  IEnumerator GetEnumerator();
}

interface IQueryable : IEnumerable
{
  Type ElementType { get; }
  Expression Expression { get; }

  IQueryable CreateQuery(Expression expression);
  object Execute(Expression expression);
}

and finally the IQueryable<T> interface itself of course:

interface IQueryable<T> : IEnumerable<T>, IQueryable, IEnumerable
{
  IQueryable<S> CreateQuery<S>(Expression expression);
  S Execute<S>(Expression expression);
}


It may look like a lot of work... I will try to describe it simply so that you can create your own implementation of IQueryable without too much difficulty once you get to know how the mechanics work.

In order to be able to implement IQueryable, you need to understand what happens behind the scenes. The from..where..select query expression you write in your code is just syntactic sugar that the compiler converts quietly into something else! In fact, when you write:

var query =
  from book in new Amazon.BookSearch()
  where
    book.Title.Contains("ajax") &&
    (book.Publisher == "Manning") &&
    (book.Price <= 25) &&
    (book.Condition == BookCondition.New)
  select book;


the compiler translates this into:

IQueryable<Book> query = Queryable.Where<Book>(new BookSearch(), <expression tree>);

Queryable .Where is a static method that takes as arguments an IQueryable followed by an expression tree.
I can hear you crying out loud: "What the hell is an expression tree?!".
Well, an expression tree is just a way to describe what you wrote after where as data instead of code. And what's the point?
  1. To defer the execution of the query
  2. To be able to analyze the query to do whatever we want in response
In our case, we will go to the web and ask Amazon to return some XML data about books, but we could also translate the query into SQL and execute it against a database (this is what Linq to SQL does!), or do anything else you'd consider useful.

And why is it called a "tree"? Because it's a hierarchy of expressions. Here is the complete expression tree in our case:

Expression.Lambda<Func<Book, Boolean>>(
  Expression.AndAlso(
    Expression.AndAlso(
      Expression.AndAlso(
        Expression.CallVirtual(
          typeof(String).GetMethod("Contains"),
          Expression.Field(book, typeof(Book).GetField("Title")),
          new Expression[] { Expression.Constant("ajax") }),
        Expression.Call(
          typeof(String).GetMethod("op_Equality"),
          null,
          new Expression[] {
            Expression.Field(book, typeof(Book).GetField("Publisher")),
            Expression.Constant("Manning") })),
      Expression.Call(
        typeof(Decimal).GetMethod("op_LessThanOrEqual"),
        null,
        new Expression[] {
          Expression.Field(book, typeof(Book).GetField("Price")),
          Expression.Constant(new Decimal(25), typeof(Decimal)) })),
    Expression.EQ(
      Expression.Convert(
        Expression.Field(book, typeof(Book).GetField("Condition")),
        typeof(BookCondition)),
      Expression.Constant(BookCondition.New))),
  new ParameterExpression[] { book }));


If you look at this tree, you should be able to locate the criteria we have specified in our query. What we will do in the next step is see how all this combines and how we will extract the information from the expression tree to be able to construct a web query to Amazon.
We'll keep that for another post... Stay tuned!

Update: the last part of this series if now available: Linq to Amazon source code
Published Wednesday, June 28, 2006 11:39 PM by Fabrice Marguerie

Comments

 

Linq in Action News said:

As an example that will be included in the Linq in Action book, I've created an example that shows how...
June 28, 2006 3:00 PM
 

Fabrice's weblog said:

As an example that will be included in the Linq in Action book, I've created an example that shows how
June 28, 2006 3:01 PM
 

DotNetKicks.com said:

You've been kicked (a good thing) - Trackback from DotNetKicks.com
July 22, 2006 3:58 AM
 

Paul Kinlan said:

Brilliant article.

Do you have any infomation about the conversion of the expression tree to Amazon REST interface?

Kind Regards,
Paul Kinlan
September 2, 2006 2:02 AM
 

Fabrice Marguerie said:

Paul, I will publish the last part of this series and the source code later this week.
September 4, 2006 5:41 AM
 

Paul Kinlan said:

Brilliant. I can't wait. :)

Kind Regards,
Paul Kinlan
September 5, 2006 3:07 AM
 

Linq in Action News said:

A while ago, I announced Linq to Amazon and started to describe how it's implemented.&amp;nbsp; Actually...
September 7, 2006 1:23 PM
 

Fabrice Marguerie said:

September 7, 2006 1:27 PM
New Comments to this post are disabled