Подтвердить что ты не робот

Общее отношение к композитному объекту С#

У меня есть следующий код, способный отображать 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;

        }
    }
}
4b9b3361

Ответ 1

Не делайте 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>() в результате вашего метода. Таким образом, вы видите, что этот алгоритм может в принципе работать без генериков.

Ответ 2

Мое предпочтение было бы оставить тяжелый подъем по вызывающему коду. Это позволяет избежать относительно медленной рекурсии и позволяет создавать объекты, где имена полей не совпадают точно или не имеют конструкторов по умолчанию:

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;
    }
}

Вы можете вызвать его с помощью mapper по умолчанию, например:

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

Конечно, это все равно будет терпеть неудачу в сложных составных типах, но я не вижу, как можно ожидать, что какой-либо универсальный кардпер преуспеет в этом сценарии... если у вашего внутреннего типа есть свойства для заполнения, нет никакого хорошего способа get указать соответствующее имя. Это позволяет быстро написать свой собственный картограф, если вам нужно:

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;      
})
{
    //...
}

И, конечно, вы всегда можете построить логику для этого преобразования в метод, который вместо этого мог бы передаваться по имени.