Creating a monster: The DataTable meets dynamic

9. februar 2010

This is just for fun, so don't use this at work! :-)

Some day ago we had a discussion on Twitter about using the DataTable and DataSet types in .Net. We talked about the differences between typed and untyped DataSets and the merits of both. 

Personally I don't like typed DataSets, not only do I see them as unnecessary, but the DataSet Designer in Visual Studio is terribly buggy and unstable. The one place where typed DataSets have an advantage is in code readability - using property names is easier to read than accessing the DataRow using the indexer property.

Having just read about the dynamic type support in C#4 I got the idea that we could use it to get the pretty syntax of the typed DataSets without all their extra annoyances. Not that it would be very useful, but it would be a good chance to get to know dynamic typing better while writing some fun code. After all, this combines the "old" techonology of the DataTables with the new fancy technology of dynamic types. What could be better?

What I would like to do

Since this is just for fun, I don't care about writing a very robust implementation. What I want is a way to do the following:

// Create a DataTable object
var tb = new DynamicDataTable();

// Add some columns using old-style syntax
tb.Columns.Add("aa", typeof(string));
tb.Columns.Add("bb", typeof(Int32));

// Add a row using dynamic syntax
dynamic row = tb.NewRow();
row.aa = "Dynamic here!";
row.bb = 42;
tb.Rows.Add(row)

// Access column values using property accessors instead of using the indexer
Console.WriteLine("AA: " + row.aa);
Console.WriteLine("BB: " + row.bb);

Note that this syntax is somewhere between the regular DataTable syntax and the syntax for typed DataTables.

Implementing it

I implemented the DynamicDataTable using three classes: The DynamicDataTable itself, a new DynamicRowsCollection that replaces the DataRowCollection of regular DataTables and the DynamicDataRow that extends the DataRow type with dynamic capabilities.

The DynamicDataTable simply wraps a DataTable and overrides or overwrites a few methods. By overriding the NewRowFromBuilder, GetRowType methods and then overwriting the NewRow method I can force the DynamicDataTable to return my DynamicDataRow objects. In addition I overwrite the Rows collection with my own DynamicRowsCollection type to ensure that we only store DynamicDataRow objects.

public class DynamicDataTable : DataTable
{

  private DynamicRowsCollection _rows;

  protected override DataRow NewRowFromBuilder(DataRowBuilder builder)
  {
    return new DynamicDataRow(builder);
  }

  protected override Type GetRowType()
  {
    return typeof(DynamicDataRow);
  }

  public new DynamicRowsCollection Rows
  { 
    get
    {
      if (_rows == null)
      {
        _rows = new DynamicRowsCollection(base.Rows);
      }
      return _rows;
    }
  }

  public new DynamicDataRow NewRow()
  {
    return (DynamicDataRow)base.NewRow();
  }

}

I then wrote a DynamicRowsCollection type as a replacement for the old RowCollection. It implements the IList<DynamicDataRow> interface but all it does is to reroute all method calls to the contained RowCollection:

public class DynamicRowsCollection : IList<DynamicDataRow>
{

  private DataRowCollection _rows;

  public DynamicRowsCollection(DataRowCollection rows
  {
    _rows = rows;
  }

  #region IList implementation

  // Snipped lots of dumb code...

Finally I implemented the DynamicDataRow. This is my specialized DataRow that reroutes all attemps at getting or setting properties to access the indexer instead. I hit upon a challenge here where I first rerouted all property calls, also those that were to existing properties. This meant that trying to access the DataRow.Table property ended up in an attemt to access a column named Table. To fix this I added a check of whether there already exists a property with the given name. I should also add the same check when setting property values, but I haven't done so yet.

public class DynamicDataRow : System.Data.DataRow, IDynamicMetaObjectProvider
{

  public DynamicDataRow(DataRowBuilder builder) : base(builder)
  { }

  public DynamicMetaObject GetMetaObject(Linq.Expressions.Expression parameter)
  {
    return new DataRowDynamicMetaObject(parameter, this);
  }

  private class DataRowDynamicMetaObject : DynamicMetaObject
  {

    private DataRow _row;

    private DataTable _table;

    public DataRowDynamicMetaObject(Expression parameter, DataRow row)
      : base(parameter, BindingRestrictions.Empty, row)
    {
      _row = row;
      _table = row.Table;
    }

    public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
    {

      // Look for an existing property with this name
      var existingProperty = typeof(DataRow).GetProperty(binder.Name);

      if (existingProperty != null)
      {
        // Call the property directly
        var callProperty = Expression.Property(Expression.Constant(_row),  existingProperty);
        var callPropertyConverted = Expression.Convert(callProperty, binder.ReturnType);
        return new DynamicMetaObject(callPropertyConverted, BindingRestrictions.GetInstanceRestriction(Expression, _row), _row);
      }
      else
      {
        // Try to find a column with the name of the property
        var indexer = typeof(DataRow).GetProperty("Item", new Type[] { typeof(string) });
        var getRow = Expression.Constant(_row);
        var colName = Expression.Constant(binder.Name);
        var getIndexerValue = Expression.Property(getRow, indexer, colName);
        return new DynamicMetaObject(getIndexerValue, BindingRestrictions.GetInstanceRestriction(Expression, _row), _row);
      }

      public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value)
      {

        var indexer = typeof(DataRow).GetProperty("Item", new Type[] { typeof(string) });
        var getRow = Expression.Constant(_row);
        var colName = Expression.Constant(binder.Name);
        var setIndexerValue = Expression.Property(getRow, indexer, colName);
        var newValue = Expression.Constant(value.Value, typeof(object));
        var assignColValue = Expression.Assign(setIndexerValue, newValue);
        return new DynamicMetaObject(assignColValue, BindingRestrictions.GetInstanceRestriction(Expression, _row), _row);
      }
    }
  }

The first thing to note here is that the DynamicDataRow itself is really simple. All it does is to inherit from the DataRow and add an implementation of the IDynamicMetaObjectProvider interface. This interface has only one method named GetMetaObject. My implementation of GetMetaObject is also simple - all it does is to return an instance of a DataRowDynamicMetaObject.

The DataRowDynamicMetaObject is more complex. It implements the BindGetMember and BindSetMember methods that handles all property calls for the dynamic object.

The implementation of BindGetMember will first check if there exists a property with the requested name. This is done by checking the return value of the Type.GetProperty method. If a property exists we just call itand return the value. If the property doesn't exist, we first get a reference to the indexer property of the DataRow. We then call the indexer passing it the name of the property/column we ask for and return the value.

The implementation of BindSetMember is somewhat easier since we don't care about existing properties here. The code is similar to that for BindGetMember but we end up with an Assign expression where we assign the new value to the column.

Possible improvements

My code is not at all meant to be used in a real-life scenario, so I haven't cared about performance of error checking at all. The most obvious improvement would be to cache the generated DynamicMetaObject objects for each property. This could be done using a simple Dictionary shared between all instances of the DataRowDynamicMetaObject type. In addition we should fix the BindSetMember method to check if the property exists on the DataRow type. On top of that we should add some error checking code to make sure the types we assign to the columns are compatible with the columns data type.

Another nice improvement would be to add new columns to the DataTable the first time we assigned a value to a property. This would save us the calls to DataTable.Columns.Add that defines the table.

Conclusion

The dynamic type support in .Net 4 is really nice and opens up some really nice possibilities. It also enables some terrible hacks like the DynamicDataTable.

Feel free to download the source code if you want: DynamicDataTable.zip (4.46 kb)

 

kick it on DotNetKicks.com

.NET

Comments

09.02.2010 15:40:06 #
Creating a monster: The DataTable meets dynamic

You've been kicked (a good thing) - Trackback from DotNetKicks.com
09.02.2010 21:52:46 #
Pingback from topsy.com

Twitter Trackbacks for
        
        It should be fun | Creating a monster: The DataTable meets dynamic
        [rag.no]
        on Topsy.com
12.02.2010 11:08:08 #
Update: The DataTable meets dynamic - improved!

Update: The DataTable meets dynamic - improved!
26.02.2014 10:29:51 #
I think that the dynamic type is really nice, I talk about the one supported in .Net 4

Add comment




  Country flag

The number (5) not with letters. This is to stop spammers.

biuquote
  • Comment
  • Preview
Loading