Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VB
Print

Data Access Using Dynamics - Part I: The DynamicDataReader

5.00/5 (2 votes)
25 May 2011CPOL3 min read 20.5K  
The DynamicDataReader provides a means of retrieving data from a database in a way that involves less code and increases code readability.

A Trip Down Memory Lane

Way back in the mists of time, before the rise of ORMs such as Entity Framework, LINQ-to-SQL, or NHibernate, data access was typically performed using the ADO.NET DataReader and DataSet classes. To most .NET developers, I am sure the following scenario will be familiar:

// C#
using (SqlConnection connection = new SqlConnection(
       ConfigurationManager.ConnectionStrings["MyDatabase"].ConnectionString))
{
    string sql = "SELECT SomeColumn, AnotherColumn FROM SomeTable";
    SqlCommand command = new SqlCommand(sql, connection);
    connection.Open();
    IDataReader reader = command.ExecuteReader();
    while (reader.Read())
    {
        int foo = reader.GetInt32(reader.GetOrdinal("SomeColumn"));
        string bar = reader.GetString(reader.GetOrdinal("AnotherColumn"));
        // Do some stuff with the data.
    }
}

Now recently, whilst having to work directly with the ADO.NET classes for the first time in a very long time, I decided to experiment with trying to leverage the Dynamic Language Runtime (DLR) to create a set of loosely-typed data-access classes which can be used in a more object-oriented manner and hopefully aid code readability to boot.

The Dynamic DataReader

In this article, we are going to look at the DynamicDataReader class, which acts as a dynamic wrapper around the System.Data.Common.DbDataReader class. However, before we start looking at the code for this class in great detail, let's take a look at one of its parent classes: the DynamicDataWrapper class:

// C#
public abstract class DynamicDataObjectWrapper<T> : DynamicObject
{
    protected T Obj { get; private set; }
    protected Type ObjType { get; private set; }

    public DynamicDataObjectWrapper(T obj)
    {
        this.Obj = obj;
        this.ObjType = obj.GetType();
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder, 
           object[] args, out object result)
    {
        try
        {
            result = ObjType.InvokeMember(binder.Name, 
              BindingFlags.InvokeMethod | BindingFlags.Instance | 
              BindingFlags.Public, null, Obj, args);
            return true;

        }
        catch (Exception)
        {
            result = null;
            return false;
        }
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        PropertyInfo property = ObjType.GetProperty(binder.Name, 
           BindingFlags.Instance | BindingFlags.Public);
        if (property != null)
        {
            result = property.GetValue(Obj, null);
            return true;
        }
        else
        {
            result = null;
            return false;
        }
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        PropertyInfo property = ObjType.GetProperty(binder.Name, 
               BindingFlags.Instance | BindingFlags.Public);
        if (property != null)
        {
            property.SetValue(Obj, value, null);
            return true;
        }
        else
            return false;
    }
}

This class acts as dynamic wrapper for any non-dynamic object and, as such, inherits from DynamicObject. The wrapper overrides the TryGetMember(), TrySetMember(), and TryInvokeMember methods such that any calls made on the dynamic object are passed to the underlying wrapped object. For example, if a call is made to a method called Foo() on the dynamic object, the Foo() method (if it exists) is called on the wrapped object.

Extending this is the DynamicEnumerableDataObjectWrapper, which is a subclass of DynamicDataObjectWrapper which implements the IEnumerable interface by passing calls to the GetEnumerator() method of the wrapped object:

// C#
public abstract class DynamicEnumerableDataObjectWrapper<T> : 
       DynamicDataObjectWrapper<T>, IEnumerable
       where T : IEnumerable
{
    public DynamicEnumerableDataObjectWrapper(T obj)
        : base(obj)
    {
    }

    public virtual IEnumerator GetEnumerator()
    {
        return Obj.GetEnumerator();
    }
}

The DynamicDataReader class is, in turn, a concrete implementation of DynamicEnumerableDataObjectWrapper which wraps a DbDataReader object:

// C#
public class DynamicDataReader : DynamicEnumerableDataObjectWrapper<DbDataReader>
{
    public DynamicDataReader(DbDataReader reader)
        : base(reader)
    {
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        if (base.TryGetMember(binder, out result))
            return true;
        else
        {
            try
            {
                if (!Obj.IsDBNull(Obj.GetOrdinal(binder.Name)))
                    result = Obj.GetValue(Obj.GetOrdinal(binder.Name));
                else
                    result = null;
                return true;
            }
            catch (Exception)
            {
                result = null;
                return false;
            }
        }
    }

    public override bool TryGetIndex(GetIndexBinder binder, 
           object[] indexes, out object result)
    {
        try
        {
            object index = indexes[0];
            if (index is int)
            {
                int intIndex = (int)index;
                if (!Obj.IsDBNull(intIndex))
                    result = Obj.GetValue(intIndex);
                else
                    result = null;
                return true;
            }
            else if (index is string)
            {
                string strIndex = (string)index;
                if (!Obj.IsDBNull(Obj.GetOrdinal(strIndex)))
                    result = Obj.GetValue(Obj.GetOrdinal(strIndex));
                else
                    result = null;
                return true;
            }
            else
            {
                result = null;
                return false;
            }
        }
        catch(Exception)
        {
            result = null;
            return false;
        }
    }

    public static implicit operator DbDataReader(DynamicDataReader reader)
    {
        return reader.Obj;
    }

    public static explicit operator DynamicDataReader(DbDataReader reader)
    {
        return new DynamicDataReader(reader);
    }
}

Firstly, note how we override the TryGetMember() method again. In this override, we first call the base method to test whether the member exists on the wrapped object. If it does, we return its value; otherwise, we attempt to read the appropriate column from the wrapped data reader. If this fails (i.e., the named column does not exist), the method returns false, causing a RuntimeBinderException to be thrown at run-time.

Secondly, we also provide an override for the TryGetIndex() method, which allows us to retrieve column data using an indexer.

Also, note how we also provide two conversion operators for converting between the original object and its wrapper. This allows us to easily use our dynamic wrapper object in places where the framework is expecting the original wrapped object; and to quickly wrap an unwrapped object.

Some Examples

Here are some examples of our DynamicDataReader in action. All the examples use the Northwind database and are designed to show the DynamicDataReader in action. They are certainly not intended as an example of data-access best-practice (embedded SQL being a case in point)! Firstly, a custom HTTP handler to render a table of employees:

// C#
public class DataReaderExample : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        context.Response.ContentType = "text/html";
        context.Response.Write("<html><body>");
        context.Response.Write("<h1>Employees:</h1>");
        context.Response.Write("<table><thead><tr>" + 
                               "<th>Employee Number</th><th>" + 
                               "Surname</th><th>First Name</th>" + 
                               "<th>Date of Birth</th>
                               </tr></thead><tbody>");
        using (SqlConnection connection = new SqlConnection(
               WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString))
        {
            string query = "SELECT EmployeeID, LastName, FirstName, " + 
                   "Title, TitleOfCourtesy, BirthDate, HireDate, Address, " + 
                   "City, Region, PostalCode, Country, HomePhone, " + 
                   "Extension, Photo, Notes, ReportsTo, PhotoPath " +
                   "FROM dbo.Employees";
            SqlCommand command = new SqlCommand(query, connection);
            connection.Open();
            dynamic reader = (DynamicDataReader)command.ExecuteReader();
            while (reader.Read())
            {
                int employeeId = reader.EmployeeID;
                string lastName = reader.LastName;
                string firstName = reader.FirstName;
                DateTime birthDate = reader.BirthDate;
                context.Response.Write
                (String.Format("<tr><td>{0}</td>" + 
                   "<td>{1}</td><td>{2}</td><td>" + 
                   "{3:dd/MM/yyyy}</td></tr>", 
                   employeeId, lastName, firstName, birthDate));
            }
        }
        context.Response.Write
        ("</tbody></table></body></html>");
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}

As our DynamicDataReader class implements IEnumerable, it can be used with the standard ASP.NET data controls. In this example, it is used as the data source for a GridView:

<!-- ASP.NET -->
<form id="form1" runat="server">
<div>
    <h1>
        GridView Examples</h1>
    <h2>
        Using DynamicDataReader</h2>
    <asp:GridView ID="dataReaderGridView" runat="server" />    
</div>
</form>

However, in most real-world multi-tier applications, we are probably most likely to be filling an object, or collection of objects, for use in the higher-level tiers of our system. The final example here shows the use of the DynamicDataReader class to fill an array of Customer objects:

// C#
public static Customer[] GetCustomers()
{
    List<Customer> customers = new List<Customer>();
    using (SqlConnection connection = new SqlConnection(
           WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString))
    {
        string query = "SELECT CustomerID, CompanyName, ContactName, " + 
               "ContactTitle, Address, City, Region, " + 
               "PostalCode, Country, Phone, Fax " +
               "FROM dbo.Customers";
        SqlCommand command = new SqlCommand(query, connection);
        connection.Open();
        dynamic reader = (DynamicDataReader)command.ExecuteReader();
        while (reader.Read())
        {
            Customer customer = new Customer()
            {
                Id = reader.CustomerID,
                Company = reader.CompanyName,
                Name = reader.ContactName,
                Title = reader.ContactTitle,
                Address = reader.Address,
                City = reader.City,
                Region = reader.Region,
                PostCode = reader.PostalCode,
                Country = reader.Country,
                Phone = reader.Phone,
                Fax = reader.Fax
            };
            customers.Add(customer);
        }
        return customers.ToArray();
    }
}

Limitations

  • If a database column has the same name as a property of the wrapped DbDataReader class, it cannot be accessed using the dynamic properties of the wrapper. Instead, the column can be accessed via the indexer on the wrapper.
  • If the application is performance-critical, you may want to consider removing the Reflection calls to the wrapped object in the TryGetMember() and TrySetMember() of the base DynamicDataObjectWrapper class.

Summary

The DynamicDataReader provides a means of retrieving data from a database in a way that involves less code and increases code readability.

The source code will be available for download with Part II.

Next: The DynamicDataSet

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)