Create a Builder with Fluent API and C#

Having already read about this elsewhere, I still thought this stuff too much fun to not blog about: creating a builder with fluent API. You can read more about the subject on internet, this blog is just code :)

The code is an example implementation, it’s about the concepts. It is a stripped down but pimped version of an implementation I did at a client I worked. The application needed to provide the users with a way to dynamically construct queries for their database. Some queries came preconfigured and I used the fluent API to create these. It also enabled me to create tests more easily.

First let’s take a look at the object we’re building, the Query.

public class Query
{
    public string TableName { get; set; }
    public IList<string> Columns { get; set; } = new List<string>();
    public IList<Table> Tables { get; set; } = new List<Table>();
    public IList<ICondition> Conditions { get; set; } = new List<ICondition>();
    public IList<OrderBy> OrderBys { get; set; } = new List<OrderBy>();
}

If we build the structure without a builder this would look something like this:

Query query = new Query();
query.TableName = "MainTable";
query.Columns.Add("Column1");
query.Columns.Add("Column2");
Table subTable1 = new Table();
subTable1.TableName = "SubTabel1";
subTable1.ParenKey = "MainTableId";
subTable1.ChildKey = "SubTabel1Id";
query.Tables.Add(subTable1);
CompareCondition compareCondition = new CompareCondition();
compareCondition.Column = "Column2";
compareCondition.Compare = CompareType.Equal;
compareCondition.Value = "SomeValue";
query.Conditions.Add(compareCondition);
OrderBy orderBy1 = new OrderBy();
orderBy1.Column = "Column1";
orderBy1.Direction = OrderByDirection.Ascending;
query.OrderBys.Add(orderBy1);

This is a lot of code, not very readable, not very expressive. So now we will start with the first simple implementation for setting the table name and adding columns, we create the QueryBuilder with the method OnTable and AddColumn.

public class QueryBuilder
{
    private readonly Query _query = new Query();

    public QueryBuilder OnTable(string tableName)
    {
        _query.TableName = tableName;
        return this;
    }
    public QueryBuilder AddColumn(string columnName)
    {
        _query.Columns.Add(columnName);
        return this;
    }
    public Query Done()
    {
        return _query;
    }
}

The interesting part is the return value of this method, it returns the builder itself so we can call AddColumn again and again. This practice called method chaining. We can now use this code like this:

Query query = new QueryBuilder()
    .OnTable("MainTable")
    .AddColumn("Column1")
    .AddColumn("Column2");
    .Done();

The Done method is to signal construction is done and to return the actual Query object. Fairly easy and the results are already more readable code. But we’re not done yet, we’re now going to use a second builder to add the OrderBy and take these concepts even further.

public class OrderByBuilder
{
    private readonly Query _query;
    private readonly QueryBuilder _queryBuilder;
    private readonly OrderBy _orderBy = new OrderBy();

    public OrderByBuilder(Query query, QueryBuilder queryBuilder)
    {
        _query = query;
        _queryBuilder = queryBuilder;
    }

    public OrderByBuilder SetOrderOn(string column)
    {
        _orderBy.Column = column;
        return this;
    }

    public QueryBuilder AndDirectionTo(OrderByDirection orderByDirection)
    {
        orderBy.Direction = orderByDirection;
        _query.OrderBys.Add(_orderBy);
        return _queryBuilder;
    }
}

We use the constructor the pass a reference to the Query object we’re building and also the calling QueryBuilder. This is so we can return to this builder when we’re done with the order by. The previous builder used the Done method to signal the end, for this builder I decided to use the AndDirectionTo method to do this. To use this first we’ll add the following to the QueryBuilder:

public OrderByBuilder AddOrderBy()
{
    OrderByBuilder orderByBuilder = new OrderByBuilder(_query, this);
    return orderByBuilder;
}

Now we can use it like this:

Query query = new QueryBuilder()
    .OnTable("MainTable")
    .AddColumn("Column1")
    .AddColumn("Column2")
    .AddOrderBy()
        .SetOrderOn("Column1")
        .AndDirectionTo(OrderByDirection.Ascending)
    .Done();

We’ve created a little domain language here by using the SetOrderOn().AndDirectionTo() methods. But this code doesn’t prohibit saying AndDirectionTo().SetOrderOn() or even AndDirectionTo().AndDirectionTo(). For this we’re going to use interfaces. We add these:

public interface ISetOrderOn
{
    IAndDirectionTo SetOrderOn(string column);
}

public interface IAndDirectionTo
{
    QueryBuilder AndDirectionTo(OrderByDirection orderByDirection);
}

And modify the OrderByBuilder like this:

public class OrderByBuilder : ISetOrderOn, IAndDirectionTo
{
    private readonly Query _query;
    private readonly QueryBuilder _queryBuilder;
    private readonly OrderBy _orderBy = new OrderBy();

    public OrderByBuilder(Query query, QueryBuilder queryBuilder)
    {
        _query = query;
        _queryBuilder = queryBuilder;
    }

    public IAndDirectionTo SetOrderOn(string column)
    {
        _orderBy.Column = column;
        return this;
    }

    public QueryBuilder AndDirectionTo(OrderByDirection orderByDirection)
    {
        _orderBy.Direction = orderByDirection;
        _query.OrderBys.Add(_orderBy);
        return _queryBuilder;
    }
}

Now we when the SetOrderOn method is called it returns the method of the IAndDirectionTo interface, not the complete builder’s interface. Nifty! We’ll make sure the QueryBuilder forces the first methods to be SetOrderOn using the same ‘trick’:

public ISetOrderOn AddOrderBy()
{
    OrderByBuilder orderByBuilder = new OrderByBuilder(_query, this);
    return orderByBuilder;
}

Now calling AddOrderBy can only be followed by SetOrderOn. But when you look at the usage code, using the AddOrderBy method seems a little redundant. So with I decided to go a little further with adding the Table objects. I’m not completely happy with this solution because the QueryBuilder needs to know too much about the TableBuilder, but it does result in a nicer syntax. Change the QueryBuilder method like this:

public IWithParentField JoinOnTable(string tableName)
{
    TableBuilder tableBuilder = new TableBuilder(_query, this);
    tableBuilder.JoinOnTable(tableName);
    return tableBuilder;
}

Like this we’re able to call the first method in the chain and use the code like this:

Query query = new QueryBuilder()
    .OnTable("MainTable")
    .AddColumn("Column1")
    .AddColumn("Column2")
    .JoinOnTable("SubTabel1")
        .WithParentField("MainTableId")
        .OnChildField("SubTabel1Id")
    .AddOrderBy()
        .SetOrderOn("Column1")
        .AndDirectionTo(OrderByDirection.Ascending)
    .Done();

That’s it. I created a sample project and put it on GitHub.

This blog was also published on my own site: http://www.eelcomulder.nl/2015/10/03/create-a-builder-with-fluent-api-and-c/

You may also like...