piątek, grudzień 31, 2010

Statystyka…

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!

środa, grudzień 29, 2010

Warunkowy breakpoint – tips & tricks

Dziś kolejne, mam nadzieję, ciekawe zastosowanie warunkowego breakpointu. Zanim jednak przejdziemy do omawiania nakreślmy naszą sytuację wyjściową.

Załóżmy, że mamy kawałek kodu aplikacji, który jest dość często wykorzystywany z różnych miejsc.

private void Calculate(params int[] coefficients)

{

    //do sth with coeffs

    Array.ForEach(coefficients, Console.WriteLine);

}

To co on robi nie jest ważne. Istotne jest, że chcielibyśmy postawić w niej breakpoint’a i zobaczyć jak się zachowuje w pewnych sytuacjach. Oczywiście chcielibyśmy, aby breakpoint nie zatrzymywał się w niej zawsze a tylko w wybranych sytuacjach. Moglibyśmy założyć warunkową pułapkę uwzględniając dane, które są w coefficients. Co jeśli one są zmienne? Wiemy jednak że wywołania, którym chcielibyśmy się przyjrzeć dokładniej pochodzą z kilku funkcji, a co więcej wiemy, że na pewno nie pochodzą z CalculateCoeffs. Spróbujmy wykorzystać warunkowy breakpoint wraz z klasami StackTrace i/lub StackFrame.

Czym jest i do czego służy StackTrace, mam nadzieję nikomu nie trzeba tłumaczyć natomiast zobaczymy co nam daje ta klasa w .NET. Pozwalają one uzyskanie ścieżki wywołań metod naszego kodu. Połączmy zatem warunkowy breakpoint ze StackTrace. Co nam wyjdzie?

conditional_stackframe

Przeanalizujmy co mamy. Tworzymy sobie ramkę stosu wywołań (można tu posłużyć się także klasą StackTrace i metodą GetFrame). Do konstruktora przekazujemy parametr 1 co oznacza, że chcemy ominąć aktualną ramkę (czyli metodę w której jesteśmy) a pobrać dane metody ją wywołującej - czyli rodzica. Dalej to już z górki. Pobieramy metodę (GetMethod) oraz jej nazwę (Name) i sprawdzamy czy nie zawiera nazwy metody, która nas nie interesuje. Voila,  gotowe.

Jako “zadanie domowe” można pokusić się o napisanie warunku, który spowoduje zatrzymanie jeśli stack trace nie zawiera wywołania z jakiegoś konkretnego modułu.

Miłego debuggowania.

wtorek, grudzień 21, 2010

WPF Tips & Tricks - PriorityBinding

Piszą swój cykl postów o WPF a w szczególności wpis, o DataBindingu pominąłem jeden ciekawy rodzaj bindingu o którym chciałbym dziś wspomnieć.

Załóżmy, że mamy UI, który wyświetla jakieś elementy, które pobierane są z WebService’u. Oczywiście strzał do WebService’u (® by Marcin Najder) jest kosztowny jeśli chodzi o czas a UI nie chcemy blokować. Możemy wykorzystać w tym celu PriorityBinding. Jak?

Załóżmy, że nasza klasa dostępu do danych wygląda następująco:

public class DataAccess

{

    public List<string> WebService

    {

        get

        {

            Thread.Sleep(5000);

            return new List<string> { "1 from WebService", "2 from WebService", "3 from WebService" };

        }

    }

 

    public List<string> Cache

    {

        get

        {

            Thread.Sleep(1500);

            return new List<string> { "1 from cache", "2 from cache" };

        }

    }

 

    public List<string> Instant

    {

        get

        {

            return new List<string> { "Loading. Please wait..." };

        }

    }

}

Mamy zatem 3 źródła różniące się szybkością dostępu. Instant nie zawiera żadnych aktualnych danych, cache ma przechowywane lokalnie dane, które oczywiście mogą być nie do końca prawidłowe, natomiast WebService zwraca nam najnowsze dane. Jak napisać binding, aby nasz UI aktualizował się w miarę jak będziemy otrzymywać dane tj. aby od razu wypisał to co zwróci Instant a później gdy otrzyma dane z Cache lub z WebService zaktualizował się automatycznie? <PriorityBinding.

<ListBox>

    <ListBox.ItemsSource>

        <PriorityBinding>

            <Binding Path="WebService" IsAsync="True" />

            <Binding Path="Cache" IsAsync="True" />

            <Binding Path="Instant" />

        </PriorityBinding>

    </ListBox.ItemsSource>

</ListBox>

Oczywiście zakładając, że DataContext jest ustawiony na naszą klasę DataAccess. Jak to zadziała? Kolejność wpisów ustala priorytet. Im wyżej tym ważniejsze i prawdopodobnie bardziej czasochłonne wykonanie funkcji. Im niżej tym powinno ono być mniej ważne i szybsze. Dzięki ustawieniu parametru IsAsync na true, wywołania czasochłonne nie zostaną wywołane w wątku UI, blokując go jednocześnie a w wątku BackgroundWorker’a.

Efekt finalny…

Nice :)

Miłego kodowania!

poniedziałek, grudzień 13, 2010

Jak zostać guru klawiatury

Wszyscy wiemy, iż opanowanie operowania VisualStudio z klawiatury to podstawa do szybkiej i wydajnej pracy. Nikt nie będzie nawigował po pozycjach w menu aby dodać plik jeśli może znacznie szybciej zrobić to używając skrótu CTRL+SHIFT+A. Dziś zobaczymy jak możemy wejść na jeszcze wyższy poziom wtajemniczenia.

Zapewne wszyscy znają już ukryte funkcje FindBox’a (jeśli nie – zapoznaj się z wpisem – Ukryte możliwości FindBox’a). Dziś zobaczymy jeszcze bardziej ukryte jego możliwości. Zanim jeszcze zaczniemy musimy zacząć od podstaw podstaw. Jak szybko dostać się do tego pola? Cała zabawa traci sens, jeśli chcąc przyśpieszyć naszą pracę będziemy za każdym razem nawigować się do tego pola używając myszy.

access

Tak więc powtarzajmy jak mantrę kombinację CTRL+/ (w ustawieniach klawiatury *bodaj* General) póki nie zapadnie ona u nas na stałe w pamięci.

Dla przykładu. Znasz nazwę metody, na której chciałbyś/chciałabyś postawić pułapkę, ale nie chce ci się szukać po swoich plikach? Wpisz nazwę metody i naciśnij F9 – Bum. Masz już pułapkę na wszystkich metodach, których nazwa pasuje do tego tekstu. We wszystkich plikach (to jest i plus i minus :)).

Masz wpisany numer wiersza do którego chcesz przeskoczyć? Wystarczy wcisnąć CTRL+G i już tam się znajdziesz (choć tu szybsze jest po prostu CTRL+G i wpisanie wiersza w okienku, które się pojawi).

Zamiast chodzić po rozległych menu wpisz po prostu >NazwaMenu.NazwaPolecenia i Enter i już się ono wykona. Dla przykładu >Build.BuildSolution lub >Tools.Options. Sweet :)

Zaktualizowany link do opisu funkcji FindBox’a – Predefined Visual Studio Aliases

sobota, grudzień 04, 2010

Warunkowy breakpoint – ciekawe zastosowanie

conditional_brk

Zapewne wszyscy zdają sobie sprawę, że nasz breakpoint możemy uczynić warunkowym tak aby VS zatrzymało się na nim tylko w specyficznej sytuacji a nie za każdym razem. Gdy breakpoint jest warunkowy jego ikona posiada mały biały plusik tak jak na obrazku w tym paragrafie abyśmy mogli odróżnić go od innych. Dziś pokażemy sobie, że warunkowy breakpoint może być użyty także do innych, mniej oczywistych (mam nadzieję) celów.

Załóżmy, że jesteśmy w trakcie debuggowania dość skomplikowanego kodu (ten poniżej takowym nie jest :)).

while (true)

{

    int receivedBytes = ReceivePackage();

    if (receivedBytes == Package.Length)

    {

        if (ParsePackage())

            break;

    }

}

Przetwarzamy sobie w nim jakieś pakiety. Zrobiliśmy już kawał dobrej roboty w identyfikacji i wiemy, że to ten fragment powoduje błędy. Chcemy zmusić aby flow programu przechodził jednak przez gałąź tak jakby if zwracał true. Oczywiście moglibyśmy zakończyć sesję debuggowania, zmienić kod na if (true) lub całkowicie warunek z if usunąć, tylko w jakim celu? Jeśli dość dużo czasu zajęło nam identyfikowanie problemu, lepiej nie ryzykować konieczności wykonania tej pracy raz jeszcze. Ustawmy na nim warunkowy breakpoint. Klikamy zatem na zwykłym breakpoincie PPM i wybieramy Condition…

condition_brk

Część z osób, które już wcześniej używały dobrodziejstw warunkowych pułapek, może się zdziwić i właśnie krzyczy, że jest błąd: “Tam powinien być znak ==”. Tak, zgoda. W normalnym przypadku tam powinien być znak porównania a nie podstawienia. Ale my nie chcemy normalnego przypadku a ten ciekawy :). Co się stanie w takim przypadku? Tak jak przy zwykłym wykorzystaniu tej funkcji, kod programu będzie wykonywany aż dojdzie do naszej pułapki, zostanie wykonany warunek a ponieważ nie zwróci on prawdy pułapka będzie nieaktywna. Kod wykona się dalej, ale ponieważ w naszym wyrażeniu przypisujemy do zmiennej receivedBytes wartość Package.Length warunek w if będzie prawdziwy i za każdym razem wykona się ParsePackage. Czyż to nie piękne? :) Dla nieznających tej funkcjonalności dodam, że jako warunek można podać w zasadzie dowolne wyrażenie. Chcemy sobie z niego zrobić Tracepoint’a? Nie ma sprawy wystarczy napisać: Console.WriteLine(receivedBytes).I konsola zapełni nam się ładnie danymi... Na plus jest jeszcze to, że działa tam Intellisense, tak więc nie musimy wszystkiego pamiętać.

Mam nadzieję, że pokazałam dość nietypowe zastosowanie warunkowego breakpointa, które może się czasem przydać, aby oszczędzić trochę czasu podczas długich i wycieńczających sesji debuggowania :).