Mini DSL's Using Extension Methods April, 2010
Earlier this year Jeremy Miller wrote an excellent article for MSDN magazine called Internal Domain Specific Languages. In it he suggests using extension methods as a simple way to create internal DSL's. Currently I'm writing a module to import and export data from a system so I've been mucking around with the ADO.NET API's. The repetitious code was getting to me and so I decided to take Jeremy's advice and go for a more declarative approach by creating a mini DSL.
Here is one of the constructs I was seeing over and over:
string select = string.Format("SELECT TOP {0} * FROM [{1}]", maxResults, tableName); OleDbCommand command = new OleDbCommand(select, _connection); DataSet dataSet = new DataSet(); OleDbDataAdapter adapter = new OleDbDataAdapter(command); adapter.Fill(dataSet);
Now I know we could shorten this up a bit by moving the code around but the mechanics are still there. By taking a more declarative approach we want to do the following:
- Create a command
- Select a maximum amount of records from a table.
- Create an adapter.
- Fill a DataSet
Lets create the following extension methods to accomplish this task in a declarative fashion:
public static class OleDbConnectionExtensions
{
public static OleDbCommand CreateCommand(this OleDbConnection connection)
{
return new OleDbCommand { Connection = connection };
}
}
public static class OleDbCommandExtensions { public static OleDbCommand SelectMaxFromTable(this OleDbCommand command, string tableName, int maxResults) { command.CommandText = string.Format("SELECT TOP {0} * FROM [{1}]", maxResults, tableName); return command; } public static OleDbDataAdapter CreateAdapter(this OleDbCommand command) { return new OleDbDataAdapter(command); } }
public static class OleDbDataAdapterExtensions { public static DataSet CreateAndFillDataSet(this OleDbDataAdapter adapter) { DataSet dataSet = new DataSet(); adapter.Fill(dataSet); return dataSet; } }
DataSet result = _connection.
CreateCommand().
SelectMaxFromTable(columnOptions.TableName, 50).
CreateAdapter().
CreateAndFillDataSet();
Very easy to follow and very reusable. Doesn't this look eerily similar to pipelining in F#?? :)
Now we can go even farther if we want to make this even more terse by adding the following two extension methods (Which are themselves using our new mini DSL):
public static class OleDbConnectionExtensions { public static OleDbCommand SelectMaxFromTable(this OleDbConnection connection, string tableName, int max) { return connection.CreateCommand().SelectMaxFromTable(tableName, max); } }
public static class OleDbCommandExtensions { public static DataSet CreateAndFillDataSet(this OleDbCommand command) { return command.CreateAdapter().CreateAndFillDataSet(); } }
There, much better:
DataSet result = _connection.
SelectMaxFromTable(columnOptions.TableName, 50).
CreateAndFillDataSet();
In our declarative syntax we really didn't care about the minutia of creating the command and adapter so we could eliminate that in our DSL.