Friday, April 06, 2007 5:45 PM bart

The IQueryable tales - LINQ to LDAP - Part 2: Getting started with IQueryable<T>

Introduction

Welcome back to the LINQ-to-LDAP series. So far, we've been discussing:

In the previous post, we put ourselves at the side of the compiler either being faced with an IEnumerable<T> or IQueryable<T> object on which a query is to be performed. As you've seen, both cases result in different types of code generation: executable IL code and expression tree representations respectively. In this post, we zoom in to the IQueryable<T> case. More specifically, we'll move focus to the implementation of a query provider that needs to do translation of LINQ queries (fed in through an expression tree) to some specific query language, in our case LDAP. Or, in more techy terms, how to implement IQueryable<T>.

 

Getting started - playground definition

To get started, create a new console application project in C# 3.0 via Visual Studio "Orcas" (alternatively, you can clean the project from the previous post). In the Program.cs file, we'll add a new type called MyDataSource with generic parameter T, that implements IQueryable<T>:

Just implement the interface trivally using the implementation option provided by the "smart tip" (or press SHIFT-ALT-F10). Next, add a simple type called User, defined as follows:

User "entity object" - Copy Code
1 class User 2 { 3 public string Name { get; set; } 4 public int Age { get; set; } 5 }

Again as in the previous post, we're using the C# 3.0 automatic property syntax. Now it's time to move our focus to writing queries, so enter the following query in Main:

A simple query - Copy Code
1 var src = new MyDataSource<User>(); 2 3 var res = from o in src 4 where o.Age >= 24 5 select o.Name;

 

Expression trees in depth

Based on the discussion from the previous post, you should know that the "simple query" code translates into an expression tree because the data source is an IQueryable<T>, in this case IQueryable<User>. Indeed, if you inspect the assembly in IL code, you'll see the code that generates the expression tree being present in the Main method:

.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // Code size 165 (0xa5) .maxstack 5 .locals init ([0] class LINQtoLDAP.MyDataSource`1<class LINQtoLDAP.User> src, [1] class [System.Core]System.Linq.IQueryable`1<string> res, [2] class [System.Core]System.Linq.Expressions.ParameterExpression CS$0$0000, [3] class [System.Core]System.Linq.Expressions.ParameterExpression[] CS$0$0001) IL_0000: nop IL_0001: newobj instance void class LINQtoLDAP.MyDataSource`1<class LINQtoLDAP.User>::.ctor() IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: ldtoken LINQtoLDAP.User IL_000d: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) IL_0012: ldstr "o" IL_0017: call class [System.Core]System.Linq.Expressions.ParameterExpression [System.Core]System.Linq.Expressions.Expression::Parameter(class [mscorlib]System.Type, string) IL_001c: stloc.2 IL_001d: ldloc.2 IL_001e: ldtoken method instance int32 LINQtoLDAP.User::get_Age() IL_0023: call class [mscorlib]System.Reflection.MethodBase [mscorlib]System.Reflection.MethodBase::GetMethodFromHandle(valuetype [mscorlib]System.RuntimeMethodHandle) IL_0028: castclass [mscorlib]System.Reflection.MethodInfo IL_002d: call class [System.Core]System.Linq.Expressions.MemberExpression [System.Core]System.Linq.Expressions.Expression::Property(class [System.Core]System.Linq.Expressions.Expression, class [mscorlib]System.Reflection.MethodInfo) IL_0032: ldc.i4.s 24 IL_0034: box [mscorlib]System.Int32 IL_0039: ldtoken [mscorlib]System.Int32 IL_003e: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) IL_0043: call class [System.Core]System.Linq.Expressions.ConstantExpression [System.Core]System.Linq.Expressions.Expression::Constant(object, class [mscorlib]System.Type) IL_0048: call class [System.Core]System.Linq.Expressions.BinaryExpression [System.Core]System.Linq.Expressions.Expression::GreaterThanOrEqual(class [System.Core]System.Linq.Expressions.Expression, class [System.Core]System.Linq.Expressions.Expression) IL_004d: ldc.i4.1 IL_004e: newarr [System.Core]System.Linq.Expressions.ParameterExpression IL_0053: stloc.3 IL_0054: ldloc.3 IL_0055: ldc.i4.0 IL_0056: ldloc.2 IL_0057: stelem.ref IL_0058: ldloc.3 IL_0059: call class [System.Core]System.Linq.Expressions.Expression`1<!!0> [System.Core]System.Linq.Expressions.Expression::Lambda<class [System.Core]System.Linq.Func`2<class LINQtoLDAP.User,bool>>(class [System.Core]System.Linq.Expressions.Expression, class [System.Core]System.Linq.Expressions.ParameterExpression[]) IL_005e: call class [System.Core]System.Linq.IQueryable`1<!!0> [System.Core]System.Linq.Queryable::Where<class LINQtoLDAP.User>(class [System.Core]System.Linq.IQueryable`1<!!0>, class [System.Core]System.Linq.Expressions.Expression`1<class [System.Core]System.Linq.Func`2<!!0,bool>>) IL_0063: ldtoken LINQtoLDAP.User IL_0068: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) IL_006d: ldstr "o" IL_0072: call class [System.Core]System.Linq.Expressions.ParameterExpression [System.Core]System.Linq.Expressions.Expression::Parameter(class [mscorlib]System.Type, string) IL_0077: stloc.2 IL_0078: ldloc.2 IL_0079: ldtoken method instance string LINQtoLDAP.User::get_Name() IL_007e: call class [mscorlib]System.Reflection.MethodBase [mscorlib]System.Reflection.MethodBase::GetMethodFromHandle(valuetype [mscorlib]System.RuntimeMethodHandle) IL_0083: castclass [mscorlib]System.Reflection.MethodInfo IL_0088: call class [System.Core]System.Linq.Expressions.MemberExpression [System.Core]System.Linq.Expressions.Expression::Property(class [System.Core]System.Linq.Expressions.Expression, class [mscorlib]System.Reflection.MethodInfo) IL_008d: ldc.i4.1 IL_008e: newarr [System.Core]System.Linq.Expressions.ParameterExpression IL_0093: stloc.3 IL_0094: ldloc.3 IL_0095: ldc.i4.0 IL_0096: ldloc.2 IL_0097: stelem.ref IL_0098: ldloc.3 IL_0099: call class [System.Core]System.Linq.Expressions.Expression`1<!!0> [System.Core]System.Linq.Expressions.Expression::Lambda<class [System.Core]System.Linq.Func`2<class LINQtoLDAP.User,string>>(class [System.Core]System.Linq.Expressions.Expression, class [System.Core]System.Linq.Expressions.ParameterExpression[]) IL_009e: call class [System.Core]System.Linq.IQueryable`1<!!1> [System.Core]System.Linq.Queryable::Select<class LINQtoLDAP.User,string>(class [System.Core]System.Linq.IQueryable`1<!!0>, class [System.Core]System.Linq.Expressions.Expression`1<class [System.Core]System.Linq.Func`2<!!0,!!1>>) IL_00a3: stloc.1 IL_00a4: ret } // end of method Program::Main

Two lines in this whole chunk of code are of interest to us for the moment: take a look at lines IL_005e and IL_009e. In here the static class System.Linq.Queryable's extension methods Where and Select are called. Based on this observation, we could rewrite the query code like this:

Resolving extension method calls - Copy Code
1 var res = Queryable.Select( 2 Queryable.Where( 3 src, 4 o => o.Age >= 24), 5 o => o.Name);

In here, I've rewritten the clauses of the query as lambda expressions with dummy variable o. This piece of code is still functionally the same as the original fragment. A question that might pop up in your mind is: "How does the compiler know to translate the lambda expressions into expression trees rather than anonymous methods?". The answer is outlined below in two simple methods:

Code versus data - Copy Code
1 static void LambdaAsCode() 2 { 3 Func<int, int, int> product = (a, b) => a * b; 4 } 5 6 static void LambdaAsData() 7 { 8 Expression<Func<int, int, int>> product = (a, b) => a * b; 9 }

When you take a look at the generated IL-code for both methods, you'll see this:

.field private static class [System.Core]System.Linq.Func`3<int32,int32,int32> '<>9__CachedAnonymousMethodDelegate5' .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) .method private hidebysig static void LambdaAsCode() cil managed { // Code size 34 (0x22) .maxstack 3 .locals init ([0] class [System.Core]System.Linq.Func`3<int32,int32,int32> product) IL_0000: nop IL_0001: ldsfld class [System.Core]System.Linq.Func`3<int32,int32,int32> LINQtoLDAP.Program::'<>9__CachedAnonymousMethodDelegate5' IL_0006: brtrue.s IL_001b IL_0008: ldnull IL_0009: ldftn int32 LINQtoLDAP.Program::'<LambdaAsCode>b__4'(int32, int32) IL_000f: newobj instance void class [System.Core]System.Linq.Func`3<int32,int32,int32>::.ctor(object, native int) IL_0014: stsfld class [System.Core]System.Linq.Func`3<int32,int32,int32> LINQtoLDAP.Program::'<>9__CachedAnonymousMethodDelegate5' IL_0019: br.s IL_001b IL_001b: ldsfld class [System.Core]System.Linq.Func`3<int32,int32,int32> LINQtoLDAP.Program::'<>9__CachedAnonymousMethodDelegate5' IL_0020: stloc.0 IL_0021: ret } // end of method Program::LambdaAsCode .method private hidebysig static int32 '<LambdaAsCode>b__4'(int32 a, int32 b) cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Code size 8 (0x8) .maxstack 2 .locals init ([0] int32 CS$1$0000) IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: mul IL_0003: stloc.0 IL_0004: br.s IL_0006 IL_0006: ldloc.0 IL_0007: ret } // end of method Program::'<LambdaAsCode>b__4' .method private hidebysig static void LambdaAsData() cil managed { // Code size 73 (0x49) .maxstack 4 .locals init ([0] class [System.Core]System.Linq.Expressions.Expression`1<class [System.Core]System.Linq.Func`3<int32,int32,int32>> product, [1] class [System.Core]System.Linq.Expressions.ParameterExpression CS$0$0000, [2] class [System.Core]System.Linq.Expressions.ParameterExpression CS$0$0001, [3] class [System.Core]System.Linq.Expressions.ParameterExpression[] CS$0$0002) IL_0000: nop IL_0001: ldtoken [mscorlib]System.Int32 IL_0006: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) IL_000b: ldstr "a" IL_0010: call class [System.Core]System.Linq.Expressions.ParameterExpression [System.Core]System.Linq.Expressions.Expression::Parameter(class [mscorlib]System.Type, string) IL_0015: stloc.1 IL_0016: ldtoken [mscorlib]System.Int32 IL_001b: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) IL_0020: ldstr "b" IL_0025: call class [System.Core]System.Linq.Expressions.ParameterExpression [System.Core]System.Linq.Expressions.Expression::Parameter(class [mscorlib]System.Type, string) IL_002a: stloc.2 IL_002b: ldloc.1 IL_002c: ldloc.2 IL_002d: call class [System.Core]System.Linq.Expressions.BinaryExpression [System.Core]System.Linq.Expressions.Expression::Multiply(class [System.Core]System.Linq.Expressions.Expression, class [System.Core]System.Linq.Expressions.Expression) IL_0032: ldc.i4.2 IL_0033: newarr [System.Core]System.Linq.Expressions.ParameterExpression IL_0038: stloc.3 IL_0039: ldloc.3 IL_003a: ldc.i4.0 IL_003b: ldloc.1 IL_003c: stelem.ref IL_003d: ldloc.3 IL_003e: ldc.i4.1 IL_003f: ldloc.2 IL_0040: stelem.ref IL_0041: ldloc.3 IL_0042: call class [System.Core]System.Linq.Expressions.Expression`1<!!0> [System.Core]System.Linq.Expressions.Expression::Lambda<class [System.Core]System.Linq.Func`3<int32,int32,int32>>(class [System.Core]System.Linq.Expressions.Expression, class [System.Core]System.Linq.Expressions.ParameterExpression[]) IL_0047: stloc.0 IL_0048: ret } // end of method Program::LambdaAsData

The difference is pretty simple: when the compiler encounters a lambda expression being assigned to an Expression-type variable, it generates an expression tree (e.g. LambdaAsData which contains an expression tree with two parameters and one Multiply node); otherwise, it will generate IL-code (e.g. <LambdaAsCode>b__4 containing the multiplication code).

In our simple code snippet

1 var res = Queryable.Select( 2 Queryable.Where( 3 src, 4 o => o.Age >= 24), 5 o => o.Name);

the two methods Select and Where take an Expression as their second parameter, telling the compiler they want the lambda expression in an expression tree format. For fun and to explain things in depth, let's play the human compiler (or a reverse MSIL-to-C# compiler) that translates both lambda expressions in their equivalent expression tree generation code:

Manual expression tree creation - Copy Code
1 ParameterExpression o = Expression.Parameter(typeof(User), "o"); 2 3 /* o => o.Age >= 24 */ 4 Expression<Func<User, bool>> predicate = 5 Expression.Lambda<Func<User,bool>>( 6 /* >= */ 7 Expression.GreaterThanOrEqual( 8 /* o.Age */ 9 Expression.Property(o, typeof(User).GetProperty("Age")), 10 /* 24 */ 11 Expression.Constant(24) 12 ), 13 /* o */ 14 new ParameterExpression[] { o } 15 ); 16 17 /* o => o.Name */ 18 Expression<Func<User, string>> projection = 19 Expression.Lambda<Func<User, string>>( 20 /* o.Name */ 21 Expression.Property(o, typeof(User).GetProperty("Name")), 22 /* o */ 23 new ParameterExpression[] { o } 24 ); 25 26 var res = Queryable.Select( 27 Queryable.Where( 28 src, 29 predicate), 30 projection);

Again, the code is still functionally the same. Recall our original query definition:

Original query - Copy Code
1 var res = from o in src 2 where o.Age >= 24 3 select o.Name;

Yes, we're still talking about the same thing :-). However, one thing we didn't try yet is to execute this code... On to IQueryable<T> (finally).

 

What IQueryable<T> does - A quick analysis

So, execute the code in the debugger with a magic press of F5 :-). Soon, the program will crash:

Watch the Call Stack window to see how we reached the Expression property of our MyDataSource<T> class that implements IQueryable<T>. Notice I did put the "Show External Code" option of the Call Stack window on:

The second line of the Call Stack is of interest to us and shows that the call to get_Expression originates from Queryable.Where. Recall lines 26 to 30 from our "Manual expression tree creation" code snippet:

1 var res = Queryable.Select( 2 Queryable.Where( 3 src, 4 predicate), 5 projection);

which are equivalent to the following:

Nested call unwinding - Copy Code
1 IQueryable<User> filtered = Queryable.Where(src, predicate); 2 IQueryable<string> projected = Queryable.Select(filtered, projection); 3 4 var res = projected;

Notice that I did make the type of the query's result explicit in line 2; indeed, the projection takes a User object and returns a string, resulting in an IQueryable<string> result type. This reflects the projection lambda expression o => o.Name where o is of type User and o.Name is of type string.

Line 1 of the fragment above explains what's happening right now. Inside the Where method, the first parameter's (which is src, or our MyDataSource<User> instance) Expression property getter is called. Basically, this is LINQ calling our data source with the words: "Dear data source, can you be so kind to represent yourself as an expression?". A good implementation for this property in order to move further in our analysis is shown below:

Copy Code
1 public Expression Expression 2 { 3 get 4 { 5 return Expression.Constant(this); 6 } 7 }

Or, in human words, the query provider responds to LINQ with the words: "Sure, here I am: this constant expression is me.". Restart the application in the debugger; again the app isn't completely happy yet:

As a second step, the Where method calls into our IQueryable<T>'s CreateQuery method. Observe the parameter to the call, this is the predicate from line 1 in the "Nested call unwinding" snippet, wrapped inside a MethodCallExpression to the Where method. Essentially, Queryable.Where has added itself as an expression parent node of type MethodCallExpression with two child nodes:

  • The expression retrieved by calling the src's Expression property from above;
  • the predicate expression that was passed in as the second parameter.

To put it in code, inside Queryable.Where something like this has happened:

public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate) { MethodCallExpression mc = Expression.Call(typeof(Queryable).GetMethod("Where"), source.Expression, predicate); return source.CreateQuery<TSource>(mc); }

In simple words, Queryable's (extension) methods lift themselves in the expression tree, making a MethodCallExpression to themselves with all arguments required, the first of which points to the first parameter (through a call to the Expression property getter of that first parameter).

What does all this magic mean to us? The answer: It's in this place - CreateQuery - that we'll hook up code to parse the where predicate filter clause, resulting in an LDAP query after all (see upcoming posts for details on this). As an implementation, we'll provide some inspection logic that prints a bit of information about the CreateQuery call and its parameters:

Simple CreateQuery implementation
1 public IQueryable<TElement> CreateQuery<TElement>(Expression expression) 2 { 3 Console.WriteLine("MyDataSource<{0}> was asked to return IQueryable<{1}>", typeof(T).Name, typeof(TElement).Name); 4 MethodCallExpression e = (MethodCallExpression)expression; 5 6 Console.WriteLine("-> Expression: {0}", e.Method.Name); 7 foreach (Expression arg in e.Arguments) 8 if (arg is ConstantExpression) 9 Console.WriteLine(" - {0}", (arg as ConstantExpression).Value); 10 else 11 Console.WriteLine(" - {0}", arg); 12 13 Console.WriteLine(); 14 15 return new MyDataSource<TElement>(); 16 }

In here, we print information about the generic parameter T type of the current MyDataSource instance, together with the requested TElement return type. These two can vary since methods like Select take in objects of some type and return another type (think of the projection o => o.Name that translates a User into a string object). In line 4, we assume the expression is a MethodCallExpression, which will be true as explained above (the "lifting" performed by Queryable's methods, such as Where and Select). Next, the arguments are printed in lines 7 to 11 (this code could be "simplified" because Where and Select only have two arguments, but let's keep it a bit generic...). Finally, on line 15, we return what we were asked for (an IQueryable<TElement> object with the desired element type of TElement), just to continue execution.

Now think back of this piece of code:

1 IQueryable<User> filtered = Queryable.Where(src, predicate); 2 IQueryable<string> projected = Queryable.Select(filtered, projection); 3 4 var res = projected;

and imagine what will happen. Indeed, first Where is executed, causing a call to CreateQuery on src, returning an object of type IQuerable<User> that's assigned to the filtered variable. Match this with the signature of Queryable.Where to confirm the generic type parameters match:

public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);

In here TSource is equal to User, thus filtered will return an IQueryable<User>. This result is fed into the call to Select which has the following signature:

public static IQueryable<TResult> Select<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector);

Since the selector (projection) is of type Func<User, string> (o => o.Name), TResult equals to string and the result is an IQueryable<string>. Here's the output of our analysis application:

 

If you revert the Main's code from the long an nasty manual expression generating code and the Where-Select call sequence to the following:

With LINQ query syntax - Copy Code
1 static void Main(string[] args) 2 { 3 var src = new MyDataSource<User>(); 4 5 var res = from o in src 6 where o.Age >= 24 7 select o.Name; 8 }

you should still see the same result when executing the code. In the whole description above, you learned how to translate the LINQ query into an equivalent expression tree representation and how the Queryable.Where and Queryable.Select methods work to complete that expression tree's definition. The result is an IQueryable<string> object in variable res that has captured enough information about the query to start execution, triggered by enumeration as explained in the next section.

 

An IQueryable<T> implementation skeleton

IQueryable<T> is more than the Expression property and the CreateQuery<TElement> method however. It's also about enumeration of the results. Here's the metadata-generated definition of the interface:

// Summary: // Provides functionality to evaluate queries against a specific data source // wherein the type of the data is known. public interface IQueryable<T> : IEnumerable<T>, IQueryable, IEnumerable { // Summary: // Constructs an System.Linq.IQueryable<T> object that can evaluate the query // represented by the specified expression tree. // // Parameters: // expression: // The System.Linq.Expressions.Expression representing the query to be encompassed. // // Returns: // An System.Linq.IQueryable<T> that can evaluate the query represented by the // specified expression tree. IQueryable<TElement> CreateQuery<TElement>(Expression expression); // // Summary: // Executes the query represented by the specified expression tree. // // Parameters: // expression: // The System.Linq.Expressions.Expression that represents the query to be executed. // // Returns: // A value of type TResult representing the result of the specified query. TResult Execute<TResult>(Expression expression); }

In our journey, we'll focus on the CreateQuery method that gets invoked automatically by the Queryable.* extension methods when creating a query object. This is where we need to prepare the query for subsequent execution, which gets triggered by the enumeration. Previously, on line 15 of our "Simple CreateQuery implementation", we did a simple return of

return new MyDataSource<TElement>();

In a real implementation however, we'll need to carry the information-gained-so-far down the drain till we reach the root level of the query. In our simple sample from above, we created a pipeline like this:

    src (MyDataSource<User>) --(Where)--> filtered (MyDataSource<User>) --(Select)--> projected (MyDataSource<string>)

When enumeration happens on the projected variable, like this:

1 IQueryable<User> filtered = Queryable.Where(src, predicate); 2 IQueryable<string> projected = Queryable.Select(filtered, projection); 3 4 foreach (var item in projected) 5 ;

or, equivalently,

1 var res = from o in src 2 where o.Age >= 24 3 select o.Name; 4 5 foreach (var item in res) 6 ;

the query needs to be launched (and not any earlier than the enumeration!). Of course, this implies that the projected variable needs to carry enough information about what happened before it in the pipeline, up to the src variable. Or: projected needs to carry the information gathered in filtered. When additional steps are involved, like sorting, similar things have to happen. Essentially, the pipeline-based construction of IQueryable<T> instances acts as an accumulator that brings together all the pieces of the query in order to be executed. Our implementation of LINQ-to-LDAP will gather the following information:

  • the LDAP query during the "Where" stage;
  • the projected properties (that need to be retrieved from AD) during the "Select" stage.

An example of a more complex query is shown below:

1 var res = from o in src 2 where o.Age >= 24 3 orderby o.Name ascending 4 select o.Name; 5 6 foreach (var item in res) 7 ;

This translates into the following composition:

You'll need to change IQueryable<T> by IOrderedQueryable<T> to make this work. In such a case, the pipeline

    src (MyDataSource<User>) --(Where)--> filtered (MyDataSource<User>) --(OrderBy)--> sorted (MyDataSource<User>) --(Select)--> projected (MyDataSource<string>)

needs to carry information about the sorting operation too, all the way down to the resulting projected IQuerable<T> variable. Feel free to play around with the query to see the result of additional calls, for example by feeding the following into our dummy IQueryable<T> implementation:

Complex query - Copy Code
1 var res = (from o in src 2 where o.Age >= 24 3 group o by o.Age 4 into g 5 select g).Skip(10).Take(5);

Back to the enumeration story for now. As we said, every IQueryable<T> implements IEnumerable<T> which means that a foreach loop can be used directly to iterate over the results. It's only when iteration is started - thus when a call to GetEnumerator is made - that the query should be launched and results should be obtained, possibly in a lazy fashion that gets a few results at a time and uses C# iterators to "yield return" results.

All of this discussion leads to a simple implementation for IOrderedQueryable<T> (or IQueryable<T> if you don't support sorting, both interfaces are the same) that can be your starting point for future investigations (and implementations):

A dummy implemention for IOrderedQueryable<T> - Copy Code
1 class DummyQueryable<T> : IOrderedQueryable<T> 2 { 3 public IQueryable<TElement> CreateQuery<TElement>(Expression expression) 4 { 5 return new MyDataSource<TElement>(); 6 } 7 8 public IQueryable CreateQuery(Expression expression) 9 { 10 return CreateQuery(expression); 11 } 12 13 public TResult Execute<TResult>(Expression expression) 14 { 15 throw new Exception("The method or operation is not implemented."); 16 } 17 18 public object Execute(Expression expression) 19 { 20 throw new Exception("The method or operation is not implemented."); 21 } 22 23 public IEnumerator<T> GetEnumerator() 24 { 25 yield break; 26 } 27 28 IEnumerator IEnumerable.GetEnumerator() 29 { 30 return GetEnumerator(); 31 } 32 33 public Type ElementType 34 { 35 get { return typeof(T); } 36 } 37 38 public Expression Expression 39 { 40 get { return Expression.Constant(this); } 41 } 42 }

You can run any LINQ query against it and you should never get an exception back (neither should you get any useful results of course :-)). By setting breakpoints in CreateQuery<TElement> you can inspect the expression tree that's passed in. Notice we didn't implement Execute which isn't required strictly spoken. This method can be used to take in a whole expression tree (representing a query) and ask the data source to execute it; normally you won't need it for simple implementations.

 

Coming next on the IQueryable tales...

So, that's it for today... In subsequent posts we'll talk about the creation of mapping entity objects (for example to represent users in AD), tree parsing, update support, etc. Next time, we'll start by making a more useful implementation of CreateQuery that's capable of taking in an expression tree and translating it into the corresponding LDAP query. Here's a little example of it:

var res = from usr in users where usr.Name == GetQueryStartWith("A") && usr.LogonCount >= 10 && usr.Description.Contains("Built-in") select new { usr.Name, usr.Description, usr.Groups, MaxLogonCount = n };

becomes

    (&(objectClass=user)(&(&(Name=A*)(LogonCount>=10))(Description=*Built-in*)))

Get ready for a rollercoaster ride - it's going to be heavy!

Read on ... The IQueryable tales - LINQ to LDAP - Part 3: Why do we need entities?

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under: ,

Comments

# New and Notable 156

Saturday, April 07, 2007 5:50 AM by Sam Gentile

A light day out there for the Holiday weekend. LINQ Bart has another series going with The IQueryable

# re: The IQueryable tales - LINQ to LDAP - Part 2: Getting started with IQueryable<T>

Saturday, April 07, 2007 12:25 PM by Roger Jennings

# LINQ to LDAP - Implementation Details

Saturday, April 07, 2007 1:04 PM by ((Research + Development) - Sleep) > 24

Today, Sam Gentile noted a series of posts by Bart De Smet describing (in great detail) how LINQ queries

# The IQueryable tales - LINQ to LDAP - Part 3: Why do we need entities?

Saturday, April 07, 2007 6:31 PM by B# .NET Blog

Introduction Welcome back to the LINQ-to-LDAP series. So far, we've been discussing: Part 0: Introduction

# nteresting finding - 04/07/2007 &laquo; Another .NET Blog

Saturday, April 07, 2007 8:14 PM by nteresting finding - 04/07/2007 « Another .NET Blog

# New "Orcas" Language Feature: Lambda Expressions

Sunday, April 08, 2007 4:22 PM by ScottGu's Blog

Last month I started a series of posts covering some of the new VB and C# language features that are

# New "Orcas" Language Feature: Lambda Expressions

Sunday, April 08, 2007 4:39 PM by BusinessRx Reading List

Last month I started a series of posts covering some of the new VB and C# language features that are

# New "Orcas" Language Feature: Lambda Expressions

Sunday, April 08, 2007 4:40 PM by ASP.NET

Last month I started a series of posts covering some of the new VB and C# language features that are

# The IQueryable tales - LINQ to LDAP - Part 1: Key concepts

Monday, April 09, 2007 6:37 PM by B# .NET Blog

Introduction Welcome to the first real part of our LINQ-to-LDAP series. So far, we've been discussing:

# 新Orcas语言特性:Lambda表达式

Monday, April 09, 2007 10:39 PM by shoutor

什么是Lambda表达式?Lambda表达式为编写匿名方法提供了更简明的函数式的句法,但结果却在编写LINQ查询表达式时变得极其有用,因为它们提供了一个非常紧凑的而且类安全的方式来编写可以当作参数来传递,在以后作运算的函数。

# The IQueryable tales - LINQ to LDAP - Part 4: Parsing and executing queries

Tuesday, April 10, 2007 3:15 PM by B# .NET Blog

Introduction Welcome back to the LINQ-to-LDAP series. So far, we've been discussing: Part 0: Introduction

# Nueva caracter??stica de "Orcas": Expresiones Lambda &laquo; Thinking in .NET

# The IQueryable tales - LINQ to LDAP - Part 5: Supporting updates

Tuesday, April 10, 2007 6:29 PM by B# .NET Blog

Introduction Welcome back to the LINQ-to-LDAP series. So far, we've been discussing: Part 0: Introduction

# Coming soon - The return of IQueryable<T> - LINQ to SharePoint

Friday, April 13, 2007 4:09 PM by B# .NET Blog

Earlier this week, "The IQueryable Tales" were published on my blog, with great success. This series

# Building Custom LINQ Enabled Data Providers using IQueryable<T>

Friday, April 20, 2007 6:16 AM by Tom's MSDN Belux Corner

Bart De Smet wrote a hands-on tutorial that explains quite in-depth how one can build a custom data provider

# New "Orcas" Language Feature: Lambda Expressions

Tuesday, May 08, 2007 1:22 AM by Tyrannosaurus Rex

Last month I started a series of posts covering some of the new VB and C# language features that are

# Community Convergence XXVII

Sunday, May 13, 2007 11:13 PM by Charlie Calvert's Community Blog

Welcome to the 27th Community Convergence. I use this column to keep you informed of events in the C#

# 新Orcas语言特性:Lambda表达式。

Sunday, June 24, 2007 9:39 AM by 勤勤同学

随VS 2005发布的C#2.0引进了匿名方法的概念,允许在预期代理(delegate)值的地方用“行内(in-line)”代码块(code blocks)来做替代。

# The IQueryable tales - LINQ to LDAP - Part 5: Supporting updates

Friday, July 27, 2007 5:42 AM by B# .NET Blog

Introduction Welcome back to the LINQ-to-LDAP series. So far, we&#39;ve been discussing: Part 0: Introduction

# LINQ to Active Directory (formerly known as LINQ to LDAP) is here

Sunday, November 25, 2007 11:23 PM by B# .NET Blog

Within a few seconds from now I&#39;ll hit the &quot;Publish This Project&quot; button in CodePlex. Back

# LINQ to Active Directory (formerly known as LINQ to LDAP) is here

Sunday, November 25, 2007 11:24 PM by Elan Hasson's Favorite Blogs

Within a few seconds from now I&#39;ll hit the &quot;Publish This Project&quot; button in CodePlex. Back

# LINQ to Active Directory (formerly known as LINQ to LDAP) is here

Friday, December 21, 2007 7:44 AM by Developer Blogs

Within a few seconds from now I&#39;ll hit the &quot;Publish This Project&quot; button in CodePlex. Back

# .NET3.5?????????,Lambda????????? | Richie's Blog

Monday, January 21, 2008 8:58 PM by .NET3.5?????????,Lambda????????? | Richie's Blog

Pingback from  .NET3.5?????????,Lambda????????? | Richie's Blog

# [转贴].NET3.5新特性,Lambda表达式

Wednesday, February 13, 2008 5:03 AM by 菩提树下的杨过

【原文地址】New

# New and Notable 156

A light day out there for the Holiday weekend. LINQ Bart has another series going with The IQueryable

# New and Notable 156

Tuesday, December 02, 2008 7:43 PM by Sam Gentile's Blog

A light day out there for the Holiday weekend. LINQ Bart has another series going with The IQueryable Tales - LINQ to LDAP. Part 1 is Key Concepts and Part 2 is Getting Started with IQueryable . CLR Jason continues Disassembling .NET with Appendix B

# ???Orcas???????????????Lambda????????? | A18??????

Monday, December 28, 2009 4:56 PM by ???Orcas???????????????Lambda????????? | A18??????

Pingback from  ???Orcas???????????????Lambda?????????  | A18??????

# ???Orcas???????????????Lambda????????? - Java??????

Thursday, September 02, 2010 6:44 AM by ???Orcas???????????????Lambda????????? - Java??????

Pingback from  ???Orcas???????????????Lambda????????? - Java??????

# RealTime - Questions: "What do we do when any program in codeblocks is not getting built?"

Pingback from  RealTime - Questions: "What do we do when any program in codeblocks is not getting built?"