Implementing IQueryable on a Business Logic Layer – Part 2

In Part 1 of this series I explained what sort of situation I am building towards.  In short I am building a highly modularized scaffolding system geared toward a microservice architecture.

Central to the functionality is a data object decorator and a gateway class that completely abstract away Entity Framework.  It was trivial to implement Take() and Skip(), passing the values to an internal IQueryable from EF.  Then I implemented ToList() to do a select as decorator.  As I mentioned in the previous post, that is great for doing linq against the gateway, but you cant cast it to a IQueryable as is expected in various places like WebApi.

Querying about the web I found a project with great potential called ReLink.  The project claims to be used by Entity Framework 7 and NHibernate afterall.  However, as I read through the introduction page I noted the phrase “Specification framework” and realized I may be reaching too deep by doing this first.  So I kept looking.

Moving up the stack I found an interesting repository containing what was called Query Interceptor. The goal of the QueryInterceptor is to intercept the query just before Execute().  This is an interesting concept.  Even so, I decided I would keep looking for something simpler.  I may need to optimize later, so I am keeping these projects bookmarked.

Several times I came across a project called Linq To Anything. I will be honest and say that the name prompted me to overlook it on several occasions. When I was not finding what I knew must exist, I took a closer look.  Linq To Anything is built on QueryInterceptor and System.Linq.Dynamic. This prompted me to try an implementation and to take a look at the code.  It seems deceptively easy to use, but in my experiments so far it seems to fit my needs.

Looking at unit tests is my favorite way to see how a project is meant to be used.  What I found was I could simply implement the properties needed for IQueryable and use LinqToAnything.QueryProvider<T> to expose a protected method I called DataAccessMethod(). Inside this method I specified how to pass Where, OrderBy, Take and Skip.

It ended up looking something like this:

#region IQueryable implementation
        public System.Linq.Expressions.Expression Expression => System.Linq.Expressions.Expression.Constant(this);
        public Type ElementType => typeof(ILoggerEntry);
        public IQueryProvider Provider => new LinqToAnything.QueryProvider<IMySpecificDecorator>(this.ApplyQueryInfo, (qi => this.ApplyQueryInfo(qi).Count()));
        protected IEnumerable<IMySpecificDecorator> ApplyQueryInfo(LinqToAnything.QueryInfo queryInfo)
            foreach (var clause in queryInfo.Clauses.OfType<LinqToAnything.Where>())
                this.Where((System.Linq.Expressions.Expression<Func<IMySpecificDecorator, bool>>)clause.Expression);
            if (queryInfo.OrderBy != null)
                var orderBy = queryInfo.OrderBy.Name;
                if (queryInfo.OrderBy.Direction == LinqToAnything.OrderBy.OrderByDirection.Desc)
                    orderBy += " descending";
                this.CurrentQuery = this.CurrentQuery.OrderBy(orderBy);
            if (queryInfo.Take != null && queryInfo.Take.Value > 0) this.Take(queryInfo.Take.Value);
            if (queryInfo.Skip > 0) this.Skip(queryInfo.Skip);
            return this.ToList();

The key to what is here is in the this.CurrentQuery.  It keeps a reference to the IQueryable as it has it’s various Linq methods called.  Notice I have not implemented OrderBy() and so I simple add it to the current query and execute it before Take() and Skip().

I have to admit, I was headed for a complex implementation before this.  This will work well for my first implementation.  At some point I will re-evaluate it for possible refactoring.  When I do you can be sure I will post about it here.

What do you think of my solution? What has been you experience with IQueryable?

Implementing IQueryable on a Business Logic Layer – Part 2

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s