Как сопоставить DataReader со свойствами класса и сохранить производительность?

Преамбула:

  1. Все строки подключения к данным, подключения и т. д. создаются с помощью DbProviderFactories.
  2. Код смешанный C# и VB.Net из нескольких библиотек.

Я сопоставляю DbDataReader с сущностями и имею несколько тестов:

[0] retrieved 159180 records in 45135 ms
[1] retrieved 159180 records in 45008 ms
[2] retrieved 159180 records in 44814 ms
[3] retrieved 159180 records in 44987 ms
[4] retrieved 159180 records in 44914 ms
[5] retrieved 159180 records in 45224 ms
[6] retrieved 159180 records in 45829 ms
[7] retrieved 159180 records in 60762 ms
[8] retrieved 159180 records in 52128 ms
[9] retrieved 159180 records in 47982 ms  

Это значительное количество времени и очень мало, учитывая, что запрос из Sql Server Management Studio занимает всего 17 секунд. Мой оператор выбора:

"ВЫБЕРИТЕ * ИЗ tbl_MyTable"

Таблица содержит 43 поля и, вероятно, не индексируется должным образом; однако, выполняя выбрать все, я не ожидаю, что индексация будет проблематичной. Итак... вот что я делаю:

Определить сущность:

public class Concept
{
    #region Columns
    [DataParameter("ConceptID", DbType.Int32)]
    public Int32 ConceptID
    { get; set; }
    [DataParameter("ConceptName", DbType.String)]
    public string ConceptName
    { get; set; }
    [DataParameter("ConceptTypeID", DbType.Int32)]
    public Int32 ConceptTypeID
    { get; set; }
    [DataParameter("ActiveYN", DbType.Boolean)]
    public bool ActiveYN
    { get; set; }
    #endregion
}

Запрос DataReader:

for (int i = 0; i <= 99; i++)
{
    sw.Start();
    var results = session.QueryReader<Concept>(
        new SqlCommand(command), dr => new Concept());

    sw.Stop();

    Console.WriteLine("[{0}] retrieved {1} records in {2} ms", i, results.Count(), sw.ElapsedMilliseconds);
    sw.Reset();
}

... вызов:

Public Function QueryReader(Of TEntity As {Class, New})(ByVal Command As DbCommand, _
                                                        ByVal Projection As Func(Of DbDataReader, TEntity)) _
                                                        As IEnumerable(Of TEntity)

    Dim list As IEnumerable(Of TEntity)

    Command.Connection = dataReader.NewConnection
    Command.Connection.Open()

    Using _reader As DbDataReader = Command.ExecuteReader()
        list = _reader.Query(Of TEntity)(Projection).ToList()
    End Using

    Command.Connection.Close()

    Return list
End Function

... и метод расширения QueryReader<T>: изменить размещение нового TEntity() - спасибо @Henk

public static IEnumerable<TEntity> Query<TEntity>(this DbDataReader Reader,
    Func<DbDataReader, TEntity> Projection)
    where TEntity : class, new()
{
    //   moving this reflection to another class
    Dictionary<string, PropertyInfo> props;

    while (Reader.Read())
    {
        TEntity entity = new TEntity();

        if (!entities.TryGetValue(typeof(TEntity).ToString(), out props))
        {
            //  reflection over TEntity
            props = (from p in entity.GetType().GetProperties()
                     from a in p.GetCustomAttributes(typeof(DataParameterAttribute), false)
                     select p)
                     .ToDictionary(p => p.Name);

            entities.Add(typeof(TEntity).ToString(), props);
        }

        foreach (KeyValuePair<string, PropertyInfo> field in props)
        {
            if (null != Reader[field.Key] && Reader[field.Key] != DBNull.Value)
            { field.Value.SetValue(entity, Reader[field.Key], null); }
        }

        yield return entity;
    }
}

Мы будем очень признательны за любые предложения по повышению производительности...


Обновлять

Я реализовал dapper-dot-net, как предложил @EtienneT - вот время поиска:

[0] retrieved 159180 records in 6874 ms
[1] retrieved 159180 records in 6866 ms
[2] retrieved 159180 records in 6570 ms
[3] retrieved 159180 records in 6785 ms
[4] retrieved 159180 records in 6693 ms
[5] retrieved 159180 records in 6735 ms
[6] retrieved 159180 records in 6627 ms
[7] retrieved 159180 records in 6739 ms
[8] retrieved 159180 records in 6569 ms
[9] retrieved 159180 records in 6666 ms

person IAbstract    schedule 26.05.2011    source источник
comment
160 тыс. записей с 43 полями, заполняющими объекты с помощью отражения? Я бы сказал, что 45-50 секунд вполне прилично, но, возможно, я слишком снисходителен.   -  person Anthony Pegram    schedule 26.05.2011
comment
@Anthony: я думаю, что это достойно ... но я хочу вау - тем более, что я использую словарь для сопоставления считывателя [поля] с propertyInfo ... но, может быть, мои ожидания слишком высоки. ;)   -  person IAbstract    schedule 26.05.2011
comment
Ради интереса попробуйте написать старомодный цикл while(Reader.Read()) в стиле Fx 2.0. Это должно дать вам представление о том, насколько дорого обходятся все эти размышления и косвенные действия.   -  person Henk Holterman    schedule 26.05.2011
comment
И я озадачен тем, что new TEntity() пока что снаружи. После этого все ваши записи одинаковы?   -  person Henk Holterman    schedule 26.05.2011
comment
@Henk: стиль Fx 2.0 ??? Я думал о попытке вставить анонимный тип вместо класса; но иметь IEnumerable‹TConcrete› намного лучше, чем работать с anonTypes или DataTables. Кроме того, мои объекты TConcrete довольно изменчивы, и я планирую использовать их с аналогичной реализацией с DataReader для обратной записи в хранилище данных... если все это имеет смысл. ;)   -  person IAbstract    schedule 26.05.2011
comment
@Хенк: ага - я с этим возился ... спасибо   -  person IAbstract    schedule 26.05.2011
comment
@Anthony: теперь у меня есть мой вау, который я искал. Это быстрее, чем MS Sql Mgr Studio.   -  person IAbstract    schedule 26.05.2011
comment
Смешиваете напитки? (С# и VB)   -  person Aran Mulholland    schedule 12.08.2014
comment
@AranMulholland: к сожалению, да :) в то время у меня был DAL, написанный на VB другим разработчиком, и я создавал серверную часть на C #. Красота .Net!   -  person IAbstract    schedule 12.08.2014
comment
Несколько вещей. 1) Вы передаете параметр Projection в свой метод запроса. Тогда почему бы просто не вернуть спроецированную форму вместо всего этого отражения? Или это была ошибка? 2) Ваша стратегия ничего не назначать свойству, когда значение db равно DBNull, может иметь неприятные последствия. Например, предположим, что ваш конструктор сущности по умолчанию выглядит так: public T() { P = someValue; } .Теперь, если свойство P имеет значение DbNull в базе данных, и вы ничего ему не присваиваете в своем коде отображения, то средство отображения возвращает T, где P = someValue (назначается в конструкторе), а в базе данных это DBNull. Это редкость, но все же..   -  person nawfal    schedule 30.07.2015
comment
Также вы несколько раз читаете из читателя одно значение. Я думаю, что это можно значительно улучшить. См.: codereview.stackexchange.com/ вопросы/58251/. В конечном счете, я считаю, что здесь следует использовать деревья выражений.   -  person nawfal    schedule 01.08.2015
comment


Ответы (1)


Рассматривали ли вы микро-ORM, например, dapper.net?

https://github.com/StackExchange/dapper-dot-net

Он создан разработчиками StackOverflow и сопоставляет SQL-запрос непосредственно с вашими объектами. Он генерирует и кэширует код IL для сопоставления результатов SQL с вашими объектами. Таким образом, код IL генерируется только один раз для каждого типа. Никогда не использовал это, но если вам нужна производительность для сопоставления результатов SQL с объектами .net, вам нужна эта библиотека.

person EtienneT    schedule 26.05.2011
comment
Вау, не знал, что такое существует. Выглядит изящно - person Earlz; 26.05.2011
comment
Я смотрю на него сейчас - думаю, я могу провести довольно быстрый тест - я опубликую некоторые тесты, как только я его закончу. - person IAbstract; 26.05.2011
comment
Нечто подобное сделал Роб Конери: blog.wekeroad.com /helpy-stuff/and-i-shall-call-it-massive Тоже довольно быстро, но больше работает с динамическими объектами. Dapper все же быстрее из того, что я читал. - person EtienneT; 26.05.2011
comment
Единственный минус, который я видел в Dapper, это то, что он генерирует IL для сопоставления ваших объектов, а затем кэширует их. Так что, если в сгенерированном IL есть ошибка, удачи. Посмотрите на изящный код: github.com/ SamSaffron/dapper-dot-net/blob/master/Dapper/ - person EtienneT; 26.05.2011
comment
До сих пор это, кажется, довольно быстро и без проблем. Я не ожидаю многого от ошибок IL, поскольку в них замешан Марк Гравелл. - person IAbstract; 27.05.2011
comment
На странице @IAbstract Dapper GitHub говорится: ручное кодирование с использованием SqlDataReader имеет на 2 мс больше производительности, чем Dapper , поэтому лучше использовать это расширение метод или это для сопоставления с классом. - person Shaiju T; 06.09.2017