24/05/2012

Krótka lekcja na temat char i varchar

Home

Na początek prosty kawałek kodu w T-SQL, w którym sprawdzane jest, czy zadany ciąg znaków pasuje do podanego wzorca tj. czy zaczyna się dwoma cyframi:

declare @input char(10)
declare @pattern char(100)

SET @input = '12aaabbb'
SET @pattern = '[0-9][0-9]%'

if @input like @pattern
 print 'OK'
else
 print 'Fail'

Pomimo, że ciąg znaków pasuje do wzorca to warunek dopasowania nie jest spełniony i na ekran zostanie wypisany napis Fail. Dzieje się tak ponieważ kiedy przypisujemy wzorzec do zmiennej @pattern, która jest typu char(100) to zostanie on dopełniony spacjami. A więc przy testowaniu warunku tak naprawdę sprawdzamy czy zadany ciąg znaków zaczyna się dwoma cyframi i kończy przynajmniej 89 spacjami (100 - długość wzorca).

Można to naprawić przechowując wzorzec w zmiennej typu varchar albo stosując funkcję rtrim. Wszystko zależy od konkretnej sytuacji ale przy wykonywaniu różnych operacji na ciągach znaków należy zawsze pamiętać o różnicy pomiędzy char i varchar. Sprawa wydaje się prosta ale kiedy pracujemy z dużą ilością kodu bazodanowego łatwo można coś przeoczyć, a znalezienie takiego błędu może nie być wbrew pozorom proste.

13/05/2012

Podglądanie tabel tymczasowych

Home

Czasami zdarza się, że pracujemy z aplikacją, która tworzy tabelę tymczasową, a następnie woła serię procedur składowanych, które: wypełniają tę tabelę, modyfikują ją itd. W takim wypadku dość często np.: przy debugowaniu błędu, potrzeba podejrzeć zawartość takiej tabeli. Pół biedy kiedy jest to globalna tabela tymczasowa np.: ##SomeTempTable. Wtedy wystarczy zatrzymać aplikację na pułapce i wykonać zapytanie w SQL Server Management Studio np.: select * from ##SomeTempTable.

Niestety, jeśli globalna tabela tymczasowa została utworzona w transakcji, to już nie zadziała. Jeszcze gorzej jest w przypadku lokalnych tabel tymczasowych np.: #SomeTempTable, których nie podejrzymy nawet jeśli zostały utworzone poza transakcją. Co w takim wypadku?

Ja w takiej sytuacji korzystam z możliwości Visual Studio, a dokładniej z potęgi okna Immediate (Debug -> Windows -> Immediate Ctrl + D, I). Zakładam, że wykonując poniższe kroki aplikacja zatrzymana jest na jakiejś pułapce np.: przed uruchomieniem kolejnej procedury robiącej coś z tabelą tymczasową.
  • W oknie Immediate wpisuje DataTable dt = new DataTable();
  • Również w oknie Immediate, wpisuję dt = DataProvider.Instance.ExecuteDataTable("select * from #SomeTempTable");.
  • Przechodzę np.: do okna Watch, podaję nazwę utworzonej zmiennej czyli dt i cieszę się wbudowanym w VS wizualizatorem dla klasy DataTable.


Kilka słów wyjaśnienia w sprawie DataProvider.Instance.ExecuteDataTable. Zakładam tutaj, że jeśli ktoś pisze aplikację bazodanową i nie korzysta z ORM, to ma napisaną jakąś pomocnicza klaskę, która: zarządza połączeniem do bazy danych, pozwala łatwo wykonać zapytania bez potrzeby każdorazowego ręcznego tworzenia DbCommand itd. W tym przypadku jest to klasa DataProvider, która jest singleton'em. Jest to o tyle ważne, że kiedy odwołuję się do niej w oknie Immediate to korzystam z tego samego połączenia co aplikacja, a więc mogę podejrzeć tabele tymczasowe. Pod spodem wykonywane jest zwykłe zapytanie w stylu ADO.NET.

Ostatnio odkryłem również fajny projekt sp_select składający się z dwóch procedur składowanych, które w "magiczny" sposób pozwalają podejrzeć lokalną tabelę tymczasową korzystając z innego połączenia niż to, w którym tabela została utworzona.

09/05/2012

Dziwne zachowanie konstruktora statycznego - ciąg dalszy

Home

Na początku marca pisałem o "dziwnym" zachowaniu konstruktora statycznego. W skrócie chodziło o to, że jeśli chcieliśmy wywołać metodę w domenie aplikacyjnej innej niż domyślna, konstruktor statyczny klasy, do której należała ta metoda, wołany był o jeden raz więcej w środowisku x64 niż w środowisku x86. Ponieważ sprawa mnie nurtowała postanowiłem zadać pytanie poprzez portal Microsoft Connect. Zajęło to trochę ale w końcu uzyskałem wyjaśnienie. Odpowiedź jest dość ciekawa, a więc zapraszam do lektury.

08/05/2012

RavenDB (cz. 7) - HttpListenerException

Home

Ten post będzie krótki i zwięzły. Jakiś czas temu kiedy włączyłem UAC (User Account Control) ze zdziwieniem zauważyłem, że moja aplikacja używająca Raven DB nie działa. Przy wywołaniu metody DocumentStore.Initialize rzucany był wyjątek HttpListenerException. Po wyłączeniu UAC błąd nie występował.

Z problem łatwo sobie poradzić nadając użytkownikowi, jaki uruchamia aplikację, uprawnienia do nasłuchiwania na porcie używanym przez bazę danych. Można to zrobić przy pomocy narzędzia httpcfg lub netsh tak jak to zostało opisane w tym dokumencie. Co jednak najlepsze Raven DB dostarcza gotowej klasy NonAdminHttp, w bibliotece Raven.Database.dll, która rozwiązuje ten problem. Poniżej przykład użycia.

...
NonAdminHttp.EnsureCanListenToWhenInNonAdminContext(Store.Configuration.Port);

Store.Initialize();
...

A tak w ogóle to warto zajrzeć, przy pomocy jakiegoś deasemblera, do wnętrza metody EnsureCanListenToWhenInNonAdminContext i zobaczyć jak została zaimplementowana.

Podsumujmy co już umiemy:
  • Osadzić Raven DB w aplikacji hostującej.
  • Zainicjować Raven DB.
  • Skonfigurować dostęp do Raven Studio i API REST'owego.
  • Tworzyć obiekty POCO jakie mogą zostać umieszczone w Raven DB.
  • Dodawać/usuwać/modyfikować dokumenty.
  • Zadawać proste i te trochę bardziej skomplikowane zapytania.
  • Utworzyć indeks.
  • Skorzystać z algorytmu Map/Reduce.
  • Skorzystać z zapytań Lucene.
  • Wymusić zwrócenie przez zapytanie aktualnych danych.
  • Sterować tym, które właściwości zostaną zapisane do bazy danych.
  • Rozwiązać kłopoty związane z IntelliTrace i Raven DB.
  • Rozwiązać problem z HttpListenerException.

12/04/2012

Programmable Data Query (cz.2)

Home

W poprzednim poście opisałem czym są Programmable Data Query i jak zdefiniować wykorzystujące je zdarzenie IntelliTrace. Teraz pora na przedstawienie przykładowej implementacji PDQ. Zacznijmy jednak od przypomnienia do czego służą poszczególne metody interfejsu IProgrammbleDataQuery. Potrzebne informacje zamieściłem na poniższej mapie myśli:



Poniższe PDQ może zostać użyte z każdym zdarzeniem diagnostycznym. Jest proste ale nie znajdziemy analogicznego w bibliotece Microsoft.VisualStudio.DefaultDataQueries.dll. Bardzo dobrze nadaje się jako baza to tworzenia bardziej skomplikowanych rozwiązań.

Jego działanie polega na odczytaniu wartości parametrów aktualnych wywołania metody lub wartości zwróconej przez metodę i stworzeniu na tej postawie opisu zdarzenia. Dla przypomnienia opis zdarzenia wyświetlany jest przez Visual Studio w czasie przeglądania nagranego logu IntelliTrace.

public class Test : IProgrammableDataQuery
    {
        public object[] EntryQuery(object thisArg, object[] args)
        {
            return args;
        }

        public object[] ExitQuery(object returnValue)
        {
            return new object[] { returnValue };
        }

        public List<CollectedValueTuple> FormatCollectedValues(object[] results)
        {
            List<CollectedValueTuple> list = new List<CollectedValueTuple>();
            for (int i = 0; i < results.Length; ++i)
            {
                list.Add(new CollectedValueTuple(
                    String.Format("Result{0}", i), 
                    String.Format("'{0}'",results[i]), 
                    "string"));
            }
            return list;
        }

        public string FormatLongDescription(object[] results)
        {
            try
            {
                StringBuilder builder = new StringBuilder();
                builder.AppendFormat("Collected objects ({0}): ", results.Length);
                bool flag = true;
                foreach (var r in results)
                {
                    if (!flag)
                    {
                        builder.Append(", ");
                    }

                    flag = false;
                    builder.AppendFormat("'{0}'", r);
                }

                return builder.ToString();
            }
            catch (Exception ex)
            {
                return ex.ToString();
            }
        }

        public string FormatShortDescription(object[] results)
        {
            return FormatLongDescription(results);
        }

        public List<Location> GetAlternateLocations(object[] results)
        {
            return null;
        }
    }
Implementacja EntryQuery oraz ExitQuery jest trywialna. W żaden sposób nie modyfikuję danych wejściowych i po prostu przekazuje je dalej. FormatLongDescription wykorzystuje te dane do stworzenia opisu zdarzenia po prostu sklejając poszczególne elementy tablicy. Dla zdarzenia przeznaczonego do analizowania danych wejściowych będą to parametry aktualne, a dla zdarzenia przeznaczonego do analizowania danych wyjściowy wynik zwrócony przez metodę. FormatShortDescription po prostu wywołuje FormatLongDescription. Logika FormatCollectedValues też nie jest skomplikowana. Metoda ta po prostu zwraca dane jakie otrzymała na wejściu, dodatkowo nadając im etykiety Result0, Result1 itd.