This is the third in a series of posts on LazyLinq, a wrapper to support lazy initialization and deferred disposal of a LINQ query context:
- Introducing LazyLinq: Overview
- Introducing LazyLinq: Internals
- Introducing LazyLinq: Queryability
- Simplifying LazyLinq
- Introducing LazyLinq: Lazy DataContext
Having defined the LazyLinq interfaces and provided concrete implementations, we’re left to provide support for the standard query operators.
Learning from Queryable
Before we try to query ILazyQueryable
, it’s instructive to look at how System.Linq.Queryable
works. There are essentially three types of operators on IQueryable<>
:
- Deferred queries returning
IQueryable<>
: Select, Where, etc. - Deferred query returning
IOrderedQueryable<>
: OrderBy, OrderByDescending, ThenBy, ThenByDescending - Everything else: Aggregate, Count, First, etc.
Reflecting Queryable.Select
, modulo error checking, we see the following:
public static IQueryable<TResult> Select<TSource, TResult>( this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector) { return source.Provider .CreateQuery<TResult>( Expression.Call(null, ((MethodInfo) MethodBase.GetCurrentMethod()) .MakeGenericMethod(new Type[] { typeof(TSource), typeof(TResult) }), new Expression[] { source.Expression, Expression.Quote(selector) })); }
The source
‘s Provider
is used to construct a new query whose expression includes the call to Select
with the given parameters. An ordered query follows a similar pattern, trusting that the query provider will return an IOrderedQueryable<>
as appropriate:
public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>( this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector) { return (IOrderedQueryable<TSource>) source.Provider .CreateQuery<TSource>( Expression.Call(null, ((MethodInfo) MethodBase.GetCurrentMethod()) .MakeGenericMethod(new Type[] { typeof(TSource), typeof(TKey) }), new Expression[] { source.Expression, Expression.Quote(keySelector) })); }
And finally, everything that’s not a query is handled by the provider’s Execute
method:
public static TSource First<TSource>(this IQueryable<TSource> source) { return source.Provider .Execute<TSource>( Expression.Call(null, ((MethodInfo) MethodBase.GetCurrentMethod()) .MakeGenericMethod(new Type[] { typeof(TSource) }), new Expression[] { source.Expression })); }
Querying ILazyQueryable
You may have noticed that the above scenarios map rather closely to the methods provided by ILazyContext
:
-
ILazyQueryable<TContext, TResult, TQuery> CreateQuery<TResult, TQuery>(Func<TContext, TQuery> queryBuilder) where TQuery : IQueryable<TResult>;
-
ILazyOrderedQueryable<TContext, TResult, TQuery> CreateOrderedQuery<TResult, TQuery>(Func<TContext, TQuery> queryBuilder) where TQuery : IOrderedQueryable<TResult>;
-
TResult Execute<TResult>(Func<TContext, TResult> action);
However, rather than expression trees we’re pushing around delegates. Execute
seems pretty simple, so let’s start there:
public static TSource First<TSource, TContext, TQuery>( this ILazyQueryable<TContext, TSource, TQuery> source ) where TQuery : IQueryable<TSource> { Func<TContext, TResult> action = context => ???; return source.Context.Execute(action); }
So our source has a Context
, which knows how to Execute
an action from context
to some result. To find that result, we need to leverage the other property of source: QueryBuilder
. Recalling that QueryBuilder
is a function from TContext
to TQuery
, and that TQuery
is an IQueryable<TSource>
, we see something on which we can execute Queryable.First
:
public static TSource First<TSource, TContext, TQuery>( this ILazyQueryable<TContext, TSource, TQuery> source ) where TQuery : IQueryable<TSource> { Func<TContext, TResult> action = context => source.QueryBuilder(context).First(); return source.Context.Execute(action); }
Now seeing as we have dozens of methods to implement like this, it seems an opportune time for a bit of eager refactoring. Recognizing that the only variance is the method call on the IQueryable
, let’s extract an extension method that does everything else:
private static TResult Execute<TSource, TContext, TResult, TQuery>( this ILazyQueryable<TContext, TSource, TQuery> source, Func<TQuery, TResult> queryOperation ) where TQuery : IQueryable<TSource> { return source.Context.Execute( context => queryOperation(source.QueryBuilder(context))); }
From there, additional lazy operators are just a lambda expression away:
public static TSource First<TSource, TContext, TQuery>( this ILazyQueryable<TContext, TSource, TQuery> source ) where TQuery : IQueryable<TSource> { return source.Execute(q => q.First()); } public static TAccumulate Aggregate<TContext, TSource, TQuery, TAccumulate>( this ILazyQueryable<TContext, TSource, TQuery> source, TAccumulate seed, Expression<Func<TAccumulate, TSource, TAccumulate>> func ) where TQuery : IQueryable<TSource> { return source.Execute(q => q.Aggregate(seed, func)); }
And now having done the hard part (finding an IQueryable
), we can translate that understanding to make similar helpers for queries:
private static ILazyQueryable<TContext, TResult, IQueryable<TResult>> CreateQuery<TSource, TContext, TQuery, TResult>( this ILazyQueryable<TContext, TSource, TQuery> source, Func<TQuery, IQueryable<TResult>> queryOperation ) where TQuery : IQueryable<TSource> { return source.Context.CreateQuery<TResult, IQueryable<TResult>>( context => queryOperation(source.QueryBuilder(context))); } private static ILazyOrderedQueryable<TContext, TResult, IOrderedQueryable<TResult>> CreateOrderedQuery<TSource, TContext, TQuery, TResult>( this ILazyQueryable<TContext, TSource, TQuery> source, Func<TQuery, IOrderedQueryable<TResult>> queryOperation ) where TQuery : IQueryable<TSource> { return source.Context.CreateOrderedQuery<TResult, IOrderedQueryable<TResult>>( context => queryOperation(source.QueryBuilder(context))); }
With similarly trivial query operator implementations:
public static ILazyQueryable<TContext, TResult, IQueryable<TResult>> Select<TContext, TSource, TQuery, TResult>( this ILazyQueryable<TContext, TSource, TQuery> source, Expression<Func<TSource, TResult>> selector ) where TQuery : IQueryable<TSource> { return source.CreateQuery(q => q.Select(selector)); } public static ILazyOrderedQueryable<TContext, TSource, IOrderedQueryable<TSource>> OrderBy<TContext, TSource, TQuery, TKey>( this ILazyQueryable<TContext, TSource, TQuery> source, Expression<Func<TSource, TKey>> keySelector ) where TQuery : IQueryable<TSource> { return source.CreateOrderedQuery(q => q.OrderBy(keySelector)); }
And the end result:
September 12, 2009 at 10:06 pm
[…] Introducing LazyLinq: Queryability […]
September 12, 2009 at 11:00 pm
[…] Introducing LazyLinq: Queryability […]
September 12, 2009 at 11:03 pm
[…] Introducing LazyLinq: Queryability […]
September 12, 2009 at 11:40 pm
[…] Introducing LazyLinq: Queryability […]
September 12, 2009 at 11:41 pm
[…] Introducing LazyLinq: Queryability […]