Каков самый быстрый способ чтения данных из DbDataReader?

В следующем коде команда - это DbCommand, которая уже настроена:

using( var dataReader = command.ExecuteReader() /*The actual execution of the query takes relatively little time.*/ ) {
                while( dataReader.Read() ) {
                    // These are what take all of the time. Replacing them all with reader.GetValues( myArray ) has no impact.
                    val0 = dataReader.GetValue( 0 );
                    val1 = dataReader.GetValue( 1 );
                    val2 = dataReader.GetValue( 2 );
                }
            }

Большая часть времени для запроса, с которым я работаю в настоящее время, тратится на вызовы GetValue. Собираетесь ли вы туда и обратно в базу данных для каждого вызова GetValue? Кажется, это так, и это кажется очень неэффективным. Как отмечает код, попытка сделать это одним выстрелом с использованием GetValues ​​() не имеет значения. Есть ли способ получить всю строку за один выстрел? Еще лучше, есть ли способ получить весь результат за один выстрел?

Спасибо.

Ответы

Ответ 1

using (connection)
    {
        SqlCommand command = new SqlCommand(
          "SELECT CategoryID, CategoryName FROM dbo.Categories;" +
          "SELECT EmployeeID, LastName FROM dbo.Employees",
          connection);
        connection.Open();

        SqlDataReader reader = command.ExecuteReader();

        while (reader.HasRows)
        {
            Console.WriteLine("\t{0}\t{1}", reader.GetName(0),
                reader.GetName(1));

            while (reader.Read())
            {
                Console.WriteLine("\t{0}\t{1}", reader.GetInt32(0),
                    reader.GetString(1));
            }
            reader.NextResult();
        }
    }

Ответ 2

Я немного сравнивал себя с различными подходами:

public DataTable Read1(string query)
{
    using (var cmd = conn.CreateCommand())
    {
        cmd.CommandText = query;
        cmd.Connection.Open();
        var table = new DataTable();
        using (var r = cmd.ExecuteReader())
            table.Load(r);
        return table;
    }
}

public DataTable Read2<S>(string query) where S : IDbDataAdapter, IDisposable, new()
{
    using (var da = new S())
    {
        using (da.SelectCommand = conn.CreateCommand())
        {
            da.SelectCommand.CommandText = query;
            DataSet ds = new DataSet();
            da.Fill(ds);
            return ds.Tables[0];
        }
    }
}

public IEnumerable<S> Read3<S>(string query, Func<IDataRecord, S> selector)
{
    using (var cmd = conn.CreateCommand())
    {
        cmd.CommandText = query;
        cmd.Connection.Open();
        using (var r = cmd.ExecuteReader())
            while (r.Read())
                yield return selector(r);
    }
}

public S[] Read4<S>(string query, Func<IDataRecord, S> selector)
{
    using (var cmd = conn.CreateCommand())
    {
        cmd.CommandText = query;
        cmd.Connection.Open();
        using (var r = cmd.ExecuteReader())
            return ((DbDataReader)r).Cast<IDataRecord>().Select(selector).ToArray();
    }
}

public List<S> Read5<S>(string query, Func<IDataRecord, S> selector)
{
    using (var cmd = conn.CreateCommand())
    {
        cmd.CommandText = query;
        cmd.Connection.Open(); 
        using (var r = cmd.ExecuteReader())
        {
            var items = new List<S>();
            while (r.Read())
                items.Add(selector(r));
            return items;
        }
    }
}

1 и 2 возвращает DataTable, в то время как остальные строго типизируют результирующий набор, поэтому его точно не яблоки для яблок, а я, в то время как время их соответственно.

Самое главное:

Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 100; i++)
{
    Read1(query); // ~8900 - 9200ms

    Read1(query).Rows.Cast<DataRow>().Select(selector).ToArray(); // ~9000 - 9400ms

    Read2<MySqlDataAdapter>(query); // ~1750 - 2000ms

    Read2<MySqlDataAdapter>(query).Rows.Cast<DataRow>().Select(selector).ToArray(); // ~1850 - 2000ms

    Read3(query, selector).ToArray(); // ~1550 - 1750ms

    Read4(query, selector); // ~1550 - 1700ms

    Read5(query, selector); // ~1550 - 1650ms
}

sw.Stop();
MessageBox.Show(sw.Elapsed.TotalMilliseconds.ToString());

Запрос возвратил около 1200 строк и 5 полей (выполняется 100 раз). Помимо Read1 все хорошо. Из всего, что я предпочитаю Read3, который возвращает данные лениво, как перечислены. Это полезно для памяти, если вам нужно только перечислить ее. Чтобы иметь копию коллекции в памяти, вам лучше с Read4 или Read5, как вам удобно.

Ответ 3

Я бы использовал что-то вроде dapper-dot-net, чтобы загрузить его в базовую модель типа; это микро-ORM, поэтому вы получаете преимущества метапрограммирования (эффективно созданный IL и т.д.) - без накладных расходов на такие вещи, как EF или DataTable.

Ответ 4

Вы можете использовать DbDataAdapter, чтобы получить все результаты и сохранить их в DataTable.

Ответ 5

        Dim adapter As New Data.SqlClient.SqlDataAdapter(sqlCommand)
        Dim DT As New DataTable
        adapter.Fill(DT)

Ответ 6

Используйте Untyped DataSet. Это самый быстрый, насколько мне известно.