Simplifying LazyLinq
This is the fourth 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
As I was iterating on the proof of concept for LazyLinq, I always wanted to get rid of the TQuery
type parameter. I thought I needed it to distinguish between ordered and unordered wrapped queries, but it just felt messy. The underlying provider mechanism didn’t need it, so why should I?
Well after taking a closer look at the SQL query provider, I figured out how to eliminate it. The object of inspiration is System.Data.Linq.DataQuery<T>
, defined as follows:
internal sealed class DataQuery<T> : IOrderedQueryable<T>, IQueryable<T>, IOrderedQueryable, IQueryable, IEnumerable<T>, IEnumerable, IQueryProvider, IListSource
The key was realizing that IOrderedQueryable<>
and ILazyOrderedQueryable<>
don’t actually do anything. Implementation-wise, they’re just IQueryable<>
or ILazyQueryable<>
with an extra interface on top. It’s only on the design side that it actually matters, essentially providing a hook for additional ordering with ThenBy
. In LINQ to SQL’s case, that means supporting orderability is as simple as specifying that the query object is both IQueryable<>
and IOrderedQueryable<>
.
So how does this revelation simplify Lazy LINQ? First, it allows us to remove TQuery
from the interfaces:
public interface ILazyContext<TContext> : IDisposable { TContext Context { get; } ILazyQueryable<TContext, TResult> CreateQuery<TResult>(Func<TContext, IQueryable<TResult>> queryBuilder); TResult Execute<TResult>(Func<TContext, TResult> action); } public interface ILazyQueryable<TContext, TSource> : IQueryable<TSource> { ILazyContext<TContext> Context { get; } Func<TContext, IQueryable<TSource>> QueryBuilder { get; } } public interface ILazyOrderedQueryable<TContext, TSource> : ILazyQueryable<TContext, TSource>, IOrderedQueryable<TSource> { }
Note that we can also eliminate ILazyContext.CreateOrderedQuery()
, instead assuming that CreateQuery()
will return something that can be treated as ILazyOrderedQueryable<>
as necessary.
For the concrete implementations, we take the cue from DataQuery<T>
, letting LazyQueryableImpl
implement ILazyOrderedQueryable<>
so we can eliminate LazyOrderedQueryableImpl
:
class LazyQueryableImpl<TContext, TSource> : ILazyQueryable<TContext, TSource>, ILazyOrderedQueryable<TContext, TSource> { // Implementation doesn't change }
Finally, our sorting query operations will look more like their counterparts in System.Linq.Queryable
, casting the result of CreateQuery()
to ILazyOrderedQueryable<>
. To keep things readable, we’ll split our CreateOrderedQuery<>
helper into separate versions for OrderBy
and ThenBy
. Note how the types of queryOperation
map to the usage of OrderBy
(unordered to ordered) and ThenBy
(ordered to ordered):
private static ILazyOrderedQueryable<TContext, TResult> CreateOrderByQuery<TSource, TContext, TResult>( this ILazyQueryable<TContext, TSource> source, Func<IQueryable<TSource>, IOrderedQueryable<TResult>> queryOperation ) { return (ILazyOrderedQueryable<TContext, TResult>) source.Context.CreateQuery<TResult>( context => queryOperation(source.QueryBuilder(context))); } private static ILazyOrderedQueryable<TContext, TResult> CreateThenByQuery<TSource, TContext, TResult>( this ILazyQueryable<TContext, TSource> source, Func<IOrderedQueryable<TSource>, IOrderedQueryable<TResult>> queryOperation ) { return (ILazyOrderedQueryable<TContext, TResult>) source.Context.CreateQuery<TResult>( context => queryOperation((IOrderedQueryable<TSource>) source.QueryBuilder(context))); }
Removing TQuery
from the query operators is left as an exercise for the reader. Or you can just get the source on CodePlex.
A Note on IOrderedEnumerable<>
Having taken advantage of how LINQ to IQueryable handles orderability, it’s worth pointing out that LINQ to Objects uses a different approach, specifying new behavior in IOrderedEnumerable<>
that is used to support multiple sort criteria:
public interface IOrderedEnumerable<TElement> : IEnumerable<TElement>, IEnumerable { IOrderedEnumerable<TElement> CreateOrderedEnumerable<TKey>( Func<TElement, TKey> keySelector, IComparer<TKey> comparer, bool descending); }