ADO.NET proste zapytania do bazy cz. 2

 

Dla przypomnienia, poprzednia część rozważań na temat zapytań do bazy danych zakończyła się na wywołaniu zapytania komendą:

SqlDataReader reader = cmd.ExecuteReader();

wyniki natomiast odbierane były za pomocą odwołania:

reader["Id"];

przy czym należy pamiętać o zamknięciu strumienia, aby nie blokować otwartego połączenia:

reader.Close();
lub wykorzystaniu bloku using, która na zakończenie tego właśnie bloku zwalnia wszelkie zasoby, zatem powinien również zamknąć strumień, jakim jest reader. Warto jeszcze raz podkreślić, że raz odczytane dane z obiektu SqlDataReader stają się niedostępne (nie można ich odczytać jeszcze raz).

W niniejszej części drugiej chciałbym zademonstrować bardzieje efektywne metody odczytu danych z zapytania wysłanego do bazy.

Przykład 2

W poprzednim przykładzie dane pobierane były jedynie w celu wyświetlenia ich na ekranie. Tym razem spróbujmy przypisać odczytane dane odpowiednim zmiennym. Zapytanie tym razem będzie miało następującą postać:

strSql = "SELECT Id, Age, ExperienceLevel FROM dbo.SimpleLogins WHERE Id = 1";

co oznacza, że będziemy potrzebować zmiennej o typie odpowiadającym typowi tinyint w Sql Server, czyli byte, zmiennej typu int, która określa wiek oraz zmiennej opisującej doświadczenie, w tym przypadku typ zmiennej zarówno w języku C# jak i w Sql Server brzmi tak samo – będzie to zatem decimal.

Jak zostało to zaznaczone w poprzedniej części wspomniane na początku pobranie wartości z bazy danych przy użyciu indeksów zwraca obiekty typu Object. Minusem takiej operacji jest to, że konieczne w przypadku przypisania do zmiennej będzie wykonanie operacji unboxingu. Dobra praktyka programistyczna nie pozwala na tego typu marnotrawstwo zasobów, zatem udostępnione zostały metody zwracające wartość określonego typu. Zobaczmy jak to wygląda na przykładzie:

protected void GetTypesButton_Click(object sender, EventArgs e)
{
    string strCon, strSql;
    byte idValue = 0;
    int ageValue = 0;
    decimal experienceValue = 0;

    strCon = BuildConnectionString();
    
    //Create new SqlConnection object using given string
    using (SqlConnection cn = new SqlConnection(strCon))
    {
        strSql = "SELECT Id, Age, ExperienceLevel FROM dbo.SimpleLogins WHERE Id = 1";
          
        //Open the connection
        cn.Open();
        
        //Create SqlCommand object in order to query database
        using (SqlCommand cmd = new SqlCommand(strSql, cn))
        {
            try
            {
                //Use SqlDataReader to retrieve sequentially results
                using(SqlDataReader reader = cmd.ExecuteReader())
                {
                    if (reader.FieldCount == 3)
                    {
                        while (reader.Read())
                        {
                             //Use ordinal numbers to retrieve results
                            idValue = reader.GetByte(0);
                            ageValue = reader.GetInt32(1);
                            experienceValue = reader.GetDecimal(2);
                        }
                    }
                    reader.Close();
                }
            }
            catch (Exception ex)
            {
                //implement
            }
        }
    }

    //Write these values to output 
    System.Diagnostics.Debug.WriteLine("idValue: " + idValue.ToString());
    System.Diagnostics.Debug.WriteLine("ageValue: " + ageValue.ToString());
    System.Diagnostics.Debug.WriteLine("experienceValue: " + experienceValue.ToString());
}

Wykorzystanie tych metod nastąpiło poprzez wywołania:
  • reader.GetByte(0);
  • reader.GetInt32(1);
  • reader.GetDecimal(2);

Widać zatem, że odwołania do obiektów mają inną postać. Nie dość, że korzystamy z metod zwracających określony typ danych to jeszcze nie pojawił się indeks z odpowiednimi nazwami, a mało znaczące cyfry jako argumenty wywołania. To jest właśnie jedno z ograniczeń tego podejścia. Mianowicie nie możemy już odwoływać się po nazwie do odpowiedniej zmiennej, a musimy skorzystać z numery porządkowego, który odpowiada odpowiednim wartościom w zapytaniu. Oznacza to, że w przypadku wywołania reader.GetByte(0) zaznaczamy, że chcemy tej zmiennej przypisać wartość kolumny nazwanej “Id”. Pomimo tej niegodności taki sposób odczytu wyników powinien być znacznie szybszy, bowiem kompilator (czy też inne cudo zajmujące się tym zadaniem) nie musi porównywać niejednokrotnie długich nazw.

Mamy zatem szybką metodę do odczytu danych z bazy, dobrze działającą z typami zaimplementowanymi w C#. Otóż nie do końca. Rozważmy następujący przykład.

Przykład 3

W poprzednim przykładzie dane odczytywane z wiersza były określone, tzn. nie zawierały wartości nieznanych – NULL. Tym razem odczytajmy dane z wiersza oznaczonego poprzez identyfikator Id = 3:

strSql = "SELECT Id, Age, ExperienceLevel FROM dbo.SimpleLogins WHERE Id = 3";

Zaglądając do tabeli zamieszczonej w poprzedniej części przedstawionej na blogu, zauważmy, że jedna z interesujących nas w tym zapytaniu zmiennych, czyli wartość kolumny Age jest nieokreślona – przyjmuje wartość NULL. Jak w tym przypadku zachowa się poprzedni kod (jest on taki sam, z wyjątkiem przedstawionego powyżej zapytania)? Oto wyniki:

Data is Null. This method or property cannot be called on Null values.

idValue: 3

ageValue: 0

experienceValue: 0

Widzimy więc, że o ile identyfikator został odczytane poprawnie o tyle zmienna ageValue ma początkową wartość 0, mimo iż w bazie danych nie była ona zdefiniowana, a takżę wartość zmiennej experienceValue również wynosi 0 pomimo, że w bazie znajdowała się wartość 2.2 (po wyrzuceniu wyjątku pozostałe dane nie zostały odczytane). Nie jest to zatem to, o co chodziło.

Jeśli chcielibyśmy przypisać wartości z bazy danych, które mogą zawierać wartości NULL należałoby najpierw pobrać informację, czy wyniki zapytania taką wartość zawierają. Efekt taki można uzyskać przy użyciu metody IsDBNull() wywoływanej na obiekcie typu SqlDataReaderi zastosowaniu instrukcji warunkowej:

if (reader.IsDBNull(1))
{
    ageValue = null;
}
else
{
    ageValue = reader.GetInt32(1);
}

if (reader.IsDBNull(2))
{
    experienceValue = null;
}
else
{
    experienceValue = reader.GetDecimal(2);
}

pamiętając przy tym, aby odpowiednie zmienne zadeklarowane były jako typ nullable, czyli:
int? ageValue = null;
decimal? experienceValue = null;

W takim przypadku na wyjściu otrzymujemy:

idValue: 3

ageValue:

experienceValue: 2.2

czyli oczekiwane wyniki. Czy jednak pisanie instrukcji warunkowych dla każdej zmiennej jest efektywne? Po raz kolejny odpowiedź brzmi – nie.

Przykład 4

Twórcy platformy .NET z pewnością przewidzieli, że taki sposób pisania kodu może być nieco uciążliwy i postarali się ułatwić ten proces wprowadzając “typy Sql” do języków programowania. Przed skorzystaniem z nich należy zadbać o dodanie odpowiedniej przestrzenii nazw, czyli:

using System.Data.SqlTypes;

Typy te mają wbudowaną obsługę wartości NULL. Na stronach msdn można znaleźć następującą informację o tych typach: “The System.Data.SqlTypes namespace provides classes for native data types in SQL Server. These classes provide a safer, faster alternative to the data types provided by the .NET Framework common language runtime (CLR). Using the classes in this namespace helps prevent type conversion errors caused by loss of precision. Because other data types are converted to and from SqlTypes behind the scenes, explicitly creating and using objects within this namespace also yields faster code.”

Otrzymujemy zatem, wedle zapewnień, “szybsze” typy niż te, które oferowane są jako wbudowane (porównanie typów Sql można znaleźć na stronie http://msdn.microsoft.com/en-us/library/system.data.sqltypes.aspx). Ponadto oferują one również dostęp za pomocą metod zwracających określony typ danych. Można by rzec rozwiązanie doskonałe. Spójrzmy zatem na następujący kod źródłowy:

protected void GetSqlTypesButton_Click(object sender, EventArgs e)
{
    string strCon, strSql;
    SqlByte idValue = 0;
    SqlInt32 ageValue = 0;
    SqlDecimal experienceValue = 0;
    
    strCon = BuildConnectionString();

    //Create new SqlConnection object using given string
    using (SqlConnection cn = new SqlConnection(strCon))
    {
        strSql = "SELECT Id, Age, ExperienceLevel FROM dbo.SimpleLogins WHERE Id = 3";

        //Open the connection
        cn.Open();

        //Create SqlCommand object in order to query database
        using (SqlCommand cmd = new SqlCommand(strSql, cn))
        {
            try
            {
                //Use SqlDataReader to retrieve sequentially results
                using (SqlDataReader reader = cmd.ExecuteReader())
                {
                    try
                    {
                        if (reader.FieldCount == 3)
                        {
                            while (reader.Read())
                            {
                                //Use ordinal numbers to retrieve results
                                idValue = reader.GetSqlByte(0);                            
                                ageValue = reader.GetSqlInt32(1);
                                experienceValue = reader.GetSqlDecimal(2);
                            }
                        }
                    }
                    catch (SqlNullValueException ex)
                    {
                        System.Diagnostics.Debug.WriteLine(ex.Message);
                    }
                    finally
                    {
                        reader.Close();
                    }
                }
            }
            catch (Exception ex)
            {
                //implement
            }
        }
    }

    //Write these values to output 
    System.Diagnostics.Debug.WriteLine("idValue: " + idValue.ToString());
    System.Diagnostics.Debug.WriteLine("ageValue: " + ageValue.ToString());
    System.Diagnostics.Debug.WriteLine("experienceValue: " + experienceValue.ToString());
}

Mamy deklarację odpowiednich zmiennych wykorzystujących typy Sql, czyli:
SqlByte idValue = 0;
SqlInt32 ageValue = 0;
SqlDecimal experienceValue = 0;
oraz użycie odpowiednich metod dla tych typów:
idValue = reader.GetSqlByte(0);
ageValue = reader.GetSqlInt32(1);
experienceValue = reader.GetSqlDecimal(2);
Wyniki uzyskane dzięki wywołaniu kodu znajdującego się w metodzie obsługującej zdarzenia dla GetSqlTypesButton wyglądają następująco:

idValue: 3

ageValue: Null

experienceValue: 2.2

Zgodne z oczekiwaniami wyniki nie pozostawiają żadnych zarzutów, co można uznać za mały sukces 😉

W następnym poście na moim blogu przedstawię proste wykorzystanie ASP.NET do uzyskanych w ten sposób wyników. Nikt bowiem tworząc aplikację internetową nie będzie przedstawiał wyników na wyjściu diagnostycznym.

P.S. Widzę, że część osób jest zainteresowanych z moimi zmaganiami z ADO.NET opisywanymi na blogu więc, w razie gdybym jakieś zagadnienie poruszył niedostatecznie lub niejasno, zachęcam do zostawienia jakiegoś feedbacka w komentarzach. Zaznaczam przy tym, że jestem osobą początkującą w tym temacie, jednak na postawione pytania w ramach moich umiejętności oraz wolnego czasu postaram się odpowiedzieć.

Reklamy

14 Responses to ADO.NET proste zapytania do bazy cz. 2

  1. Krzysztof Smętek says:

    Zgodnie z życzeniem, jeśli ktoś ma coś do dodania do artykułu, to pozwolę sobie na parę słów uwagi. Napotkałem na problem przypisania nulli do zmiennych ktore będą parametrami dla Stored Procedure. Artykuł ten jest fajny i pomocny i utwierdziułem sie dzieki niemu w tym co sam przed chwilą odkryłem w tej kwestii zanim go rzeczytałem, jednak dla zamkniecia calego tematu trzeba bedzie albo do tego artykulu albo nastepnego dodac kwestie, ktora opisuje ponizej. Przechodząc do sedna:

    Typy SQLowe z przestrzeni SQLTypes zachowują sie dla mnie w sposób nieco nierozumiały. Patrzac na wyniki powyzszego kodu widac jasno, ze przy POBIERANIU danych z bazy dla zmiennej liczbowej np int (SqlInt32) w przypadku NULL w bazie zmienna ageValue otrzyma też Null, zgodnie z tym co jest napisane wyzej, ze typy sqlowe są nullable.

    Jednak co w przypadku, gdy do tych zmiennych SqlDBTypes chcemy przypisac przygotowane juz odpowiednio dane, ktore maja zawierac null? Bedziemy miec bląd juz w edytorze VS.

    Przykład, mamy w bazzie pole DateTime, a na stronie pole tekstowe Data konca, ktore moze byc puste, albo zawierac datę. CHcemy przyspiac null do zmiennej ktora ma date lub jest cyfrą, to musimy dodac albo Nullable albo nazwaTypu? zebysmy pozniej mogli zrobic zmienna=null.

    Podsumowujac, gdy uzyjemy np SqlInt32 do pobrania pustego pola z bazy, bedziemy mieli zmienna ktora ma nulla, jest pusta, ale gdy chcemy przypisac nulla, to musimy dac wyraznie do zrozumienia kompilatorowi , ze mamy doczynienia z typem Nullable. Chyba ze jest jakies inne rozwiazanie jak przypisac pusta wartosc do zmiennej int lub datetime?

  2. ksmetek says:

    Moj poprzedni komentarz wcieło, czy ktos mu pomogł? W skrocie pozwole sobie powtorzyc. Typy z przestrzeni SqlTypes są wedlug moich wczorajszych spostrzezen na wlasnym projekcie, typami domyslnie Nullable tylko w 1 stronę. Jak autor pokazał w artykule, przy pobieraniu danych z bazy, gdy kolumna moze nie posiadac danych, to wartosc zmiennej typu int z powyzszego przykladu, jak widac, jest Null. Jednakze wstawiajac dane do bazy, gdy chcemy przypisac do takiej zmiennej typu SqlInt32 ageValue = null, to otrzymamy w edytorze czerwone falki oraz komunikat: „Cannot convert null to System.Data.SqlTypes.SqlInt32 because it is non-nullable value type. Dodajac ? lub Nullable pozbyawamy sie tego errora i mozemy smialo przypisac null nawet do zmiennje int, ktora potem mozemy ładnie podczepic pod parametr wywolania procedury skladowanej np. Pamietac nalezy rowniez, że typ NULL jest typem abstrakcyjnym i nie ma odzwierciedlenia w jakims nosniku. Z kolei pusty znak „” nie jest Nullem.

    • Nic nie wcieło 🙂 WordPress chwali się tym, że ma najlepszy filtr antyspamowy, co w praktyce objawia się tym, że prawie każdy komentarz musi być ręcznie zatwierdzony przez właściciela bloga.

      Przyznam, że nie sprawdzałem ale to co piszesz wygląda sensownie. Dziękuję za spostrzegawczość. Aczkolwiek typów Sql raczej nie stosuje się – przynajmniej dla własnych klas.

      • ksmetek says:

        Ok, dzieki za informacjeę. Czy mógłbys powiedzieć, jakie typy się powinno stosować w takim razie we własnych klasach, mając na uwadze pilnowanie dokładności danych, żeby nie było strat konwersji bo konwersja zwykłych typów na typy SQL nie zawsze daje takie same wyniki dla danych, ktore wymagają dużej precyzji? Czy nie będzie problemu podając zmienną int w C# do procedury składowanej która ma parametr np smallint albo tinyint? int jest 32 bity, a ja mam w bazie rozne typy wiec, wykonuje w C# parsowanie stringa na dany typ a potem podaje do procedury.

  3. Stosowanie typów Sql jest dość kłopotliwe, ze względu na to, że np. posiadam w aplikacji już obiekt typu User, który załóżmy ma UserStatus zadeklarowany jako int. W momencie, gdy zajdzie potrzeba napisania obsługi wstawienia tej kolumny do bazy danych, ciężko wymagać, żeby Ktoś pisał nowy obiekt i zmieniał typy danych na Sql-owe. Stąd trzeba wykorzystywać zwykłe typy z C#. Jeśli chodzi o straty w czasie konwersji to tutaj np. odnośnie zamiany double na T-SQL-owy decimal(.., … ) nie mogę się wypowiedzieć. Jeśli natomiast chodzi o sytuację, kiedy w bazie mamy np. typ SmallInt, a obiekt ma już pole typu Int to nie ma problemu. W momencie, gdy przy wstawianiu przekroczymy zakres podając np. 35000, to zostanie wyrzucony wyjątek z opisem: „Value was either too large or too small for an Int16.”, co oczywiście debuggerem łatwo namierzyć i w razie potrzeby dokonać poprawki.

    • ksmetek says:

      No tak racja, jak mamy napisany juz projekt w jakiejs konwencji, to czasem cięzko zmieniac specjalnie kod. Ja uważam, że wszystko zalezy od specyfikacji projektu. Ja musze u siebie pamietac zeby zrobic konwersje, a inni musza pamietac o tym ze liczby do 65,5k zmieszcza sie, a wieksze to zlapac i obsluzyc wyjatek trzeba no i trzeba wiedziec, że moze nastapic utrata danych, o ktorej to mozna przecytac w przytoczonym powyzej przez autora cytacie ze strony MSDN na samej gorze: http://msdn.microsoft.com/en-us/library/system.data.sqltypes.aspx . Po coś te typy zostały wprowadzone. Warto o nich pamietac przy tworzeniu aplikacji od nowa, szczegolnie tych, gdzie wyniki pomiarów parametrów fizycznych lub kwoty pieniezne sa zapisywane w bazie. Zobaczcie rowniez, ze konfigurujac kontrolke zrodla danych, po wybraniu procedury mamy w VS mozliwosc przetestowania jej w takim okienku i tam sa listy rozwijanej z typem danych procedury i typem danych z naszego programu. Klikamy Test Query i widzimy czy jest okej, czy nie jest okej. Mi się wydaje, że dla ulatwienia sobie zycia wiekszosc programistow poprostu używa inty. Jak napisano na stronie MSDN,, konwersja i tak jest wykonywana w tle, jesli operujemy na typach CLR .NET, wiec w perzypadku ASP.NET to i tak musi byc przez serwer przerobione i tak, wiec kwestie konwersji mozna chyba pominac. obojetnie czy mamy serwer, czy desktopa, tak bedzie robiona konwersja i tak. Jako ciekawostke przytcoze tutaj rowniez fakt, ze w SQL Server od wersji 2005 czy mlodszej baza działa z wykorzystaniem Unicode, podonie system windows wyswietla znaki w Unicode wiec nie ma sensu marnowac czasu na chary, bo nchar jest o 2 bajty dluzszy, ale za to konwersje w tle nie zostana wykoanne, jelsi beidzmey operowac na znakach unicode.

      p.s fajna buzka w dolnym lewym rogu ;]

  4. ksmetek says:

    Jak kpozbyc sie bledu Procedure or function ‚nazwa procedury’ expects parameter ‚@nazwaparametru’, which was not supplied? Blad jest w trakcie wykonywania executenonquery, gdy parametr ma wartosc null, ale ja chce podac nulla, wiec co zrobic zeby wstawic nulla do bazy? Dbnull.value tez nie działa tutaj, a pole mam nullable w bazie danych.Jakies pomysly jak to objesc?

    • ksmetek says:

      znalazłem rozwiązanie, które niestety zmusiło mnie do ingerencji w kod SQL procedury składowanej. Do parametru procedury np @iloscWakatow smallint, trzeba dodac =Null. Działa to tak, jakb domyslna wartosc w przypadku braku parametru. Nie jest to napewno rozwiązanie w pełni zadowalajace, ponieważ jeśli np ulozymy sami zapytanie SQL’owe i przypiszemy do parametru ktory podamy do SP @nazwa= null, bez zmiany definicji procedury skaldowanej (@nazwa smallint bez =null po smallint) , to polecenie wykona sie prawidlowo. Dlatego nie rozumiem, czemu zglasza mi powyzszy blad ze procedura oczekuje parametru, skoro ja mu przypisuje wartosc zmiennej ktora jest nullable.

    • cmd.Parameters.Add(…) i przekazanie DBNull nie działa ?

      • ksmetek says:

        Przed chwilą rozwiązałem problem. Był nim .SQLValue zamaist .Value.

        Przykład:
        cmd.Parameters.Add(new SqlParameter(„@nazwa”, SqlDbType.SmallInt));
        cmd.Parameters[„@nazwa”].SqlValue = DBNull.Value;

        Jeszcze przed wykonanniem gdy podano Null, pole Value dla tego parametru przyjelowartosc: „Exception: {„Dane mają wartość Null. Ta metoda lub właściwość nie może być wywołana na wartościach zerowych.”}”

        Miało to miejsce jeszcze przed wykonanniem executeNonQuery ale podczas debugowania. Zmieniłem SqlValue na Value i usunalem =NULL z kodu SQL mojej SP i działa. Jestem tak zły ze nie chce mi sie nawet sprawdzac co robi to SQLValue, ale zaraz wywale go.

        Dlaczego nie mozna do tej wlasciwosci przypisac Null tego nie wiem :/ przeciez null jest za pan brat z T-SQL em. Tak czy inaczej utwierdzam sie w swoim przekonaniu jeszcze bardziej, ze wszystko co jest we Frameworku zaimplementowane, ma jakąś różnicę.

  5. ksmetek says:

    Ok, cofam swoj hurra optymizm. Polowicznie rozwiazal sie problem. Wczesniej dla testu wstawilem na sztywno zeby przypisywal DBNull.value, ale jak usunalem to i wstawilem moją zmienną, ktora ma wartosc null, to przy Execute non query otrzymuje: Procedure or function ‚sp_INS_Oferty’ expects parameter ‚@nazwa’, which was not supplied.

    Po najechaniu na wlasciwosc Value widze teraz w zmiennej null, czyli to co chce by bylo. Zatem DBNull.value jest inaczej traktowany niz =null w kodzie C# :/ . mam do wyboru teraz albo sprawdzanie czy zminna jest =null, jesli tak to przypisz wartosc DBNull.value albo zrobic w kodzie sql @liczbaWakatow smallint =null. Czyli punkt wyjscia. Jak uzyje SqlValue to mam wyjatek juz ze nie moze byc tam null, a jak zrobie Value to trzyma ladnie nulla ale wtedy burzy sie execute non query ;]

    • ksmetek says:

      Dodam jeszcze, ze moja zmienna jest typu SqlInt16? . Jak robie w metodzie dla klikniecia przycisku dla strony page, to mam tam else {zmienna = null;}, ale zmienna = DBNull.value nie moze byc bo nie mozna skonwertowac z dbnull na sqlint16?

    • Dokładnie tak jak napisałeś powyżej – nie można w prosty sposób wstawić do bazy parametru, który ma wartość null. Do tego celu trzeba usuwać specjalnie przygotowanego typu DBNull. Zdaje się, że tak samo jest przy wyciąganiu danych z bazy, że trzeba sprawdzić najpierw czy jest DBNull i wtedy przypisać do zmiennej null, ale to mogę jeszcze sprawdzić dla pewności.

      • ksmetek says:

        Podsumowujac wszystko, u mnie bylo tak ze do wartosci parametru (cmd.parameteres(….)) mogłem przypisac DbNull.value ale przypisujac zmienną, ktora wczesniej ma przypisane =null, nie mozna bylo. NIe mozna bylo rowniez do tej zmiennej SQLInt32 ani sqlint32? ani int32? przypisac =DBNull.value. Poprostu gdy do parametru przypisac chcemy nulla, zwyklego nulla to execute non query widzi to jakby w ogole nie zostala przypisana zadna zmienna dla parametru a baza danych sie oburzała. Dodalem do parametrow w kodzie procedury sql po nazwie typu =null i procedura przyjmowala wtedy NIC i jest ok.

        W przypadku zwracania zwracania z bazy nie napotkalem problemow i albo mamy juz odpowiednio skonwertowany null albo ten null zwrocony jes tak elastyczny ze nie ma z nim problemow 😀 . W debuggerze widzialem tylko zwykly null wiec takjak pisales w artykule swoim ze typy sql sa nullable, a ja do tego dodam , ze sa nullable tylko w 1 stronę z bazy na komputer. W druga strone musimy dodac znak zapytania zeby mozna bylo przypisac nulla do zmienej np SQLInt16, czyli SQLInt32 bedzie. Jak dla mnie nie musisz sprawdzac.

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Wyloguj / Zmień )

Zdjęcie na Facebooku

Komentujesz korzystając z konta Facebook. Wyloguj / Zmień )

Zdjęcie na Google+

Komentujesz korzystając z konta Google+. Wyloguj / Zmień )

Connecting to %s

%d blogerów lubi to: