An idea the team an I had today, was to build a more fluent interface for creating dynamic SQL queries. Here's what I mean:

 1 [TestFixture]
 2   public class when_creating_an_insert_query_for_two_or_more_columns {
 3       [Test]
 4       public void should_return_the_correct_sql() {
 5           var query = Insert.Into<CustomersTable>()
 6               .ValueOf("mo").ForColumn(c => c.FirstName())
 7               .And()
 8               .ValueOf("khan").ForColumn(c => c.LastName())
 9               .End();
10 
11           var expected =
12               "INSERT INTO Customers ( FirstName, LastName ) VALUES ( @FirstName, @LastName );";
13           query.ToSql().ShouldBeEqualTo(expected);
14       }
15   }

It's the responsibility of the query object to prepare the command with the command parameter names and values, so in this test I'm just focused on the raw sql. One of the benefits of this API, is that it's strongly typed, so you can't stick a string in a column represented by a long.

For example, Imagine a customers table that looks like this:

 1 public class CustomersTable : IDatabaseTable {
 2       public string Name() {
 3           return "Customers";
 4       }
 5 
 6       public IDatabaseColumn<long> Id() {
 7           return new DatabaseColumn<long>("Id");
 8       }
 9 
10       public IDatabaseColumn<string> FirstName() {
11           return new DatabaseColumn<string>("FirstName");
12       }
13 
14       public IDatabaseColumn<string> LastName() {
15           return new DatabaseColumn<string>("LastName");
16       }
17   }

Here's what we've got so far for contracts...

 1 public class Insert {
 2       public static ITableSelector<Table> Into<Table>() where Table : IDatabaseTable {
 3           return new TableSelector<Table>();
 4       }
 5   }
 6 
 7   public interface ITableSelector<Table> {
 8       IColumnSelector<Table, ColumnType> ValueOf<ColumnType>(ColumnType value);
 9   }
10 
11   public interface IColumnSelector<Table, ColumnType> {
12       IChainedSelector<Table> ForColumn<TColumn>(Func<Table, TColumn> columnSelection)
13           where TColumn : IDatabaseColumn<ColumnType>;
14   }
15 
16   public interface IChainedSelector<Table> {
17       ITableSelector<Table> And();
18       IQuery End();
19   }

And here's as far as we got with the implementation...

 1 public class TableSelector<Table> : ITableSelector<Table> where Table : IDatabaseTable {
 2       public IColumnSelector<Table, T> ValueOf<T>(T value) {
 3           return new ColumnSelector<Table, T>(value);
 4       }
 5   }
 6 
 7   public class ColumnSelector<Table, T> : IColumnSelector<Table, T> where Table : IDatabaseTable {
 8       private readonly T value;
 9 
10       public ColumnSelector(T value) {
11           this.value = value;
12       }
13 
14       public IChainedSelector<Table> ForColumn<TColumn>(Func<Table, TColumn> columnSelection)
15           where TColumn : IDatabaseColumn<T> {
16           var table = Activator.CreateInstance<Table>();
17           return new ChainedSelector<Table, T, TColumn>(
18               table,
19               value,
20               columnSelection(table)
21               );
22       }
23   }
24 
25   public class ChainedSelector<Table, Value, Column> : IChainedSelector<Table>
26       where Table : IDatabaseTable
27       where Column : IDatabaseColumn<Value> {
28       private readonly Table table;
29       private readonly Value value;
30       private readonly Column column;
31 
32       public ChainedSelector(Table table, Value value, Column column) {
33           this.table = table;
34           this.value = value;
35           this.column = column;
36       }
37 
38       public ITableSelector<Table> And() {
39           throw new NotImplementedException();
40       }
41 
42       public IQuery End() {
43           var builder = new InsertStatementBuilder(table.Name());
44           builder.Add(column, value);
45           return builder.EndQuery();
46       }
47   }

The most important piece is still missing, and that's implementing the "And()" method on ChainedSelector... and finishing off the End method. I'm drawing a blank.. Thoughts are appreciated!

comments powered by Disqus