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:

  1. Introducing LazyLinq: Overview
  2. Introducing LazyLinq: Internals
  3. Introducing LazyLinq: Queryability
  4. Simplifying LazyLinq
  5. 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);
}

Related Articles:

About Keith Dahlby

I'm a .NET developer, Git enthusiast and language geek from Cedar Rapids, IA. I work as a software guru at J&P Cycles and studied Human-Computer Interaction at Iowa State University.
This entry was posted in ILazyContext, ILazyQueryable, LazyLinq, LINQ, LINQ to SQL, Queryable. Bookmark the permalink. Follow any comments here with the RSS feed for this post.