Mapper:
Mapper的核心功能是创建一个委托函数并映射到一个目标类的实例。此委托是使用表达式树创建的一个lambda表达式。
在这个函数中有一个双重循环,从 DataRecord 获取字段并和从实体类中获取的属性名称比较从而填充实体实例。
所以第一个要求就是在使用这个 Mapper 时,DataReader的字段名必须匹配将要填充类的属性名且要填充的属性是可写的。
对于每个映射属性检查源和目标类型,不管他们是否为空,不管他们是否需要转换。
我们需要的所有信息中存在 SchemaTable,但是 IDataReader 不处理 null 值。
/// <summary> /// 从提供的 DataRecord 对象创建新委托实例。 /// </summary> /// <param name="RecordInstance">表示一个 DataRecord 实例</param> /// <returns>从提供的 DataRecord 对象创建新委托实例。</returns> /// <remarks></remarks> private static Func<Record, Target> GetInstanceCreator(Record RecordInstance) {List<MemberBinding> Bindings = new List<MemberBinding>();Type TargetType = typeof(Target);Type SourceType = typeof(Record);ParameterExpression SourceInstance = Expression.Parameter(SourceType, "SourceInstance");MethodInfo GetSourcePropertyMethodExpression = SourceType.GetProperty("Item", new Type[] { typeof(int) }).GetGetMethod();DataTable SchemaTable = ((IDataReader)RecordInstance).GetSchemaTable();//通过在目标属性和字段在记录中的循环检查哪些是匹配的for (int i = 0; i <= RecordInstance.FieldCount - 1; i++){foreach (PropertyInfo TargetProperty in TargetType.GetProperties(BindingFlags.Public | BindingFlags.Instance)) {//如果属性名和字段名称是一样的if (TargetProperty.Name.ToLower() == RecordInstance.GetName(i).ToLower() && TargetProperty.CanWrite) {//获取 RecordField 的类型Type RecordFieldType = RecordInstance.GetFieldType(i);//RecordField 可空类型检查if ((bool)(SchemaTable.Rows[i]["AllowDBNull"]) == true && RecordFieldType.IsValueType) {RecordFieldType = typeof(Nullable<>).MakeGenericType(RecordFieldType);}//为 RecordField 创建一个表达式Expression RecordFieldExpression = Expression.Call(SourceInstance, GetSourcePropertyMethodExpression, Expression.Constant(i, typeof(int)));//获取一个表示 SourceValue 的表达式Expression SourceValueExpression = GetSourceValueExpression(RecordFieldType, RecordFieldExpression);Type TargetPropertyType = TargetProperty.PropertyType;//从 RecordField 到 TargetProperty 类型的值转换Expression ConvertedRecordFieldExpression = GetConvertedRecordFieldExpression(RecordFieldType, SourceValueExpression, TargetPropertyType);MethodInfo TargetPropertySetter = TargetProperty.GetSetMethod();//为属性创建绑定var BindExpression = Expression.Bind(TargetPropertySetter, ConvertedRecordFieldExpression);//将绑定添加到绑定列表 Bindings.Add(BindExpression);}}}//创建 Target 的新实例并绑定到 DataRecordMemberInitExpression Body = Expression.MemberInit(Expression.New(TargetType), Bindings);return Expression.Lambda<Func<Record, Target>>(Body, SourceInstance).Compile(); }
现在我们需要从 IDataReader 创建一个 sourceproperty。
/// <summary> /// 获取表示 RecordField 真实值的表达式。 /// </summary> /// <param name="RecordFieldType">表示 RecordField 的类型。</param> /// <param name="RecordFieldExpression">表示 RecordField 的表达式。</param> /// <returns>表示 SourceValue 的表达式。</returns> private static Expression GetSourceValueExpression(Type RecordFieldType, Expression RecordFieldExpression) {//首先从 RecordField 取消装箱值,以便我们可以使用它UnaryExpression UnboxedRecordFieldExpression = Expression.Convert(RecordFieldExpression, RecordFieldType);//获取一个检查 SourceField 为 null 值的表达式UnaryExpression NullCheckExpression = Expression.IsFalse(Expression.TypeIs(RecordFieldExpression, typeof(DBNull)));ParameterExpression Value = Expression.Variable(RecordFieldType, "Value");//获取一个设置 TargetProperty 值的表达式Expression SourceValueExpression = Expression.Block(new ParameterExpression[] { Value }, Expression.IfThenElse(NullCheckExpression, Expression.Assign(Value, UnboxedRecordFieldExpression),Expression.Assign(Value, Expression.Constant(GetDefaultValue(RecordFieldType), RecordFieldType))), Expression.Convert(Value, RecordFieldType));return SourceValueExpression; }
现在把源转换到目标属性。
如果它们相同,只需要在装箱之前将源对象分配给目标属性。如果他们不同我们还需要将源对象强制转换为目标类型。
还有一个特殊情况,需要处理这里。没有操作符为原始类型转换为字符串。所以如果我们试试这个函数将抛出异常。这是通过调用 ToString 方法处理源。
/// <summary> /// Gets an expression representing the recordField converted to the TargetPropertyType /// </summary> /// <param name="RecordFieldType">The Type of the RecordField</param> /// <param name="UnboxedRecordFieldExpression">An Expression representing the Unboxed RecordField value</param> /// <param name="TargetPropertyType">The Type of the TargetProperty</param> /// <returns></returns> private static Expression GetConvertedRecordFieldExpression(Type RecordFieldType, Expression UnboxedRecordFieldExpression, Type TargetPropertyType) {Expression ConvertedRecordFieldExpression = default(Expression);if (object.ReferenceEquals(TargetPropertyType, RecordFieldType)){//Just assign the unboxed expressionConvertedRecordFieldExpression = UnboxedRecordFieldExpression;}else if (object.ReferenceEquals(TargetPropertyType, typeof(string))){//There are no casts from primitive types to String.//And Expression.Convert Method (Expression, Type, MethodInfo) only works with static methods.ConvertedRecordFieldExpression = Expression.Call(UnboxedRecordFieldExpression, RecordFieldType.GetMethod("ToString", Type.EmptyTypes));}else{//Using Expression.Convert works wherever you can make an explicit or implicit cast.//But it casts OR unboxes an object, therefore the double cast. First unbox to the SourceType and then cast to the TargetType//It also doesn't convert a numerical type to a String or date, this will throw an exception.ConvertedRecordFieldExpression = Expression.Convert(UnboxedRecordFieldExpression, TargetPropertyType);}return ConvertedRecordFieldExpression; }
为了使用其更快,我们将使用缓存
/// <summary> /// A Singleton construct that returns a precompiled delegate if it exists, otherwise it will create one /// </summary> /// <param name="RecordInstance"></param> /// <returns></returns> static internal Func<Record, Target> GetCreator(Record RecordInstance) {if (_Creator == null){lock (SyncRoot){if (_Creator == null){//Get Creator on first access_Creator = GetInstanceCreator(RecordInstance);}}}return _Creator; }
使用示例:
公有方法包括两个简单的扩展方法,所以 IDataRecord 所以使用映射器是很容易的。
/// <summary> /// ExtensionMethod that creates a List<Target> from the supplied IDataReader /// </summary> /// <param name="Reader"></param> /// <returns></returns> public static List<Target> ToList<Target>(this IDataReader Reader) where Target : class, new() {List<Target> List = new List<Target>();while (Reader.Read()){List.Add(CreateInstance<Target>(Reader));}return List; }/// <summary> /// ExtensionMethod that Creates an instance<Target>) from a DataRecord. /// </summary> /// <param name="Record">The DataRecord containing the values to set on new instance</param> /// <returns>An instance of Target class</returns> public static Target CreateInstance<Target>( this IDataRecord Record) where Target : class, new() {return (Mapper<IDataRecord, Target>.GetCreator(Record))(Record); }
或Reader.ToList<MyClass>()