X

Readable fluent queries with Entity Framework Core

After my first experiments with Query Specification pattern on Entity Framework Core I came to interesting idea – why not using extension methods that wrap query specifications or add directly some more conditions to IQueryable<T> the way that queries are easy to read. Here’s my experiment and thoughts of fluent readable queries.

Warning! This blog post covers my new experiments with readable fluent queries. Don’t take it as a new pattern or final solution or something too serious. I have no idea if this approach is applicable in real codebase and what are the limits or side effects. It’s just an interesting idea to play with.

Wired query specifications

My previous post Implementing Query Specification pattern in Entity Framework Core introduced query specification classes and it was possible to wire these one after another. Here’s the last code example from this post.

var invoices = _dataContext.Invoices.Specify(new DueInvoicesSpecification())
                                    .Specify(new CustomerInvoicesSpecification(100))
                                    .OrderBy(i => i.Customer.Name)
                                    .ToList();

Code itself looks okay to me. Specify() method that applies query specifications looks okay and clean. It doesn’t use actions and funcs line other Entity Framework Core extension methods but still it looks fine and it’s easy to read.

Issues with wired query specifications

One thing started suddenly annoying me when I just watched the wiring of specifications and thought if it’s okay or are there other ways to do it better. Red rectangle on the following image shows the problematic part.

Few things:

  • Names of query specification classes repeat the word Specification, making class names too long.
  • Calls to Specify() method doesn’t add any literal value.
  • Call to Specify() method with query specification class is too long and not enough informative.

We can be sure that the longer the list of specifications grows the uglier the code gets until it’s not easy to follow with eyes.

Introducing readable fluent queries

After some experiments with my previous query specification code I wrote the following extension methods for IQueryable<Invoice>.

public static class InvoiceQueries
{
    public static IQueryable<Invoice> GetOverDueInvoices(this IQueryable<Invoice> query)
    {
        return query.Specify(new DueInvoicesSpecification());
    }

    public static IQueryable<Invoice> ForCustomer(this IQueryable<Invoice> query, int customerId)
    {
        return query.Specify(new CustomerInvoicesSpecification(customerId));
    }
}

These methods are actually dummy wrappers for query specification classes but the their effect is pretty cool. Take a look at the following code example and compare it to one with Specify() methods above.

var invoices = _dataContext.Invoices.GetOverDueInvoices()
                                    .ForCustomer(100)
                                    .OrderBy(i => i.Customer.Name)
                                    .ToList();

Instead of query stuffed with technical lingo we have now query that make clear and honest confession about what they will actually do.

Longer fluent queries

Let’s wire some more specifications to query and see how code looks when using query specifications. I added SentUsing and Total property to Invoice class and created the following specifications.


public class SentUsingEmailSpecification : BaseSpecification<Invoice>
{
    public SentUsingEmailSpecification()
    {
        Criteria = i => i.SentUsing == SendMethodEnum.Email;
    }
}

public class HavingTotalAtLeastSpecification : BaseSpecification<Invoice>
{
    public HavingTotalAtLeastSpecification(double total)
    {
        Criteria = i => i.Total >= total;
    }
}

Now let’s add these new specification to sample query.

var invoices = _dataContext.Invoices.Specify(new DueInvoicesSpecification())
                                    .Specify(new CustomerInvoicesSpecification(100))
                                    .Specify(new SentUsingEmailSpecification())
                                    .Specify(new HavingTotalAtLeastSpecification(150))
                                    .OrderBy(i => i.Customer.Name)
                                    .ToList();

So, readability is gone. When we look at code above it’s messy – we can’t catch what query is doing with taking quick look on it. We have to read through all lines where Specify() method is called to understand what the query is doing.

Using same dirty trick I did in previous post I add two extension methods to wrap new query specifications.

public static IQueryable<Invoice> SentUsingEmail(this IQueryable<Invoice> query)
{
    return query.Specify(new SentUsingEmailSpecification());
}

public static IQueryable<Invoice> HavingTotalAtLeast(this IQueryable<Invoice> query, float total)
{
    return query.Specify(new HavingTotalAtLeastSpecification(total));
}

After making sample query use these extension methods we get it back on track.

var invoices = _dataContext.Invoices.GetOverDueInvoices()
                                    .ForCustomer(100)
                                    .SentUsingEmail()
                                    .HavingTotalAtLeast(150)
                                    .OrderBy(i => i.Customer.Name)
                                    .ToList();

Our query is still readable and communicates its purpose. Query specification classes made sample query ugly and hard to read but extension methods solved these issues.

Pros and cons of fluent queries

It’s good question if we need query specifications when going with extension methods like the ones above. Maybe we can go with extension methods and abandon query specifications as we can host query conditions in extension methods instead. It makes sense but we cannot forget one thing – specification and query specification classes are more familiar to developers.

There’s also another issue with extension methods that query specifications doesn’t have – moving queries through layers of application. Extension method is used where it is called. We can move extension method by using actions but I’m not sure if this is something we want to do. Query specification classes allow us to create instances and use these instances with method calls to service layer or as parameters of command classes.

Although I’m not sure if extension methods like show here are good or bad they still look damn nice and readable to me.

Wrapping up

Playing with patterns and their implementations and stepping further with all kind of random ideas may sometimes lead to something interesting. This time I started with implementation of query specification pattern and got to an idea of fluent readable queries. Although I have not much idea if this idea leads to somewhere or if it is applicable in practice it still seems promising when looking at my readable fluent queries. As it’s not yet safe to go live with my queries I will try these out in some smaller project to see where it leads me.

Liked this post? Empower your friends by sharing it!
Related Post