纸上得来终觉浅,绝知此事要躬行。

 

【译】将IDataRecord自动填充到实体的扩展方法

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 的新实例并绑定到 DataRecord
    MemberInitExpression 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 expression
        ConvertedRecordFieldExpression = 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.CreateInstance<MyClassInstance>
Reader.ToList<MyClass>()

posted on 2014-01-17 13:47  JRoger  阅读(557)  评论(0编辑  收藏  举报

导航