fd Blog

Daniel Hilgarth on software development

A Fluent Repository Implementation - Part 2

The last article showed a simple implementation of the Fluent Repository.

Now, if you have a lot of entities it makes sense to extract as much of the repeating code as possible into reusable code to make the actual query implementations to only contain the query logic.

Step 1: Extract general purpose interfaces

In the simple implementation of the last article I didn’t show that it is easy to make the Fluent Repository implementation even more useful by providing some default operations:

  • First()
  • FirstOrDefault()
  • Single()
  • SingleOrDefault()

These are the first ones we are going to extract into their own interface:

public interface IQuery<out T> : IEnumerable<T>
{
    T First();
    T FirstOrDefault();
    T Single();
    T SingleOrDefault();
}

T is the entity type, e.g. Employee. The interface inherits IEnumerable<T>, making it the base interface for all of our queries.

The next thing to extract are the properties that allow a more natural chaining of the primary operations:

public interface IFluentQueryInterface<TEntity, out TQuery>
    where TQuery : IQuery<TEntity>
{
    TQuery And { get; }
    TQuery ThatAre { get; }
}

This interface provides the properties to support the fluent syntax and make the query readable like a sentence. It is important to know that these properties are supposed to be no-ops. In other words, they don’t actually do anything except returning the query. This interface is meant to be used on a class that also implements IQuery<out T>.
The generic parameters are needed here, because the properties need to return the exact query type - e.g. IQueryEmployees. If the properties would only return IQuery<out TEntity> all the primary operations of the actual query interface would no longer be available after using one of these fluent syntax properties.

After we created these general purpose interfaces our concrete query interface now only contains the primary operations and nothing else:

public interface IQueryEmployees : IQuery<Employee>, IFluentQueryInterface<Employee, IQueryEmployees>
{
    IQueryEmployees ByGender(Gender gender);
    IQueryEmployees TeamLeaders { get; }
    // more primary operations
}

This will make it very simple to create additional queries as needed.

Step 2: Extract base classes

To make the actual implementation of additional queries just as simple we need to create a base class that handles all the infrastructure and leaves the concrete query implementation with nothing but the query logic itself.
The implementation of the base class and the queries depends heavily on what data store we are accessing and how we are accessing it.
This article assumes we are accessing a relational database using LINQ to NHibernate.

The base class that all query implementations will use looks like this:

public class NHibernateIQueryableQueryBase<TEntity, TQuery, TQueryInterface>
    : IQuery<TEntity>, IFluentQueryInterface<TEntity, TQueryInterface>
    where TQuery : NHibernateIQueryableQueryBase<TEntity, TQuery, TQueryInterface>, TQueryInterface
    where TQueryInterface : IQuery<TEntity>
{
    private IQueryable<TEntity> _query;

    protected NHibernateIQueryableQueryBase(ISession session)
    {
        if (session == null)
            throw new ArgumentNullException("session");
        _query = session.Query<TEntity>();
    }

    public TQueryInterface And
    {
        get { return (TQuery)this; }
    }

    public TQueryInterface ThatAre
    {
        get { return (TQuery)this; }
    }

    protected IQueryable<TEntity> Query
    {
        get { return _query; }
    }

    public TEntity First()
    {
        return Query.First();
    }

    public TEntity FirstOrDefault()
    {
        return Query.FirstOrDefault();
    }

    public IEnumerator<TEntity> GetEnumerator()
    {
        return Query.GetEnumerator();
    }

    public TEntity Single()
    {
        return Query.Single();
    }

    public TEntity SingleOrDefault()
    {
        return Query.SingleOrDefault();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    protected TQuery UpdateQuery(Func<IQueryable<TEntity>, IQueryable<TEntity>> queryUpdater)
    {
        if (queryUpdater == null)
            throw new ArgumentNullException("queryUpdater");

        _query = queryUpdater(_query);

        return (TQuery)this;
    }
}

There’s quite a lot of code here. I will walk you through this class beginning at the top:

  1. This class implements our two general purpose interfaces IQuery<out T> and IFluentQueryInterface<TEntity, out TQuery>. This removes the necessity to implement these in the actual query implementations. As those interfaces are general purpose the implementations of them are general purpose as well and as such are well suited for a base class.
  2. This class has three generic parameters, all of which are necessary to satisfy the compiler.
    • TEntity and TQueryInterface are necessary to satisfy the generic parameters required by the implemented interfaces.
    • TQuery is needed for the implementation of IFluentQueryInterface<TEntity, out TQuery>.
      As you can see, the implementation of the properties of this interface explicitly cast this to TQuery but the return type actually is TQueryInterface.
      Now, that looks a bit strange but it becomes clear when we have a look at the constraints for the generic parameters:
      • TQuery needs to derive from our base class and needs to implement TQueryInterface.
      • TQueryInterface needs to implement IQuery<TEntity>.

      Casting this directly to TQueryInterface is not possible, because our base class simply doesn’t implement this interface. But we can cast it to TQuery, because TQuery is derived from our base class. TQuery now can be implicitly converted to TQueryInterface because the generic constraint ensures that TQuery implements TQueryInterface.

  3. The constructor takes in an instance of NHibernate’s ISession. This is the session that will be used to execute the query. The constructor directly creates a query without filters for our entity. At this point the query can already be enumerated and would return all entities of type TEntity.
  4. The protected property Query returns the current query so it can be used to implement primary operations that execute the query directly and return only a single entity.
    Example:
    Assume a Setting entity with a Name property. One natural primary operation would be ByName(string name).
    Now assume that the name is unique. In that case it would make sense to return the setting with the specified name directly.
    The implementation would look like this and would make use of the Query property:

     public Setting ByName(string name)
     {
         return Query.SingleOrDefault(x => x.Name == name);
     }
    
  5. The implementations of First, FirstOrDefault, Single and SingleOrDefault are trivial. They simply return the result of calling the respective Queryable extension method on the configured query. This will execute it against the data store.
  6. The implementation of GetEnumerator() is also trivial. It returns the result of calling GetEnumerator() on the configured query, executing it against the data store. That’s actually what will be called when enumerating the query using a foreach loop or similar.
  7. UpdateQuery is a small helper method that makes implementing a concrete query even simpler. It encapsulates the fact that the updated query needs to be re-assigned to the _query field. I forgot that sometimes and so I added that method to get rid of the need to think about it.

Step 3: A concrete query implementation

A concrete query implementation now is as simple as this:

public class Employees : NHibernateIQueryableQueryBase<Employee, Employees, IQueryEmployees>,
                         IQueryEmployees
{
    public Employees(ISession session) : base(session)
    {
    }
    
    public IQueryEmployees TeamLeaders
    {
        get { return UpdateQuery(q => q.Where(x => x.IsTeamLeader)); }
    }
    
    public IQueryEmployees ByGender(Gender gender)
    {
        return UpdateQuery(q => q.Where(x => x.Gender == gender));
    }
    
    // more primary operations
}

It derives from the base class we just discussed and implements a specific query interface.
Because of the base class, it only has to implement those methods and properties that are defined in the query interface itself. The implementation of all the rest is handled by the base class.
Compare that to the implementation in the previous article. And this implementation now is complete, I didn’t omit anything for brevity as I did in the previous implementation.

Conclusion

This article shows how the usage of a base class removes most of the friction of implementing a new query, reducing the overhead tremendously. After the infrastructure has been put into place, creating a new query isn’t much more effort than writing LINQ directly in the domain layer. But it provides a much better abstraction.

Comments