Using a dynamic DataReader with ADO.NET
I've recently had to do some data parsing, pulling data from a CSV file, performing some validation and then dumping a series of SQL commands for our data population scripts. I know there are a myriad ways to do this but I wanted to do this as quickly and easy as possible.
The process is quite simple:
- Open the file using a StreamReader
- Use the awesome [CsvReader](https://github.com/MikeStall/DataTable" target="_blank)
- Map csv data to object ids from database
- Output SQL statements and error log
To make things fast, I load all the necessary data collections from the database at the beginning of the execution. For this, I use ADO.NET. No micro ORMs or EF/nHibernate. When it comes to pulling data from the database quickly and efficiently, ADO.NET is the tool of choice.
However, there is one thing that annoyed me and that was reading the retrieved datasets using a DataReader. It usually goes like this:
while (reader.Read())
{
string name = reader["Name"] as string;
string address = reader["Address"] as string;
//rest of code omitted
This involves reading one record at a time and then mapping and casting each field to the appropriate object property. If only there was a way to abstract and make this process a bit easier...
DynamicDataReader
Enter the DynamicDataReader, a neat utility provided by Rick Stahl. You can find some more information about the utility on his [website](https://weblog.west-wind.com/posts/2011/Dec/06/Creating-a-Dynamic-DataReader-for-easier-Property-Access#GoodNews,BadNews" target="_blank), but if you want to get started quickly, this is all you need to do:
Create a class, name it DynamicDataReader and paste this code:
public class DynamicDataReader : DynamicObject
{
/// <summary>
/// Cached Instance of DataReader passed in
/// </summary>
IDataReader DataReader;
/// <summary>
/// Pass in a loaded DataReader
/// </summary>
/// <param name="dataReader">DataReader instance to work off</param>
public DynamicDataReader(IDataReader dataReader)
{
DataReader = dataReader;
}
/// <summary>
/// Returns a value from the current DataReader record
/// If the field doesn't exist null is returned.
/// DbNull values are turned into .NET nulls.
/// </summary>
/// <param name="binder"></param>
/// <param name="result"></param>
/// <returns></returns>
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = null;
// 'Implement' common reader properties directly
if (binder.Name == "IsClosed")
result = DataReader.IsClosed;
else if (binder.Name == "RecordsAffected")
result = DataReader.RecordsAffected;
// lookup column names as fields
else
{
try
{
result = DataReader[binder.Name];
if (result == DBNull.Value)
result = null;
}
catch
{
result = null;
return false;
}
}
return true;
}
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
// Implement most commonly used method
if (binder.Name == "Read")
{
result = DataReader.Read();
}
else if (binder.Name == "Close")
{
DataReader.Close();
result = null;
}
else
{
// call other DataReader methods using Reflection (slow - not recommended)
// recommend you use full DataReader instance
result = ReflectionUtils.CallMethod(DataReader, binder.Name, args);
}
return true;
}
}
View DynamicDataReader.cs on GitHub
This class has a dependency to another small utility - ReflectionUtils - which, as the name suggests, does some reflection magic to retrieve values when calling the TryInvokeMember() method. You can find the source code for ReflectionUtils below:
using System;
using System.Reflection;
using System.Collections;
using System.Globalization;
using System.ComponentModel;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Westwind.Utilities.Properties;
using System.Runtime.InteropServices;
namespace Westwind.Utilities
{
/// <summary>
/// Collection of Reflection and type conversion related utility functions
/// </summary>
public static class ReflectionUtils
{
/// <summary>
/// Binding Flags constant to be reused for all Reflection access methods.
/// </summary>
public const BindingFlags MemberAccess =
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Static | BindingFlags.Instance | BindingFlags.IgnoreCase;
/// <summary>
/// Retrieve a property value from an object dynamically. This is a simple version
/// that uses Reflection calls directly. It doesn't support indexers.
/// </summary>
/// <param name="instance">Object to make the call on</param>
/// <param name="property">Property to retrieve</param>
/// <returns>Object - cast to proper type</returns>
public static object GetProperty(object instance, string property)
{
return instance.GetType().GetProperty(property, ReflectionUtils.MemberAccess).GetValue(instance, null);
}
/// <summary>
/// Retrieve a field dynamically from an object. This is a simple implementation that's
/// straight Reflection and doesn't support indexers.
/// </summary>
/// <param name="Object">Object to retreve Field from</param>
/// <param name="Property">name of the field to retrieve</param>
/// <returns></returns>
public static object GetField(object Object, string Property)
{
return Object.GetType().GetField(Property, ReflectionUtils.MemberAccess).GetValue(Object);
}
/// <summary>
/// Parses Properties and Fields including Array and Collection references.
/// Used internally for the 'Ex' Reflection methods.
/// </summary>
/// <param name="Parent"></param>
/// <param name="Property"></param>
/// <returns></returns>
private static object GetPropertyInternal(object Parent, string Property)
{
if (Property == "this" || Property == "me")
return Parent;
object result = null;
string pureProperty = Property;
string indexes = null;
bool isArrayOrCollection = false;
// Deal with Array Property
if (Property.IndexOf("[") > -1)
{
pureProperty = Property.Substring(0, Property.IndexOf("["));
indexes = Property.Substring(Property.IndexOf("["));
isArrayOrCollection = true;
}
// Get the member
MemberInfo member = Parent.GetType().GetMember(pureProperty, ReflectionUtils.MemberAccess)[0];
if (member.MemberType == MemberTypes.Property)
result = ((PropertyInfo)member).GetValue(Parent, null);
else
result = ((FieldInfo)member).GetValue(Parent);
if (isArrayOrCollection)
{
indexes = indexes.Replace("[", string.Empty).Replace("]", string.Empty);
if (result is Array)
{
int Index = -1;
int.TryParse(indexes, out Index);
result = CallMethod(result, "GetValue", Index);
}
else if (result is ICollection)
{
if (indexes.StartsWith("\""))
{
// String Index
indexes = indexes.Trim('\"');
result = CallMethod(result, "get_Item", indexes);
}
else
{
// assume numeric index
int index = -1;
int.TryParse(indexes, out index);
result = CallMethod(result, "get_Item", index);
}
}
}
return result;
}
/// <summary>
/// Parses Properties and Fields including Array and Collection references.
/// </summary>
/// <param name="Parent"></param>
/// <param name="Property"></param>
/// <returns></returns>
private static object SetPropertyInternal(object Parent, string Property, object Value)
{
if (Property == "this" || Property == "me")
return Parent;
object Result = null;
string PureProperty = Property;
string Indexes = null;
bool IsArrayOrCollection = false;
// Deal with Array Property
if (Property.IndexOf("[") > -1)
{
PureProperty = Property.Substring(0, Property.IndexOf("["));
Indexes = Property.Substring(Property.IndexOf("["));
IsArrayOrCollection = true;
}
if (!IsArrayOrCollection)
{
// Get the member
MemberInfo Member = Parent.GetType().GetMember(PureProperty, ReflectionUtils.MemberAccess)[0];
if (Member.MemberType == MemberTypes.Property)
((PropertyInfo)Member).SetValue(Parent, Value, null);
else
((FieldInfo)Member).SetValue(Parent, Value);
return null;
}
else
{
// Get the member
MemberInfo Member = Parent.GetType().GetMember(PureProperty, ReflectionUtils.MemberAccess)[0];
if (Member.MemberType == MemberTypes.Property)
Result = ((PropertyInfo)Member).GetValue(Parent, null);
else
Result = ((FieldInfo)Member).GetValue(Parent);
}
if (IsArrayOrCollection)
{
Indexes = Indexes.Replace("[", string.Empty).Replace("]", string.Empty);
if (Result is Array)
{
int Index = -1;
int.TryParse(Indexes, out Index);
Result = CallMethod(Result, "SetValue", Value, Index);
}
else if (Result is ICollection)
{
if (Indexes.StartsWith("\""))
{
// String Index
Indexes = Indexes.Trim('\"');
Result = CallMethod(Result, "set_Item", Indexes, Value);
}
else
{
// assume numeric index
int Index = -1;
int.TryParse(Indexes, out Index);
Result = CallMethod(Result, "set_Item", Index, Value);
}
}
}
return Result;
}
/// <summary>
/// Returns a property or field value using a base object and sub members including . syntax.
/// For example, you can access: oCustomer.oData.Company with (this,"oCustomer.oData.Company")
/// This method also supports indexers in the Property value such as:
/// Customer.DataSet.Tables["Customers"].Rows[0]
/// </summary>
/// <param name="Parent">Parent object to 'start' parsing from. Typically this will be the Page.</param>
/// <param name="Property">The property to retrieve. Example: 'Customer.Entity.Company'</param>
/// <returns></returns>
public static object GetPropertyEx(object Parent, string Property)
{
Type type = Parent.GetType();
int at = Property.IndexOf(".");
if (at < 0)
{
// Complex parse of the property
return GetPropertyInternal(Parent, Property);
}
// Walk the . syntax - split into current object (Main) and further parsed objects (Subs)
string main = Property.Substring(0, at);
string subs = Property.Substring(at + 1);
// Retrieve the next . section of the property
object sub = GetPropertyInternal(Parent, main);
// Now go parse the left over sections
return GetPropertyEx(sub, subs);
}
/// <summary>
/// Returns a PropertyInfo object for a given dynamically accessed property
///
/// Property selection can be specified using . syntax ("Address.Street" or "DataTable[0].Rows[1]") hence the <<i>>Ex<</i>> name for this function.
/// </summary>
/// <param name="Parent"></param>
/// <param name="Property"></param>
/// <returns></returns>
public static PropertyInfo GetPropertyInfoEx(object Parent, string Property)
{
Type type = Parent.GetType();
int at = Property.IndexOf(".");
if (at < 0)
{
// Complex parse of the property
return GetPropertyInfoInternal(Parent, Property);
}
// Walk the . syntax - split into current object (Main) and further parsed objects (Subs)
string main = Property.Substring(0, at);
string subs = Property.Substring(at + 1);
// Retrieve the next . section of the property
object sub = GetPropertyInternal(Parent, main);
// Now go parse the left over sections
return GetPropertyInfoEx(sub, subs);
}
/// <summary>
/// Returns a PropertyInfo structure from an extended Property reference
/// </summary>
/// <param name="Parent"></param>
/// <param name="Property"></param>
/// <returns></returns>
public static PropertyInfo GetPropertyInfoInternal(object Parent, string Property)
{
if (Property == "this" || Property == "me")
return null;
string propertyName = Property;
// Deal with Array Property - strip off array indexer
if (Property.IndexOf("[") > -1)
propertyName = Property.Substring(0, Property.IndexOf("["));
// Get the member
return Parent.GetType().GetProperty(propertyName, ReflectionUtils.MemberAccess);
}
/// <summary>
/// Sets the property on an object. This is a simple method that uses straight Reflection
/// and doesn't support indexers.
/// </summary>
/// <param name="obj">Object to set property on</param>
/// <param name="property">Name of the property to set</param>
/// <param name="value">value to set it to</param>
public static void SetProperty(object obj, string property, object value)
{
obj.GetType().GetProperty(property, ReflectionUtils.MemberAccess).SetValue(obj, value, null);
}
/// <summary>
/// Sets the field on an object. This is a simple method that uses straight Reflection
/// and doesn't support indexers.
/// </summary>
/// <param name="obj">Object to set property on</param>
/// <param name="property">Name of the field to set</param>
/// <param name="value">value to set it to</param>
public static void SetField(object obj, string property, object value)
{
obj.GetType().GetField(property, ReflectionUtils.MemberAccess).SetValue(obj, value);
}
/// <summary>
/// Sets a value on an object. Supports . syntax for named properties
/// (ie. Customer.Entity.Company) as well as indexers.
/// </summary>
/// <param name="Object Parent">
/// Object to set the property on.
/// </param>
/// <param name="String Property">
/// Property to set. Can be an object hierarchy with . syntax and can
/// include indexers. Examples: Customer.Entity.Company,
/// Customer.DataSet.Tables["Customers"].Rows[0]
/// </param>
/// <param name="Object Value">
/// Value to set the property to
/// </param>
public static object SetPropertyEx(object parent, string property, object value)
{
Type Type = parent.GetType();
// no more .s - we got our final object
int lnAt = property.IndexOf(".");
if (lnAt < 0)
{
SetPropertyInternal(parent, property, value);
return null;
}
// Walk the . syntax
string Main = property.Substring(0, lnAt);
string Subs = property.Substring(lnAt + 1);
object Sub = GetPropertyInternal(parent, Main);
SetPropertyEx(Sub, Subs, value);
return null;
}
/// <summary>
/// Calls a method on an object dynamically. This version requires explicit
/// specification of the parameter type signatures.
/// </summary>
/// <param name="instance">Instance of object to call method on</param>
/// <param name="method">The method to call as a stringToTypedValue</param>
/// <param name="parameterTypes">Specify each of the types for each parameter passed.
/// You can also pass null, but you may get errors for ambiguous methods signatures
/// when null parameters are passed</param>
/// <param name="parms">any variable number of parameters.</param>
/// <returns>object</returns>
public static object CallMethod(object instance, string method, Type[] parameterTypes, params object[] parms)
{
if (parameterTypes == null && parms.Length > 0)
// Call without explicit parameter types - might cause problems with overloads
// occurs when null parameters were passed and we couldn't figure out the parm type
return instance.GetType().GetMethod(method, ReflectionUtils.MemberAccess | BindingFlags.InvokeMethod).Invoke(instance, parms);
else
// Call with parameter types - works only if no null values were passed
return instance.GetType().GetMethod(method, ReflectionUtils.MemberAccess | BindingFlags.InvokeMethod, null, parameterTypes, null).Invoke(instance, parms);
}
/// <summary>
/// Calls a method on an object dynamically.
///
/// This version doesn't require specific parameter signatures to be passed.
/// Instead parameter types are inferred based on types passed. Note that if
/// you pass a null parameter, type inferrance cannot occur and if overloads
/// exist the call may fail. if so use the more detailed overload of this method.
/// </summary>
/// <param name="instance">Instance of object to call method on</param>
/// <param name="method">The method to call as a stringToTypedValue</param>
/// <param name="parameterTypes">Specify each of the types for each parameter passed.
/// You can also pass null, but you may get errors for ambiguous methods signatures
/// when null parameters are passed</param>
/// <param name="parms">any variable number of parameters.</param>
/// <returns>object</returns>
public static object CallMethod(object instance, string method, params object[] parms)
{
// Pick up parameter types so we can match the method properly
Type[] parameterTypes = null;
if (parms != null)
{
parameterTypes = new Type[parms.Length];
for (int x = 0; x < parms.Length; x++)
{
// if we have null parameters we can't determine parameter types - exit
if (parms[x] == null)
{
parameterTypes = null; // clear out - don't use types
break;
}
parameterTypes[x] = parms[x].GetType();
}
}
return CallMethod(instance, method, parameterTypes, parms);
}
/// <summary>
/// Calls a method on an object with extended . syntax (object: this Method: Entity.CalculateOrderTotal)
/// </summary>
/// <param name="parent"></param>
/// <param name="method"></param>
/// <param name="params"></param>
/// <returns></returns>
public static object CallMethodEx(object parent, string method, params object[] parms)
{
Type Type = parent.GetType();
// no more .s - we got our final object
int lnAt = method.IndexOf(".");
if (lnAt < 0)
{
return ReflectionUtils.CallMethod(parent, method, parms);
}
// Walk the . syntax
string Main = method.Substring(0, lnAt);
string Subs = method.Substring(lnAt + 1);
object Sub = GetPropertyInternal(parent, Main);
// Recurse until we get the lowest ref
return CallMethodEx(Sub, Subs, parms);
}
/// <summary>
/// Creates an instance from a type by calling the parameterless constructor.
///
/// Note this will not work with COM objects - continue to use the Activator.CreateInstance
/// for COM objects.
/// <seealso>Class wwUtils</seealso>
/// </summary>
/// <param name="typeToCreate">
/// The type from which to create an instance.
/// </param>
/// <returns>object</returns>
public static object CreateInstanceFromType(Type typeToCreate, params object[] args)
{
if (args == null)
{
Type[] Parms = Type.EmptyTypes;
return typeToCreate.GetConstructor(Parms).Invoke(null);
}
return Activator.CreateInstance(typeToCreate, args);
}
/// <summary>
/// Creates an instance of a type based on a string. Assumes that the type's
/// </summary>
/// <param name="typeName"></param>
/// <param name="args"></param>
/// <returns></returns>
public static object CreateInstanceFromString(string typeName, params object[] args)
{
object instance = null;
Type type = null;
try
{
type = GetTypeFromName(typeName);
if (type == null)
return null;
instance = Activator.CreateInstance(type, args);
}
catch
{
return null;
}
return instance;
}
/// <summary>
/// Helper routine that looks up a type name and tries to retrieve the
/// full type reference in the actively executing assemblies.
/// </summary>
/// <param name="typeName"></param>
/// <returns></returns>
public static Type GetTypeFromName(string typeName)
{
Type type = null;
type = Type.GetType(typeName, false);
if (type != null)
return type;
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
// try to find manually
foreach (Assembly asm in assemblies)
{
type = asm.GetType(typeName, false);
if (type != null)
break;
}
return type;
}
/// <summary>
/// Creates a COM instance from a ProgID. Loads either
/// Exe or DLL servers.
/// </summary>
/// <param name="progId"></param>
/// <returns></returns>
public static object CreateComInstance(string progId)
{
Type type = Type.GetTypeFromProgID(progId);
if (type == null)
return null;
return Activator.CreateInstance(type);
}
/// <summary>
/// Converts a type to string if possible. This method supports an optional culture generically on any value.
/// It calls the ToString() method on common types and uses a type converter on all other objects
/// if available
/// </summary>
/// <param name="rawValue">The Value or Object to convert to a string</param>
/// <param name="culture">Culture for numeric and DateTime values</param>
/// <param name="unsupportedReturn">Return string for unsupported types</param>
/// <returns>string</returns>
public static string TypedValueToString(object rawValue, CultureInfo culture = null, string unsupportedReturn = null)
{
if (rawValue == null)
return string.Empty;
if (culture == null)
culture = CultureInfo.CurrentCulture;
Type valueType = rawValue.GetType();
string returnValue = null;
if (valueType == typeof(string))
returnValue = rawValue as string;
else if (valueType == typeof(int) || valueType == typeof(decimal) ||
valueType == typeof(double) || valueType == typeof(float) || valueType == typeof(Single))
returnValue = string.Format(culture.NumberFormat, "{0}", rawValue);
else if (valueType == typeof(DateTime))
returnValue = string.Format(culture.DateTimeFormat, "{0}", rawValue);
else if (valueType == typeof(bool) || valueType == typeof(Byte) || valueType.IsEnum)
returnValue = rawValue.ToString();
else if (valueType == typeof(Guid?))
{
if (rawValue == null)
returnValue = string.Empty;
else
return rawValue.ToString();
}
else
{
// Any type that supports a type converter
TypeConverter converter = TypeDescriptor.GetConverter(valueType);
if (converter != null && converter.CanConvertTo(typeof(string)) && converter.CanConvertFrom(typeof(string)))
returnValue = converter.ConvertToString(null, culture, rawValue);
else
{
// Last resort - just call ToString() on unknown type
if (!string.IsNullOrEmpty(unsupportedReturn))
returnValue = unsupportedReturn;
else
returnValue = rawValue.ToString();
}
}
return returnValue;
}
/// <summary>
/// Turns a string into a typed value generically.
/// Explicitly assigns common types and falls back
/// on using type converters for unhandled types.
///
/// Common uses:
/// * UI -> to data conversions
/// * Parsers
/// <seealso>Class ReflectionUtils</seealso>
/// </summary>
/// <param name="sourceString">
/// The string to convert from
/// </param>
/// <param name="targetType">
/// The type to convert to
/// </param>
/// <param name="culture">
/// Culture used for numeric and datetime values.
/// </param>
/// <returns>object. Throws exception if it cannot be converted.</returns>
public static object StringToTypedValue(string sourceString, Type targetType, CultureInfo culture = null)
{
object result = null;
bool isEmpty = string.IsNullOrEmpty(sourceString);
if (culture == null)
culture = CultureInfo.CurrentCulture;
if (targetType == typeof(string))
result = sourceString;
else if (targetType == typeof(Int32) || targetType == typeof(int))
{
if (isEmpty)
result = 0;
else
result = Int32.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);
}
else if (targetType == typeof(Int64))
{
if (isEmpty)
result = (Int64)0;
else
result = Int64.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);
}
else if (targetType == typeof(Int16))
{
if (isEmpty)
result = (Int16)0;
else
result = Int16.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);
}
else if (targetType == typeof(decimal))
{
if (isEmpty)
result = 0M;
else
result = decimal.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);
}
else if (targetType == typeof(DateTime))
{
if (isEmpty)
result = DateTime.MinValue;
else
result = Convert.ToDateTime(sourceString, culture.DateTimeFormat);
}
else if (targetType == typeof(byte))
{
if (isEmpty)
result = 0;
else
result = Convert.ToByte(sourceString);
}
else if (targetType == typeof(double))
{
if (isEmpty)
result = 0F;
else
result = Double.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);
}
else if (targetType == typeof(Single))
{
if (isEmpty)
result = 0F;
else
result = Single.Parse(sourceString, NumberStyles.Any, culture.NumberFormat);
}
else if (targetType == typeof(bool))
{
if (!isEmpty &&
sourceString.ToLower() == "true" || sourceString.ToLower() == "on" || sourceString == "1")
result = true;
else
result = false;
}
else if (targetType == typeof(Guid))
{
if (isEmpty)
result = Guid.Empty;
else
result = new Guid(sourceString);
}
else if (targetType.IsEnum)
result = Enum.Parse(targetType, sourceString);
else if (targetType == typeof(byte[]))
{
// TODO: Convert HexBinary string to byte array
result = null;
}
// Handle nullables explicitly since type converter won't handle conversions
// properly for things like decimal separators currency formats etc.
// Grab underlying type and pass value to that
else if (targetType.Name.StartsWith("Nullable`"))
{
if (sourceString.ToLower() == "null" || sourceString == string.Empty)
result = null;
else
{
targetType = Nullable.GetUnderlyingType(targetType);
result = StringToTypedValue(sourceString, targetType);
}
}
else
{
TypeConverter converter = TypeDescriptor.GetConverter(targetType);
if (converter != null && converter.CanConvertFrom(typeof(string)))
result = converter.ConvertFromString(null, culture, sourceString);
else
{
Debug.Assert(false, string.Format("Type Conversion not handled in StringToTypedValue for {0} {1}",
targetType.Name, sourceString));
throw (new InvalidCastException(Resources.StringToTypedValueValueTypeConversionFailed + targetType.Name));
}
}
return result;
}
/// <summary>
/// Generic version allow for automatic type conversion without the explicit type
/// parameter
/// </summary>
/// <typeparam name="T">Type to be converted to</typeparam>
/// <param name="sourceString">input string value to be converted</param>
/// <param name="culture">Culture applied to conversion</param>
/// <returns></returns>
public static T StringToTypedValue<T>(string sourceString, CultureInfo culture = null)
{
return (T)StringToTypedValue(sourceString, typeof(T), culture);
}
/// <summary>
/// Returns a List of KeyValuePair object
/// </summary>
/// <param name="enumeration"></param>
/// <returns></returns>
public static List<KeyValuePair<string, string>> GetEnumList(Type enumType, bool valueAsFieldValueNumber = false)
{
//string[] enumStrings = Enum.GetNames(enumType);
Array enumValues = Enum.GetValues(enumType);
List<KeyValuePair<string, string>> items = new List<KeyValuePair<string, string>>();
foreach (var enumValue in enumValues)
{
var strValue = enumValue.ToString();
if (!valueAsFieldValueNumber)
items.Add(new KeyValuePair<string, string>(enumValue.ToString(), StringUtils.FromCamelCase(strValue)));
else
items.Add(new KeyValuePair<string, string>(((int)enumValue).ToString(),
StringUtils.FromCamelCase(strValue)
));
}
return items;
}
/// <summary>
/// Retrieves a value from a static property by specifying a type full name and property
/// </summary>
/// <param name="typeName">Full type name (namespace.class)</param>
/// <param name="property">Property to get value from</param>
/// <returns></returns>
public static object GetStaticProperty(string typeName, string property)
{
Type type = GetTypeFromName(typeName);
if (type == null)
return null;
return GetStaticProperty(type, property);
}
/// <summary>
/// Returns a static property from a given type
/// </summary>
/// <param name="type">Type instance for the static property</param>
/// <param name="property">Property name as a string</param>
/// <returns></returns>
public static object GetStaticProperty(Type type, string property)
{
object result = null;
try
{
result = type.InvokeMember(property, BindingFlags.Static | BindingFlags.Public | BindingFlags.GetField | BindingFlags.GetProperty, null, type, null);
}
catch
{
return null;
}
return result;
}
#region COM Reflection Routines
/// <summary>
/// Retrieve a dynamic 'non-typelib' property
/// </summary>
/// <param name="instance">Object to make the call on</param>
/// <param name="property">Property to retrieve</param>
/// <returns></returns>
public static object GetPropertyCom(object instance, string property)
{
return instance.GetType().InvokeMember(property, ReflectionUtils.MemberAccess | BindingFlags.GetProperty | BindingFlags.GetField, null,
instance, null);
}
/// <summary>
/// Returns a property or field value using a base object and sub members including . syntax.
/// For example, you can access: oCustomer.oData.Company with (this,"oCustomer.oData.Company")
/// </summary>
/// <param name="parent">Parent object to 'start' parsing from.</param>
/// <param name="property">The property to retrieve. Example: 'oBus.oData.Company'</param>
/// <returns></returns>
public static object GetPropertyExCom(object parent, string property)
{
Type Type = parent.GetType();
int lnAt = property.IndexOf(".");
if (lnAt < 0)
{
if (property == "this" || property == "me")
return parent;
// Get the member
return parent.GetType().InvokeMember(property, ReflectionUtils.MemberAccess | BindingFlags.GetProperty | BindingFlags.GetField, null,
parent, null);
}
// Walk the . syntax - split into current object (Main) and further parsed objects (Subs)
string Main = property.Substring(0, lnAt);
string Subs = property.Substring(lnAt + 1);
object Sub = parent.GetType().InvokeMember(Main, ReflectionUtils.MemberAccess | BindingFlags.GetProperty | BindingFlags.GetField, null,
parent, null);
// Recurse further into the sub-properties (Subs)
return ReflectionUtils.GetPropertyExCom(Sub, Subs);
}
/// <summary>
/// Sets the property on an object.
/// </summary>
/// <param name="Object">Object to set property on</param>
/// <param name="Property">Name of the property to set</param>
/// <param name="Value">value to set it to</param>
public static void SetPropertyCom(object Object, string Property, object Value)
{
Object.GetType().InvokeMember(Property, ReflectionUtils.MemberAccess | BindingFlags.SetProperty | BindingFlags.SetField, null, Object, new object[1] { Value });
}
/// <summary>
/// Sets the value of a field or property via Reflection. This method alws
/// for using '.' syntax to specify objects multiple levels down.
///
/// ReflectionUtils.SetPropertyEx(this,"Invoice.LineItemsCount",10)
///
/// which would be equivalent of:
///
/// Invoice.LineItemsCount = 10;
/// </summary>
/// <param name="Object Parent">
/// Object to set the property on.
/// </param>
/// <param name="String Property">
/// Property to set. Can be an object hierarchy with . syntax.
/// </param>
/// <param name="Object Value">
/// Value to set the property to
/// </param>
public static object SetPropertyExCom(object parent, string property, object value)
{
Type Type = parent.GetType();
int lnAt = property.IndexOf(".");
if (lnAt < 0)
{
// Set the member
parent.GetType().InvokeMember(property, ReflectionUtils.MemberAccess | BindingFlags.SetProperty | BindingFlags.SetField, null,
parent, new object[1] { value });
return null;
}
// Walk the . syntax - split into current object (Main) and further parsed objects (Subs)
string Main = property.Substring(0, lnAt);
string Subs = property.Substring(lnAt + 1);
object Sub = parent.GetType().InvokeMember(Main, ReflectionUtils.MemberAccess | BindingFlags.GetProperty | BindingFlags.GetField, null,
parent, null);
return SetPropertyExCom(Sub, Subs, value);
}
/// <summary>
/// Wrapper method to call a 'dynamic' (non-typelib) method
/// on a COM object
/// </summary>
/// <param name="params"></param>
/// 1st - Method name, 2nd - 1st parameter, 3rd - 2nd parm etc.
/// <returns></returns>
public static object CallMethodCom(object instance, string method, params object[] parms)
{
return instance.GetType().InvokeMember(method, ReflectionUtils.MemberAccess | BindingFlags.InvokeMethod, null, instance, parms);
}
/// <summary>
/// Calls a method on a COM object with '.' syntax (Customer instance and Address.DoSomeThing method)
/// </summary>
/// <param name="parent">the object instance on which to call method</param>
/// <param name="method">The method or . syntax path to the method (Address.Parse)</param>
/// <param name="parms">Any number of parameters</param>
/// <returns></returns>
public static object CallMethodExCom(object parent, string method, params object[] parms)
{
Type Type = parent.GetType();
// no more .s - we got our final object
int at = method.IndexOf(".");
if (at < 0)
{
return ReflectionUtils.CallMethod(parent, method, parms);
}
// Walk the . syntax - split into current object (Main) and further parsed objects (Subs)
string Main = method.Substring(0, at);
string Subs = method.Substring(at + 1);
object Sub = parent.GetType().InvokeMember(Main, ReflectionUtils.MemberAccess | BindingFlags.GetProperty | BindingFlags.GetField, null,
parent, null);
// Recurse until we get the lowest ref
return CallMethodEx(Sub, Subs, parms);
}
#endregion
}
}
View ReflectionUtils.cs on GitHub
Again, you'll need to create a class in your project and add the code above to allow you to use the DynamicDataReader
With this in place, we can now code our ADO.NET methods to make use of the new DynamicDataReader:
public void SomeRandomMethod()
{
using (var connection = new SqlConnection("<database connection string>"))
{
connection.Open();
var command = new SqlCommand("<SQL query>, connection);
var reader = command.ExecuteReader();
dynamic dynamicReader = new DynamicDataReader(reader);
while (reader.Read())
{
var student = new Student
{
Id = dynamicReader.Id,
CreatedBy = dynamicReader.CreatedBy,
CreatedOn = dynamicReader.CreatedOn,
Name = dynamicReader.Name,
Age = dynamicReader.Age,
Address = dynamicReader.Address,
};
}
}
}
View DataReaderExample.cs on GitHub
You'll notice on the example above that there are certain benefits using this approach:
- Uses
<object>.PropertyNamesyntax - No more type casting
- Automatic conversion of
DBNullto.NET NULL
There are also a few drawbacks:
- It relies on
dynamicwhich is available > .NET 4 - There's no strong typing. If you mess with the property name you'll get an exception
- Using
dynamicmeans that there's a performance penalty
Overall, this is a neat little utility that allows you to quickly and easily pull data from any DataReader. Thanks to Rick Stahl for making this utility available.