与复合C#对象映射器的通用关系

时间:2015-05-14 06:19:23

标签: c# datareader

我有以下代码能够将Reader映射到简单对象。麻烦在于对象是复合的,它无法映射。如果属性本身是

,我无法通过检查属性来执行递归

prop.PropertyType.IsClass  因为类型需要致电DataReaderMapper()。有关如何实现这一点或其他方法的任何想法?此外,目前我不想使用任何ORM。

public static class MapperHelper
{

    /// <summary>
    /// extension Method for Reader :Maps reader to type defined
    /// </summary>
    /// <typeparam name="T">Generic type:Model Class Type</typeparam>
    /// <param name="dataReader">this :current Reader</param>
    /// <returns>List of Objects</returns>
    public static IEnumerable<T> DataReaderMapper<T>(this IDataReader dataReader)where T : class, new()
    {
        T obj = default(T);

        //optimized taken out of both foreach and while loop
        PropertyInfo[] PropertyInfo;
        var temp = typeof(T);
        PropertyInfo = temp.GetProperties();

        while (dataReader.Read())
        {  
            obj = new T();

            foreach (PropertyInfo prop in PropertyInfo)
            {
                if (DataConverterHelper.ColumnExists(dataReader,prop.Name) && !dataReader.IsDBNull(prop.Name))
                {
                    prop.SetValue(obj, dataReader[prop.Name], null);
                }
            }
            yield return obj;

        }
    }
}

2 个答案:

答案 0 :(得分:9)

不要使DataReaderMapper递归。只需使映射部分递归:

static void Assign(IDataReader reader, object instance) {
        foreach (PropertyInfo prop in PropertyInfo)
        {
            if (IsValue(prop))
            {
                prop.SetValue(obj, dataReader[prop.Name], null);
            }
            else if (IsClass(prop)) {
               var subInstance = Activator.CreateInstance(prop.PropertyType);
                prop.SetValue(obj, subInstance, null);
               Assign(subInstance, dataReader);
            }
        }
}
像那样。这将使用默认构造实例递归初始化所有类类型属性,并为其分配数据读取器值。

代码明显简化。我省略了你的一些内容,IsValue / IsClass可以根据自己的喜好来实现。此外,您可能希望使用命名方案,以便a.b.c作为列名映射到该属性。通过将当前名称前缀作为参数传递给Assign来实现这一点。

进一步注意,DataReaderMapper不是通用的。我这样说是因为你挣扎着。将typeof(T)替换为Type参数并返回IEnumerable<object>。然后在方法的结果上调用Cast<T>()。所以你看到这个算法原则上可以在没有泛型的情况下工作。

答案 1 :(得分:1)

我的偏好是将繁重的工作留给调用代码。这样可以避免相对较慢的递归,并允许您构建对象,其中字段名称不能完全排列或者没有默认构造函数:

public static class MapperHelper
{
    public static IEnumerable<T> DataReaderMapper<T>(this IDataReader dataReader, Func<IDataRecord, T> map)
    {
        while (dataReader.Read())
        {  
            yield return map(dataReader);
        }
    }

然后我会将您现有的属性代码移动到一个可以在这里作为默认实现传递的方法:

    public static T DefaultMapper<T>(IDataRecord record) where T : class, new()
    {
        //This is now effectively inside the while loop,
        // but .Net caches the expensive recursive calls for you
        PropertyInfo[] PropertyInfo;
        var temp = typeof(T);
        PropertyInfo = temp.GetProperties();

        obj = new T();
        foreach (PropertyInfo prop in PropertyInfo)
        {
            if (DataConverterHelper.ColumnExists(dataReader,prop.Name) && !dataReader.IsDBNull(prop.Name))
            {
                prop.SetValue(obj, dataReader[prop.Name], null);
            }
        }
        return obj;
    }
}

您可以使用默认映射器调用它:

foreach (var record in myDataReader.DataReaderMapper<SomeType>(MapperHelper.DefaultMapper) )
{
    //...
}

当然,对于复杂的复合类型,这仍然会失败,但我不知道如何期望任何通用映射器在该场景中成功...如果您的内部类型本身具有要填充的属性,那么&#39 ; s没有好的方法来指定匹配的名称。如果您需要,这可以让您快速编写自己的映射器:

foreach (var record in myDataRecord.DataReaderMapper<SomeType>(r => {
    //complex mapping goes here
    SomeType result = new SomeType() {
        field1 = r["field1"],
        field2 = new OtherType() {
           subField = r["subField"],
           otherField = r["otherField"]
        }
    }
    return result;      
})
{
    //...
}

当然,你总是可以将转换的逻辑构建成一个可以通过名称传递的方法。