Nie… w tym wpisie nie będzie o statystyce odwiedzin bloga i innych podobnych rzeczach, o których można dziś na wielu blogach (u mnie o tym będzie może następny wpis :)). Będzie o klasie o której pewnie niewielu z was wcześniej wiedziało. Do wczoraj nie wiedziałem także i ja. Poznajcie – SqlStatistics. Klasa jest internal sealed tak więc czemu o niej cokolwiek piszę? A no ponieważ do samej klasy dostać się nie możemy natomiast do danych przez nią zbieranych już tak. Śledząc jej zależności Reflector’em zauważyłem, że klasa SqlConnection ma niewinną metodę RetrieveStatistics ukrytą wśród wielu innych znajdujących się tam metod.

Reflector podaje, że klasa ta zawiera takie pola jak _buffersReceived, _buffersSent, _selectCount, _selectRows i trochę innych zatem jest tam trochę danych, które mogą się nam przydać. Jak zatem możemy z tych danych skorzystać? Przede wszystkim trzeba je włączyć. Odpowiada za to właściwość EnableStatistics. Aby umożliwić jeszcze łatwiejsze z nich korzystanie postanowiłem napisać małe klasy helperów w stylu Extensions Methods.

namespace Octal.Helpers
{
    public static class SqlExtensions
    {
        public static SqlConnection WithStatistics(this SqlConnection connection)
        {
            EnableStatistics(connection);
            return connection;
        }
        [Conditional(“DEBUG”)]
        private static void EnableStatistics(SqlConnection connection)
        {
            if (connection != null)
                connection.StatisticsEnabled = true;
        }
        [Conditional(“DEBUG”)]
        public static void PrintStatistics(this SqlCommand command, TextWriter writer, bool reset)
        {
            if (command == null || command.Connection == null || writer == null)
                return;
            var stats = command.Connection.RetrieveStatistics();
            writer.WriteLine(“SQL query: {0}”, command.CommandText);
            Array.ForEach(stats.Cast<DictionaryEntry>().ToArray(),
                    entry => writer.WriteLine(string.Format(“Klucz: {0}, Wartość: {1}”,
                                                              entry.Key, entry.Value)));           
            writer.WriteLine();
            if (reset)
                command.Connection.ResetStatistics();
        }
    }
}

Co tu robimy? Definiujemy Extension Method, która ustawia właściwość EnableStatistics na połączeniu na true. Dodatkowe wywołanie metody w .WithStatistics podyktowane jest chęcią umieszczenia atrybutu Conditional. Dzięki temu nasz kod wykona się tylko w trybie Debug.
PrintStatistics to zwykłe wypisanie uzbieranych danych. Definiujemy je na klasie SqlCommand, abyśmy mogli dobrać się do treści zapytania, które wykonywaliśmy. Na koniec, w zależności od parametru resetujemy wyniki (czyli dla kolejnego zapytania będziemy mieli dane tylko tego nowego zapytania).
Jak tego używać? Bardzo prosto. Jeśli chcemy aby jakieś zapytanie było uruchomione z włączonymi statystykami musimy dodać wywołanie .WithStatistics() na połączeniu, którego dane zapytanie używa (niestety statystyki znajdują się na klasie połączenia).

using (var connection = new SqlConnection(@”Data Source=.\SQlExpress;Initial Catalog=test;Integrated Security=True”)
                                                                                                .WithStatistics())
{               
    if (connection.State != ConnectionState.Open)
        connection.Open();
    using (var command = new SqlCommand(“SELECT * FROM test t1,test t2,test t3, test t4, test t5, test t6”,
                                                                                          connection))               
    {
        using (var reader = command.ExecuteReader())
        {
            if (reader != null)
                while (reader.Read()) { }
        }
        command.PrintStatistics(Console.Out, true);
    }
    using (var cmd = new SqlCommand(“SELECT * from test”,connection))
    {
        cmd.ExecuteReader();
        cmd.PrintStatistics(Console.Out, true);
    }
}

Potem zostaje nam już tylko wydrukować nasze statystyki za pomocą PrintStatistics. Wynik?
result
Nasze dane w ładnej postaci. Oczywiście z racji atrybutu Conditional, nic z tego nie będzie działać w trybie Release. Zobaczę, czy da się to także ładnie wprowadzić do LINQu.
Miłego kodowania!