ObjectReader<T>

A recent project I worked on required moving CSLA business objects to and from a sql server database through stored procs (no adhoc queries were allowed). Overtime, and after a few refactoring sessions trying to normalize some of the CSLA property/field to stored proc parameter mapping code, a nice little reusable component emerged that allowed us to write code as follows:

Download code

/// <summary>
/// 
/// </summary>
/// <returns></returns>
public static Customer[] FindAll()
{
    using (SqlConnection connection = new SqlConnection(
        ConfigurationManager.ConnectionStrings["simple_orm_unit_test"].ConnectionString))
    {
        using (SqlCommand command = new SqlCommand())
        {
            //  init the command
            command.Connection = connection;
            command.CommandText = "dbo.uspCustomer_FindAll";
            command.CommandType = CommandType.StoredProcedure;

            //  open the connection
            command.Connection.Open();
            using (ObjectReader<Customer> reader = new ObjectReader<Customer>(
                new SqlServer.SqlServerFieldInfoMap<Customer>(), command.ExecuteReader()))
            {
                return reader.ReadAll();
            }
        }
    }
}

The ObjectReader<Customer>.ReadAll method loops through the rows in the data reader and creates a new instance of a Customer for each row it finds. The values from the row in the data reader are used to populate the fields of the object. In order for this to work, the mapping metadata that assigns the fields from the Customer to columns in the table must be available. For out project, the column name can be derived from the field name (that is part of our standards) so, making this connection is very simple. I realize not everyone uses this same rule, I moved the logic that looks this up into an interface (IFieldInfoMap). Here is the implementation that we are using. As you can see it is pretty simple – just peel off the ‘@’ symbol and use the resulting string to look up the field name.

/// <summary>
/// 
/// </summary>
public class SqlServerFieldInfoMap<T> : IFieldInfoMap where T : class
{
    #region IFieldInfoMap Members

    /// <summary>
    /// 
    /// </summary>
    public SqlServerFieldInfoMap() { }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="alias"></param>
    /// <returns></returns>
    public FieldInfo this[string alias]
    {
        get
        {
            //  trim off the parameter (this is sql sever specific)
            string normalizedAlias = alias.Replace("@", string.Empty);
            return typeof(T).GetField(
                normalizedAlias, 
                BindingFlags.GetField | BindingFlags.NonPublic | 
                BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
        }
    }

    #endregion
}

Caveats

  1. If your object doesn’t have a default c-tor, you will have to replace the Activator.CreateInstance call in ObjectReader<T> to call whatever component is responsible for creating new objects in your system (In our project, every object has a static New method – so it is easy to reflect on the object to find this method)
  2. If you system uses custom .Net class/structs for data types (i.e. PhoneNumber) you will have to add custom logic that converts the strings to PhoneNumbers (or have your data time implement IConvertable)

Here is the complete code listing for ObjectReader<T>. You can download the code here to view the complete listing and a sample Customer implementation that hits northwind.

/// <summary>
/// 
/// </summary>
/// <typeparam name="T"></typeparam>
public class ObjectReader<T> : IDisposable where T : class
{
    /// <summary>
    /// 
    /// </summary>
    private IFieldInfoMap _fieldInfoMap;
    /// <summary>
    /// 
    /// </summary>
    private IDataReader _dataReader;
    /// <summary>
    /// 
    /// </summary>
    private T _current;
    /// <summary>
    /// 
    /// </summary>
    private int _position = -1;

    /// <summary>
    /// 
    /// </summary>
    /// <param name="fieldInfoMap"></param>
    /// <param name="dataReader"></param>
    public ObjectReader(IFieldInfoMap fieldInfoMap, IDataReader dataReader)
    {
        this._fieldInfoMap = fieldInfoMap;
        this._dataReader = dataReader;
    }

    /// <summary>
    /// 
    /// </summary>
    private IFieldInfoMap FieldInfoMap
    {
        get { return this._fieldInfoMap; }
    }

    /// <summary>
    /// 
    /// </summary>
    private IDataReader DataReader
    {
        get { return this._dataReader; }
    }

    /// <summary>
    /// 
    /// </summary>
    public T Current
    {
        get
        {
            if (this._position == -1)
            {
                throw new InvalidOperationException();
            }

            return this._current;
        }
        private set { this._current = value; }
    }

    /// <summary>
    /// 
    /// </summary>
    /// <returns></returns>
    public bool Read()
    {
        if (this.DataReader.Read())
        {
            //  update the position
            this._position++;

            //  create the object
            //  this is the place to inject custom creational patterns
            //  if your framework requires
            this.Current = (T)Activator.CreateInstance(typeof(T));

            //  map the fields from the reader to the object
            for (int i = 0; i < this.DataReader.FieldCount; i++ )
            {
                string fieldName = this.DataReader.GetName(i);
                FieldInfo finfo = this.FieldInfoMap[fieldName];
                if (finfo != null)
                {
                    //  this is where you could include logic for
                    //  converting to a custom type
                    finfo.SetValue(this.Current, this.DataReader.GetValue(i));
                }
            }

            return true;
        }
        else
        {
            return false;
        }
    }

    /// <summary>
    /// 
    /// </summary>
    /// <returns></returns>
    public T[] ReadAll()
    {
        List<T> items = new List<T>();
        
        while (this.Read())
        {
            items.Add(this.Current);
        }
        
        return items.ToArray();
    }
    
    #region IDisposable Members

    /// <summary>
    /// 
    /// </summary>
    public void Dispose() { }

    #endregion
}

 


TrackBack

TrackBack URL for this entry:
http://mattberseth.com/blog-mt/mt-tb.fcgi/3

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)