Unreadable Linq
Chad Myers has posted about the query objects with the repository pattern. He is trying to solve the problem of reusable pieces of queries that can be combined together. In theory, this gives you a way to compose queries in a way that hide the actual query logic. This is a good thing.
Unfortunately, the syntax leaves a lot to be desired:
_query = new TopCustomersWithLowDiscountQuery() .IncludePreferred(); var otherQuery = new DeliquentCustomersQuery() .WithPendingLitigation() .WithDebtOver(999); var results = _customers .AsQueryable() .Where(_query.OrElse(otherQuery));
Note how you compose the queries, that is simply unreadable as the query complexity scale up. At the same time, we don't really have a better way of handling that since the from expression will not accept Expression<T> instances directly. What has me worried is the tendency toward over abstraction that I have seen in a few code bases. In particular, I have seen API that takes several Expression<T> objects and compose them in interesting ways. Here is an example:
var customers = repo.FindBy( new TopCustomersWithLowDiscountQuery() .IncludePreferred() .BelowDiscountThreshold(3) .WithMoreSalesThan(500) .AndAlso( new DeliquentCustomersQuery() .WithDebtOver(9999)) );
Oh, this is a really neat trick from a programming perspective, but it makes it very hard to read and understand the code. Try to add ordering and projections to this type of query. And then try to understand the code.
I don't have any great solutions for that, unfortunately, but it is a problem that is worth pointing out.
A better solution would be to integrate that with the Linq query provider to support extracting the expression tree from the query, but that is not practical as an overall solution.
Comments
I think Linq hasn't been around long enough for us to discover the dos and don'ts. My guess is that we will get our share, and then some, of ugly code and things totally unsuited for Linq.
The community will eventually agree on patterns and anti-patterns, but we aren't there yet.
@Ayende
Thanks for the critique. It was my hope that this would spawn some discussion!
The syntax stinks, no doubt. I was trying to make clear examples but it appears I totally failed, lol.
What if the queries didn't allow any options, or they only took parameters (i.e. thresholds and such)?
You could also configure the queries to pull their parameters from somewhere else (a rules engine or something?)
Also, what if I overloaded the && and || operators so you could do something like:
var customers =
repo.Query(
-c
Overloading would make it easier, yes.
I am leery of that because of the experience with NHQG, where the complexity just exploded on us.
If you don't like the syntax, step up to the plate:
What would be your suggestion for what these queries should look like?
I've been toying with the specification pattern in relation to linq lately aswell, and have been able to get stuff like this working:
var otherQuery = from customer in customerRepository
Where f.ex Customer.HasDebtOver() is implemented like so:
[Specification(typeof(CustomerInDebtSpecification))]
public bool HasDebtOver(decimal threshold)
{
return new CustomerInDebtSpecification(threshold).IsSatisfiedBy(this);
}
By manipulating the expression tree before it gets passed onto f.ex Linq 2 Sql, we can then substitute the property/method calls that are decorated with the Specification attribute with the appropriate expression as defined by the specification.
I'm just writing up a post detailing my finished implementation of this which I'll post on my blog soon (http://iridescence.no) - I'd also recommend checking out Luke Marshalls blog (http://mathgeekcoder.blogspot.com/) - he's done some great work with the same ideas.
Good post.
My problem with Linq is that although its object-oriented the queries are too low level, they tell you about the "how" but not the "what" (and sometimes they don't even tell you the how clearly). This gets old very quickly especially when the queries move out of the repositories and into other areas of the codebase.
Linq based DDD Specifications could be the answer as they raise the level a bit, tried using them with Linq to NHibernate and had issues though (ending up trying to use features that are NYI) but I think thats the best hope:
http://iancooper.spaces.live.com/blog/cns!844BD2811F9ABE9C!451.entry
You could then also use this approach:
http://codebetter.com/blogs/david_laribee/archive/2008/06/15/super-models-part-1-sexy-specifications.aspx
Comment preview