/// <summary>
		/// Returns an object reader for the given type that matches the given schema.
		/// </summary>
		/// <param name="reader">The reader containing the schema to analyze.</param>
		/// <param name="type">The type to analyze.</param>
		/// <returns>An ObjectReader for the schema and type.</returns>
		public static ObjectReader GetObjectReader(IDataReader reader, Type type)
		{
			SchemaIdentity schemaIdentity = new SchemaIdentity(reader);
			SchemaMappingIdentity mappingIdentity = new SchemaMappingIdentity(schemaIdentity, type, null, SchemaMappingType.ExistingObject);

			return _readerDataCache.GetOrAdd(mappingIdentity, i => new ObjectReader(i, reader));
		}
		/// <summary>
		/// Initializes a new instance of the ObjectListDbDataReader class.
		/// </summary>
		/// <param name="schemaTable">The IDbDataReader schema table to use (get it from Sql Server).</param>
		/// <param name="listType">The type of the list to bind to.</param>
		/// <param name="list">The list of objects.</param>
		public ObjectListDbDataReader(DataTable schemaTable, Type listType, IEnumerable list)
		{
			_schemaTable = schemaTable;
			_listType = listType;
			_list = list.GetEnumerator();

			_isValueType = _listType.IsValueType || listType == typeof(string);

			// SQL Server tells us the precision of the columns
			// but the TDS parser doesn't like the ones set on money, smallmoney and date
			// so we have to override them
			_schemaTable.Columns["NumericScale"].ReadOnly = false;
			foreach (DataRow row in _schemaTable.Rows)
			{
				string dataType = row["DataTypeName"].ToString();
				if (String.Equals(dataType, "money", StringComparison.OrdinalIgnoreCase))
					row["NumericScale"] = 4;
				else if (String.Equals(dataType, "smallmoney", StringComparison.OrdinalIgnoreCase))
					row["NumericScale"] = 4;
				else if (String.Equals(dataType, "date", StringComparison.OrdinalIgnoreCase))
					row["NumericScale"] = 0;
			}
 
			// if this is not a value type, then we need to have methods to pull values out of the object
			if (!_isValueType)
			{
				// generate an identity for the schema and a key for our accessors
				SchemaIdentity identity = new SchemaIdentity(schemaTable);
				Tuple<Type, SchemaIdentity> key = new Tuple<Type, SchemaIdentity>(listType, identity);

				_readerData = _readerDataCache.GetOrAdd(key, k => CreateFieldReaderData(k.Item1, k.Item2));
			}
		}
		/// <summary>
		/// Returns an object reader for the given type that matches the given schema.
		/// </summary>
		/// <param name="command">The command associated with the reader.</param>
		/// <param name="reader">The reader containing the schema to analyze.</param>
		/// <param name="type">The type to analyze.</param>
		/// <returns>An ObjectReader for the schema and type.</returns>
		public static ObjectReader GetObjectReader(IDbCommand command, IDataReader reader, Type type)
		{
			SchemaIdentity schemaIdentity = new SchemaIdentity(reader);

			var key = Tuple.Create(schemaIdentity, type);

			return _readerDataCache.GetOrAdd(key, k => new ObjectReader(command, k.Item2, reader));
		}
		/// <summary>
		/// Create accessors to pull data from the object of the given type for the given schema.
		/// </summary>
		/// <param name="type">The type to analyze.</param>
		/// <param name="identity">The schema identity to analyze.</param>
		/// <returns>A list of accessor functions to get values from the type.</returns>
		private FieldReaderData CreateFieldReaderData(Type type, SchemaIdentity identity)
		{
			FieldReaderData readerData = new FieldReaderData();

			readerData.Accessors = new List<Func<object, object>>();
			readerData.MemberTypes = new List<Type>();

			for (int i = 0; i < identity.SchemaTable.Rows.Count; i++)
			{
				// get the name of the column
				string name = _schemaTable.Rows[i]["ColumnName"].ToString();

				// create a new anonymous method that takes an object and returns the value
				var dm = new DynamicMethod(string.Format(CultureInfo.InvariantCulture, "GetValue-{0}-{1}", type.FullName, Guid.NewGuid()), typeof(object), new[] { typeof(object) }, true);
				var il = dm.GetILGenerator();

				il.Emit(OpCodes.Ldarg_0);						// push object argument
				il.Emit(OpCodes.Isinst, type);					// cast object -> type

				// get the value from the object
				ClassPropInfo propInfo = new ClassPropInfo(type, name);
				propInfo.EmitGetValue(il);
				propInfo.EmitBox(il);

				il.Emit(OpCodes.Ret);

				readerData.MemberTypes.Add(propInfo.MemberType);
				readerData.Accessors.Add((Func<object, object>)dm.CreateDelegate(typeof(Func<object, object>)));
			}

			return readerData;
		}