I've made the following DataSetReader
, DS
Attribute, and DataSetManager
.
My main concerns are the following:
- This is my first time using a Singleton. Should I? And have I done it well?
- My aim is to make this class reusable in other projects. Is there anything I should improve to allow for this further? I pulled the logic for handling DBNull value into the Singleton I created. Is there anything else I can or should do?
DataSetReader:
public class DataSetReader<T> where T : new()
{
#region Events
/// <summary>
/// Fires when a column is found in the DataSet but not in Object, T.
/// </summary>
public event InvalidValueHandler ColumnNotFound;
protected virtual void OnColumnNotFound(DataColumn column, ref Object value)
{
if (ColumnNotFound != null)
ColumnNotFound(this, column, ref value);
}
/// <summary>
/// Allows for Handling of default values for specific types when a DBNull value is returned from a DataSet Column.
/// </summary>
public event InvalidValueHandler ColumnReturnedDBNull;
protected virtual void OnColumnReturnedDBNull(DataColumn column, ref Object value)
{
if (ColumnReturnedDBNull != null)
ColumnReturnedDBNull(this, column, ref value);
}
/// <summary>
/// Allows for Handling of default values for specific types when a Null value is returned from a DataSet Column.
/// </summary>
public event InvalidValueHandler ColumnReturnedNull;
protected virtual void OnColumnReturnedNull(DataColumn column, ref Object value)
{
if (ColumnReturnedNull != null)
ColumnReturnedNull(this, column, ref value);
}
#endregion
/// <summary>
/// Retrieves all values from the DataRow that match a property with a DS attribute into an new collection of objects.
/// NOTE: Matching done via DS.Name if DS.Name is null the property's name is used.
/// </summary>
public IEnumerable<T> ReadObjects(DataSet ds)
{
if (ds.Tables.Count > 0 && ds.Tables[0].Rows.Count > 0)
{
return ReadObjects(ds.Tables[0].Rows);
}
return new List<T>();
}
/// <summary>
/// Retrieves all values from the DataRow that match a property with a DS attribute into an new collection of objects.
/// NOTE: Matching done via DS.Name if DS.Name is null the property's name is used.
/// </summary>
public IEnumerable<T> ReadObjects(DataRowCollection rows)
{
List<T> objects = new List<T>();
if (rows == null || rows.Count <= 0) return objects;
IEnumerable<Tuple<String, DataColumn>> dataColumns = GetDataColumns<T>(rows[0].Table.Columns);
foreach (DataRow row in rows)
{
objects.Add(ReadObject(row, dataColumns));
}
return objects;
}
/// <summary>
/// Retrieves all values from the DataRow that match a property with a DS attribute into an new object.
/// NOTE: Matching done via DS.Name if DS.Name is null the property's name is used.
/// </summary>
public T ReadObject(DataRow row, IEnumerable<Tuple<String, DataColumn>> dataColumns = null)
{
T obj = new T();
dataColumns = dataColumns == null ? GetDataColumns<T>(row.Table.Columns) : dataColumns;
foreach (Tuple<String, DataColumn> dcp in dataColumns)
{
String propertyName = dcp.Item1;
Object columnValue = row[dcp.Item2];
PropertyInfo propInfo = obj.GetType().GetProperty(propertyName);
if (propInfo != null)
{
if (columnValue == DBNull.Value)
OnColumnReturnedDBNull(dcp.Item2, ref columnValue);
else if (columnValue == null)
OnColumnReturnedNull(dcp.Item2, ref columnValue);
}
else
{
// Will only fire if the Programmer passed in dataColumns
// GetDataColumns will never cause this since it only adds
// properties that exist on T.
OnColumnNotFound(dcp.Item2, ref columnValue);
}
propInfo.SetValue(obj, columnValue);
}
return obj;
}
#region Private Helpers
/// <summary>
/// Generates a collection of DataColumns from the properties with the DS attribute for the given type.
/// Returns a Tuple of the PropertyName and the DataColumn.
/// MetadataType Attributes take precedence over Attributes on the class itself.
/// </summary>
private static IEnumerable<Tuple<String, DataColumn>> GetDataColumns<T>(DataColumnCollection columns)
{
Type type = typeof(T);
List<Tuple<String, DataColumn>> dataColumns = new List<Tuple<String, DataColumn>>();
foreach (PropertyInfo propInfo in type.GetProperties())
{
DS attr = propInfo.GetCustomAttribute<DS>(true);
DS metaAttr = null;
PropertyInfo metaInfo = null;
MetadataTypeAttribute meta = type.GetCustomAttribute<MetadataTypeAttribute>(true);
if (meta != null)
metaInfo = meta.MetadataClassType.GetProperty(propInfo.Name);
if (metaInfo != null)
metaAttr = metaInfo.GetCustomAttribute<DS>(true);
String Name = propInfo.Name;
if (attr != null)
Name = attr.Name;
if (metaAttr != null)
Name = metaAttr.Name;
DataColumn column = GetColumnFromCollection(columns, Name);
if (column == null) continue;
dataColumns.Add(new Tuple<String, DataColumn>(propInfo.Name, column));
}
return dataColumns;
}
/// <summary>
/// Gets the column with the given name.
/// If not found, returns null.
/// </summary>
private static DataColumn GetColumnFromCollection(DataColumnCollection columns, String ColumnName)
{
foreach (DataColumn column in columns)
if (column.ColumnName == ColumnName)
return column;
return null;
}
#endregion
}
Attribute:
/// <summary>
/// The DS attribute tells the DataSetReader which column to look for this property's value.
/// If one isn't provided it uses the Property's name as default, so there's no need to clutter
/// the property with an attribute if they're the same.
/// DS attributes on MetadataTypeAttribute classes take precedence.
/// </summary>
public class DS : Attribute
{
public String Name { get; set; }
public DS() { }
}
Manager (Singleton):
public class DataSetManager<T> where T : new()
{
private static DataSetReader<T> _DSReader { get; set; }
private static void InitializeReader()
{
_DSReader = new DataSetReader<T>();
_DSReader.ColumnReturnedDBNull += HandleNull;
_DSReader.ColumnReturnedNull += HandleNull;
}
public static DataSetReader<T> Manager
{
get
{
if (_DSReader == null)
InitializeReader();
return _DSReader;
}
}
private static void HandleNull(Object sender, DataColumn column, ref Object columnValue)
{
if (column.DataType == typeof(String))
columnValue = String.Empty;
else if (column.DataType == typeof(int))
columnValue = 0;
else if (column.DataType == typeof(Decimal))
columnValue = 0.0m;
else
columnValue = null;
}
}
DataSetManager
to simply be a static class that creates the reader similarly to theInitializeReader
function. So my useage is still short but attains the functionality I desireDataSetManager<T>.CreateReader().ReadeObjects(DataSet);
– Shelby115 Feb 11 at 2:29