sobota, 22 listopada 2014

SQL: Zapytanie działa szybko, ale w procedurze nie.... o co chodzi?

Problem wydajności zapytań SQL bardzo często można rozwiązać już na etapie ich samej "wewnętrznej" optymalizacji (bez myślenia o indeksach i innych sztuczkach).

Czasami jednak, podczas optymalizacji przytrafiają się bardzo "dziwne" przypadki...


Procedura SQL = "drogie opakowanie" ?

Niedawno przytrafił mi się interesujący przypadek.Otrzymałem zgłoszenie, iż jedna funkcja systemu działa koszmarnie wolno.

Okazało się, że istotnie, po wychwyceniu odpowiedniego wywołania procedury w SQL Server Profiler ta wykonywała się przez dobre 10 sekund, zanim zwróciła wyniki, w chwili kiedy powinna zajmować nie więcej niż "chwilę" (poniżej sekundy).

dla uproszenia:
alter procedure abc
@a int,
@b int,
@c nvarchar(100)
as
begin
  select
     *
  from table_a ta
  join table_b tb on  ta.id=tb.ref_ta
  where 
  (
    ta.a=@a
    and tb.b = @b 
  )
    or tb.c=@c

end


Pierwszym krokiem, jaki wykonuję w celu optymalizacji jest "wyjęcie" ciała procedury, wrzucenie do nowego zapytania łącznie z parametrami i szukanie krok po kroku, gdzie jest "hamulec" czyli

alter procedure abc
@a int,
@b int,
@c nvarchar(100)
as
begin
...
end

zastąpione


declare @a int=10
declare @b int = 20
declare @c nvarchar(100)='asdf'


I tutaj niespodzianka:po "wyjęciu" procedury do zapytania śmiga aż miło! Pierwsza myśl (po wyjściu z szoku): bardzo drogie "opakowanie" :) A przecież nie wypada wywoływać samego "zapytania" z poziomu aplikacji bo... nie. I tyle.

Co robić? Jak żyć?


Na szczęście (pomijając już fakt tegoż dziwnego przypadku) jest na to lekarstwo:
Stworzenie zmiennych "lokalnych" (wewnątrz procedury), które przyjmą wartości PARAMETRÓW, następnie wewnątrz wykorzystywane będą nasze nowe zmienne :)

 czyli:

alter procedure abc
@a int,
@b int,
@c nvarchar(100)
as
begin
  declare @la int=@a
  declare @lb int=@b
  declare @lc nvarchar(100) = @c
  select
     *
  from table_a ta
  join table_b tb on  ta.id=tb.ref_ta
  where 
  (
    ta.a=@la
    and tb.b = @lb 
  )
    or tb.c=@lc

end

VOILA :) 


piątek, 31 października 2014

#Off: O programowaniu


Programowanie jest sztuką

Programowanie jest dzisiaj dla mnie sztuką. Nie chodzi o samo napisanie działającego programu. Nie jest ważne, z ilu linii kodu się składa. Nie ważne, jak wyrafinowane, mega zaawansowane metody zaimplementujemy. Ważne, aby kod był później czytelny, zoptymalizowany i przede wszystkim - zrozumiały, nie tylko dla nas.
Dobry program, to taki do którego chcemy wrócić. Ale nie po to żeby coś poprawić, lecz żeby jakąś jego część wykorzystać, bo po prostu działa doskonale. A jeśli coś działa doskonale, to wypadałoby wiedzieć nie tylko "CZY", ale "JAK", prawda?

Jeden program, wiele rozwiązań

Tworząc program, zawsze istnieje wiele rozwiązań.
Weźmy pod uwagę choćby program wykonujący prostą operację - mnożenie.
Pomijając kwestie technologiczne (aplikacja okienkowa, konsolowa, WS, czy np. DLL'ka), możemy zaszyć gotową metodę przyjmującą dwa parametry lub po prostu wykonać po prostu samo mnożenie.
Jeśli upakujemy kod w metodzie, możemy po prostu zwrócić np:

return a*b

Możemy również pokusić się o metodę, która wykona operację mnożenia iteracyjnie, dodając n-krotnie wartość pierwotną a do wyniku, z obsługą mnożenia przez zero (zwracając po prostu 0).

Powinniśmy również rozważyć, jakie wartości chcemy mnożyć  - liczby całkowite, z przecinkiem, czy uwzględniać wartości ujemne itd.

A może, jak już upakujemy nasz kod w metodzie, zamiast przekazywania na sztywno dwóch argumentów, dodać tablicę, dzięki czemu w jednej metodzie wykonamy mnożenie n-liczb? A może warto przeciążyć metodę i stworzyć jej różne warianty? :)

Programowanie daje nam dowolność w realizacji pewnych rozwiązań. To od nas zależy, w jaki sposób zrealizujemy kod, oraz czy istotnie spełni on nasze oczekiwania.

Liczy się wnętrze...

Tworząc programy bardzo często kompilujemy je, jak działa wszystko ok, zapominamy o "wnętrzu". Nie zapominajmy jednak, że kiedyś być może będziemy chcieli wrócić do kodu, aby coś poprawić, udoskonalić, a być może nasz program realizował już coś czego potrzebujemy w nowym projekcie i warto wykorzystać stosowny fragment kodu lub stworzony komponent.


Dlatego podczas tworzenia kodu wyobraźmy sobie, że minęły 3 lata, zapomnieliśmy jak działa program "od środka" i odpowiedzmy sobie na pytania:
  • Czy kod jest wystarczająco czytelny? Czy za jakiś czas będę wiedział, do czego służy zmienna o nazwie xxx1?
  • Czy nie powinienem zastanowić się nad nazwami niektórych funkcji? Może warto je upakować w pewną klasę? Raczej nie wszystkie metody powinny być "public", niektóre mogą być dodatkowo do wykorzystania bez deklarowania, może warto przekształcić je na statyczne? Do czego służy metoda o nazwie Licz(int a, int b)? dzisiaj wiem, że sprawdza czy a jest podzielne przez b, kod również na to wskazuje - ale może jednak warto zmienić nazwę funkcji na bardziej rozsądną?
  • Może warto dopisać kilka stosownych komentarzy, abym wiedział, co się dzieje w danym momencie?
  • A może dobrym pomysłem będzie odseparowanie części naszego "programiku" do osobnej DLL'ki? W końcu tę metodę Foo(int bar) wykorzystuję w co trzecim programie; jeśli okaże się że był jakiś błąd, wystarczy że podmienię wtedy DLL'kę w programach, które z niej korzystają.
Nie ważne jak ładny interfejs użytkownika utworzymy, jakimi "wodotryskami" zachwyci nas czy potencjalnego odbiorcę.
Poza działającym programem (co jest oczywiście najwyższym priorytetem), najważniejsze jest to czego nie widać, czyli kod.

Przykłady z życia:
  1. Jeśli samochód się zepsuje,  mechanik wykonuje rozpoznanie - problem (samochód nie odpala) poznaje przyczynę (np. uszkodzona świeca), odnajduje rozwiązanie (wymiana świecy). Dlaczego wymiana świecy zajmie mu technicznie ujmując kilka minut? Ponieważ wie, gdzie szukać, jest zachowany pewien standard, świece nie są "klejone", wystarczy wymienić ten element, bez potrzeby rozbierania całego silnika.
Pamiętajmy też, że program jest jak kobieta:

Umawiasz się z dziewczyną na randkę, idziecie do restauracji. wszystko ładnie, pięknie, cudowne atrybuty, nasza partnerka wykazuje dużo interakcji, no i przechodzicie do konkretów... rozbierasz partnerkę żeby ją lepiej poznać, a tu.... No, niekoniecznie to czego się spodziewaliśmy :)

Mniej znaczy więcej. Więcej znaczy mniej

Głównym problemem wielu programistów jest tworzenie bardzo złożonego kodu, bez późniejszego upakowania fragmentów w możliwe do późniejszego wykorzystania metody, czy też bez jakiejkolwiek optymalizacji.
Konsekwencją tego są nigdy niezadeklarowane zmienne (o tym na szczęście poinformują już nas środowiska programistyczne), niepotrzebne fragmenty kodu, które jedynie zwiększają złożoność obliczeniową, oczywiście nieczytelność kodu.
Oczywiście nie zawsze muszą wystąpić powyższe konsekwencje. Często możemy dysponować ładnym, zwięzłym kodem. Tylko czemu taki fragment kodu upakować np bezpośrednio pod "button1_click(sender,e)"? Jeśli gdzieś pojawi się analogiczna funkcjonalność, oczywiście - możemy po drodze w kodzie "kliknąć" przycisk - ale czy to jest ładne rozwiązanie?

Czyż nie lepszym rozwiązaniem byłoby stworzenie odrębnej metody o bardziej logicznej nazwie niż nazwa akcji wybranej kontrolki?

Może jednak założyć, że funkcjonalność pojawi się nie tylko w wybranej klasie (kontrolce) i przenieść jej funkcjonalność do pewnej klasy?

Mniej znaczy więcej --> Mniej linii kodu = większy porządek i z reguły wydajność
Więcej znaczy mniej --> Więcej zachodu na początku =  mniej późniejszych modyfikacji i poprawek.

NAZWY, Names, nazwy1...

Jednym z grzechów programistów jest nazywanie "jak popadnie". Typowe błędy:
  • Brak stosowania standardu wielkości liter (np nazwy rozpoczynające od dużej litery to klasy)
  • Nic nie mówiące nazwy zmiennych, czy klas (np Class1)
  • Wielojęzyczność :) czasami opisujemy ładnie, po angielsku; w innych przypadkach pojawia się z reguły język ojczysty
  • Wiele klas o takiej samej nazwie, a różnej funkcjonalności; np klasa maintain.User i application.User, do tego Forms.User. Pomimo stosowania namespace'ów, myślę że warto zastosować inne nazwy klas - w końcu pełnią one zupełnie inną funkcję.

Poza bardzo kreatywnym podejściem do nazywania różnych elementów, np "zapożyczając" fragment kodu np. z "gotowców", zapominamy o detalu pt "źródło" czy krótka informacja o licencji (choćby GPL).
Oczywiście w kontekście działania naszego programu nie ma to większego znaczenia. Niemniej jednak, czy tworząc kod, który udostępniamy w sieci, chcielibyśmy aby ktoś przypisywał sobie wszelkie zasługi za działający program? Niezależnie od podejścia do własności intelektualnej - nie sądzę.


Podsumowanie

Podczas tworzenia programów dobrze jest pamiętać o wielu "dobrych zasadach". Oczywiście samo programowanie jest procesem, w którym my jako programiści mamy wiele do powiedzenia, wdrożenia, usprawnienia. Twórzmy oprogramowanie, z którego istotnie będziemy dumni. Zastanówmy się nad optymalnym rozwiązaniem. Już podczas tworzenia wersji 1.0 myślmy o tym, że kiedyś powstanie wersja 4.0 i kolejne, więc warto od razu uczytelnić i zoptymalizować nasz kod... nawet jeśli do takich zmian nie dojdzie.

X3

wtorek, 9 września 2014

Dobre praktyki SQL: Stosowanie aliasów w zapytaniach i przedrostków w tabelach

Jedną z dobrych praktyk tworzenia zapytań w SQL jest stosowanie aliasów podczas pobierania danych z bazy.

Alias jest alternatywną nazwą, której możemy używać do określenia danego obiektu.

Zaczniemy jednak od zbudowania naszej prostej bazy w oparciu o stosowanie przedrostków:

Stosowanie przedrostków w nazwach tabel

Wyobraźmy sobie, że posiadamy  prostą bazę złożoną z trzech tabel, która posiada informację na temat projektu, oraz osób realizujących.
Zastosujemy przedrostki, które umożliwią nam błyskawiczne określenie, jakie dane przechowuje baza:
  • TBL_ - tabele z danymi
  • DIC_ - słowniki


TBL_PROJECT
PKProject_Idint
ProjectNamenvarchar(100)

zawierającą:

Project_IdProjectName
1Package Control
2Divide by zero system
3Fridge Automation

tabeli z uczestnikami projektu

TBL_PROJECTCREATOR
PKEmployee_Idint
FKProject_Idint
Namenvarchar(100)
Surnamenvarchar(100)
FKRole_Idint

oraz słownika ról:
DIC_ROLE
PKRole_Idint
RoleNamenvarchar(100)


Role_IdRoleName
1Project Manager
2Team Leader
3Tester

Dzięki takiej strukturze szybko możemy zidentyfikować, czy dane zawarte w tabeli są słownikiem, czy też innymi, czy bardziej "produkcyjnym" zbiorem (w naszym przypadku określającym kto jest przypisany do jakiego projektu, oraz jaką pełni funkcję).

Możemy rozszerzać listę przedrostków zgodnie z naszymi oczekiwaniami (np określając poszczególne podsystemy/moduły), a późniejsze przestrzeganie standardów pomoże w późniejszym odnalezieniu interesujących nas rekordów.

Aliasy

Chcielibyśmy pobrać ogólne informacje na temat projektu, tj:
1. nazwa projektu
2. imię i nazwisko project managera
3. imię i nazwisko testera

SELECT
   P.ProjectName as [Project Name],
   PM.Name + ' ' + PM.Surname as [Project Manager],
   Tester.Name +  ' ' + Tester.Surname as [Tester]
From TBL_PROJECT P
   LEFT JOIN TBL_ProjectCreator PM on P.Project_Id = PM.Project_Id and PM.Role_Id = 1
   LEFT JOIN TBL_ProjectCreator Tester on P.Project_Id = Tester.Project_Id and Tester.Role_Id = 3


Zrozumiałym jest, że w takiej sytuacji MUSIELIŚMY skorzystać z aliasów, ponieważ dwukrotnie odnosimy się do tabeli TBL_ProjectCreator i SQL nie wiedziałby, o co nam dokładnie chodzi, do którego joina się odnosimy itd.

Jednak uprościmy formę bez wykorzystania aliasów do pobrania samych danych na temat projektu i osoby testującej:

SELECT
   ProjectName as [Project Name], 
   Name + Surname as [Tester]
FROM TBL_PROJECT
   LEFT JOIN TBL_ProjectCreator on TBL_Project.Project_Id = TBL_ProjectCreator.Project_Id and TBL_ProjectCreator.Role_Id = 3


Oczywiście kod zadziała, jednak zmniejsza się czytelność zapytania. Ponadto jeśli pojawi się konflikt nazw z różnych tabel (przykładowo "ID" stosowane we wszystkich tabelach z taką samą nazwą, co jest jak najbardziej dozwolone - ale odradzam :) ), musimy powiedzieć kochanemu SQL'owi, z której dokładnie tabeli chcemy teraz wyświetlić rekordy. Bez aliasów - operujemy na pełnych nazwach.

Ograniczenia i "dobre praktyki"

Jest kilka podstawowych ograniczeń związanych z aliasami, oraz dobrych 
  • Nie możemy użyć takiego samego aliasu do różnych tabel (wówczas ich stosowanie nie miałoby najmniejszego sensu)
  • Powinniśmy stosować raczej proste aliasy, np User. , Tester. , PM. , Contact. , etc. 
  • powinniśmy unikać stosowania nic nie znaczących aliasów typu X. , Y. , U1. , U2. 

Podsumowanie

Zastosowanie aliasów przy zapytaniach przynosi korzyści w postaci:
  1. Ograniczenia długości zapytania (oczywiście nie zawsze - np jeśli zastosujemy bardziej wykwintne aliasy), w tym skrócenia zapytań również w przypadku stosowania wielu schem (innych niż domyślnego  dbo. )
  2. Poprawy czytelności zapytań
  3. przy pewnym poziomie generalizacji danych (jak w przykładzie z uczestnikami projektu, którzy mają przypisane role; nie są  "rozdzieleni" na różne tabele) - bezpośrednią identyfikację podmiotów (jak w przykładzie - aliasy PM oraz TESTER)  

Zachęcam również do stosowania przedrostków przy nazewnictwu tabel, niezależnie od tego czy składa się z pięciu, czy kilku tysięcy tabel. Zachowanie porządku w danych jest niezwykle ważne, nie tylko na etapie jej projektowania, ale również podczas obsługi i "prac codziennych".


środa, 20 sierpnia 2014

Nowe: ściągi, T-SQL

Moi drodzy,

Postanowiłem utworzyć nowy cykl zawierający serię "skrótów tematycznych" dla wybranych platform, zawierających podręczne "ściągawki".
Seria będzie dostępna na osobnych podstronach bloga, a w zawartości będzie można znaleźć krótki wstęp do wybranej technologii oraz wybrane, najczęściej stosowane funkcje wraz ze składnią oraz przykładami ich użycia

Cykl będzie regularnie rozbudowywany. Jeśli macie jakieś propozycje dodania nowych elementów - piszcie w komentarzach.

Na pierwszy ogień: T-SQL .Zapraszam - zawsze i wszędzie!

wtorek, 19 sierpnia 2014

T-SQL: Tabele tymczasowe, zmienne typu TABLE, różnice

Język T-SQL umożliwia tworzenie tabel tymczasowych oraz zmiennych typu TABLE.

Tabele tymczasowe dzielimy na:

#lokalne - do których dostęp posiadamy w ramach otwartej sesji

##globalne - dostępne w ramach SERWERA (czyli będzie ona dostępna również w ramach innej bazy).

@zmienne typu TABLE - dostępne od chwili wywołania do zakończenia działania skryptu

Pierwsza różnica pomiędzy tabelami tymczasowymi a zmiennymi: tabele tymczasowe TWORZYMY, a zmienne DEKLARUJEMY.

Składnia tworzenia tabel tymczasowych:

CREATE TABLE #NAZWA_TABELI
(
kolumna1   typ,
kolumna2   typ,
...
kolumnaN, typ )

warto też pamiętać, że możemy od razu przekazać dane z zapytania do tabeli tymczasowej, bez konieczności podawania jej definicji, dodając przed FROM magiczne "INTO #TABELA", np

CREATE TABLE #TEMP 
(
NAME NVARCHAR(100) 
)
SELECT
name, surname, age

FROM PERSON

możemy zastąpić

SELECT
name, surname, age
INTO #TEMP
FROM PERSON


Wówczas tabela tymczasowa utworzy się sama, a jej kolumny będą odpowiadały nazwom kolumn zwracanych przez kwerendę. Należy wówczas pamiętać o dwóch podstawowych zasadach:
  1. Wszystkie zwracane kolumny w kwerendzie muszą posiadać nazwy
  2. Nazwy kolumn muszą być unikatowe (nie mogą się powtórzyć)



W przypadku deklarowania zmiennej typu TABLE:

DECLARE @NAZWA_TABELI TABLE
(
kolumna1   typ,
kolumna2   typ,
...
kolumnaN, typ
)

Zastosowanie

Zapewne niejednokrotnie pojawiała się potrzeba tworzenia tabel "na chwilę". Możemy oczywiście tworzyć pewne tabele, nazwać je "tymczasowe" i wierzyć, że kiedyś je usuniemy :) Niemniej jednak takie rozwiązanie jest mało eleganckie, niejednokrotnie później pozostają dziwne byty, których boimy się usunąć, a które w rzeczywistości utworzyliśmy na 5 minut...

Tabele tymczasowe wprost świetnie nadają się do importów i migracji danych pomiędzy środowiskami oraz analizy danych, gdzie operujemy już na jednym, stałym, wstępnie ograniczonym "wiaderku danych", do których możemy co jakiś czas później się ponownie odwołać.


Zmienne natomiast mają dość krótki żywot (do zakończenia działania skryptu) :) Niemniej jednak są w teorii bardziej wydajne, ponieważ w przypadku SQL SERVER przechowywane są w pamięci operacyjnej do chwili zakończenia działania skryptu. Jeśli zaraz po wykonaniu zapytania dane nie będą nam już potrzebne, zmienna może okazać się niezłym pomysłem. Są one szczególnie fajne przy mniejszych zbiorach, np. gdy nie chcemy operować korzystać z kursorów do poruszania się po danych (a kysz!) - wówczas odpowiednia pętla po naszym "mikro zbiorze" i po krzyku.



Który rodzaj wybrać?

Zmienne typu TABLE powinniśmy stosować, gdy przekazujemy tablicę jako parametr do procedury czy funkcji. Ponadto zmienna "znika" po wykonaniu skryptu.

Przy tabelach tymczasowych powinniśmy dodatkowo pamiętać o konieczności ich usunięcia, gdy nie są już potrzebne.

Jeśli chcielibyśmy posiadać dostęp do naszej tabeli tymczasowej globalnie (np. migracja danych, głębsze analizy itd), oczywistym jest, że powinniśmy stosować ##, jednakże tylko w sytuacji, kiedy faktycznie będziemy korzystali z tabeli tymczasowej później.

Warto też pamiętać, że tabele tymczasowe możemy wykorzystać do złączeń (JOIN). Możemy dzięki temu łączyć już przefiltrowany zbiór pewnych danych z tabeli/tabel (np słowników) w formie #tabeli_tymczasowej, z głównym źródłem - praktyczne przy dużych słownikach, z których interesuje nas niewielki ich fragment.

Wydajność

Pod względem wydajności, nie istnieje uniwersalne uzasadnienie, kiedy powinniśmy korzystać z tabel tymczasowych # / ##, a kiedy ze zmiennych. W google można odnaleźć masę rankingów na temat szybkości działania.
Zmienne przechowywane są w pamięci (chyba że przekroczą określony rozmiar - wówczas trafią do tempdb), więc w teorii powinny być szybsze.
Nie przeprowadzałem wystarczającej ilości prób, aby jednoznacznie powiedzieć, kiedy stosować @, a kiedy #.

Osobiście uważam, że optymalnym rozwiązaniem będzie przetestowanie "na żywo" i po prostu - wybranie szybszego rozwiązania per-case. Osobiście wielokrotnie byłem zaskoczony rezultatem działania. Od razu przypomina mi się cudowny cytat, który kiedyś gdzieś usłyszałem:

W teorii, teoria i praktyka to to samo. 
W praktyce, tak nie jest.

poniedziałek, 18 sierpnia 2014

#Quick: zabawa kursorem (myszki), oraz o rozdzielczości ekranu

info

Poniższy artykuł powstał wyłącznie do celów edukacyjnych, a autor (czyli ja) nie ponosi odpowiedzialności za wykorzystanie informacji zawartych w poniższym tekście niezgodnie z prawem lub etyką zawodową.


Położenie kursora


Aby odczytać lub zmienić położenie kursora, wystarczy odwołać się do Cursor.Position :
//odczytanie położenia
int px = Cursor.Position.X;
int py = Cursor.Position.Y;


//zmiana położenia, trololo :)
Cursor.Position = new Point(100, 100);


Ograniczamy wolność kursora ;)

Ciekawym atrybutem przy Cursor jest Clip.
Umożliwia on ustalenie granic, w których możemy operować naszym kursorem. W Windows taką granicą domyślnie jest ustalona rozdzielczość. Spróbujmy ograniczyć możliwość poruszania się naszego kursora myszki wyłącznie do obszaru naszego okna (uwzględniając, aby nie możliwe było zamknięcie/zmiana rozmiaru naszego okna), jak na poniższym zrzucie:



Oczywiście możemy zmienić atrybut formatki form.FormBorderStyle na odpowiedni, ale wówczas tracimy na wyglądzie okienka i nie ma zabawy, rozwiązanie stałoby się mniej uniwersalne.

przepis:

 //pobieramy szybko wymiar naszego okna:
            Rectangle currentwindow = this.Bounds;

            //zmniejszamy obszar do zabawy oknie:
            //35 - przybliżony rozmiar górnego paska

            currentwindow.Height = this.Height - 35;

            //10: aby uniemożliwić zmianę rozmiaru okna
            currentwindow.Width = this.Width - 10;

            /* Ustawiamy położenie początkowe
             * (przesunięte o kilka pikseli,
             * aby nie można było zmieniać rozmiaru
             * z lewej/góry */

            currentwindow.Location = new Point(currentwindow.Location.X+5, currentwindow.Location.Y + 30);

            //podpinamy punkt początkowy granic i rozmiar
            Cursor.Clip = new Rectangle(currentwindow.Location, currentwindow.Size);



możemy pobrać sobie gotowe granice np do rozdzielczości ekranu głównego:

Rectangle resolution = Screen.PrimaryScreen.Bounds;

..lub wskazując na dowolny, poruszając się po kolekcji AllScreens:
Rectangle r2 = Screen.AllScreens[1].Bounds;

Zastosowanie

Zmianę/odczyt położenia możemy wykorzystać m.in. przy:
  • podpowiedzi użytkownikowi, gdzie w danym momencie powinien kliknąć użytkownik
  • tworzeniu aplikacji do zdalnego sterowania komputerem (a'la TeamViewer)
  • grach / programach graficznych
  • programach egzaminacyjnych
  • aplikacjach dla osób niepełnosprawnych, ułatwiających pracę
  • POS 
  • ...z okazji 1 kwietnia :)

Ograniczenia

Granice kursora zostaną "zresetowane" m.in. po:
  • zamknięciu programu
  • zmianie okna wiodącego
  • zmianie podświetlenia okna (np przycisk start, alt+tab, etc)
  • zmianie rozmiaru/położenia okna
Oczywiście można sobie z tym poradzić przy użyciu  zdarzeń, timera, backgroundworkera, etc.

środa, 6 sierpnia 2014

DataView - ciekawa zabawka

Tworząc aplikacje bazodanowe (pomijam wszelkiego rodzaju "ulepszacze") praktycznie zawsze pobieramy dane, które wcześniej czy później wyświetlimy w tabelce (np dataGridView).

Dane możemy pobrać wygodnie np za pomocą SQLDataAdaptera do DataTable lub DataSet, a następnie przerzucić na nasz dataGrid.

Jeśli chcemy filtrować zbiór, możemy zrealizować to na wiele sposobów, m.in.:
  1. Za każdym razem pobierać "świeże", przefiltrowane dane z bazy - tylko wtedy dodatkowo obciążamy system, a w chwili kiedy pobieramy dane z dość statecznego zbioru (np słownika), nie zawsze będzie to "eleganckie" rozwiązanie.
  2. Filtrować dane w naszej kolekcji (np DataTable, Combobox, DataGridView, etc) - wszystko fajnie, ale musimy to ręcznie oprogramować, co bywa męczące, a w dodatku możemy być gorsi z algorytmów, ale dobrzy z SQL'a... co wtedy?

Jedno i drugie rozwiązanie jest jak najbardziej akceptowalne (zależnie oczywiście od sytuacji). Natomiast mimo wszystko, fajnym wynalazkiem jest DataView.

System.Data.DataView

DataView osobiście nazywam "nakładką" na zbiór danych (sam w sobie nie przechowuje danych). Umożliwia m.in. sortowanie, edycję wyszukiwanie/filtrowanie, etc (więcej informacji pod tym linkiem ) W artykule opowiem trochę o filtrowaniu (oczywiście na przykładzie) :)

Zastosowanie:
  1. Pobranie kompletu danych (czasami działa szybciej niż przy przefiltrowanych zbiorach - o tym również wspomnę :) )
  2. upakowanie danych naszym DataView (amulet szczęścia :) )
  3. Filtrowanie danych po stronie klienta (Serwer odpoczywa)

Koniec teorii, czas na praktykę.

załóżmy, że mamy prostą tabelkę

TBLServers
ID
ServerName
OperatingSystem
IP_Address
Location_City

Do pobrania danych (wszystkich) stworzyliśmy sobie procedurę SQL:

getServerList

w aplikacji pobraliśmy sobie dane i "wrzuciliśmy" do datagrida:

private void GetServers()
{
 SqlConnection con = new SqlConnection(/*nasz ConnectionString*/);
 SqlDataAdapter da = new System.Data.SqlClient.SqlDataAdapter("exec getServerList", con);
 DataTable dt = new DataTable();

 //wypełniamy DataTable danymi za pomocą metody Fill:
 da.Fill(dt);

 //wypełniamy (wskazujemy źródło) naszym dataTable
 dataGridView1.DataSource=dt;

}


Działa, pobraliśmy dane. Ale chcielibyśmy móc filtrować wyniki po miejscowości i obsłużmy zdarzenie
Dodajemy Textbox, po drodze dorzucamy DataView i metodę do obsługi filtrowania i obsługujemy zdarzenie na TextChanged...


DataView dataViewServers;
private void GetServers()
{
 SqlConnection con = new SqlConnection(/*nasz ConnectionString*/);
 SqlDataAdapter da = new System.Data.SqlClient.SqlDataAdapter("exec getServerList", con);

 DataTable dt = new DataTable();
 da.Fill(dt);

 //upakujmy nasz zbiór danych DataView...
 dataViewServers = new DataView(dt);
             
 //...i podłączmy DataView zamiast DataTable dataGridView1.DataSource=dataViewServers;
 } 
 //utwórzmy dla porządku metodę odpowiedzialną za filtrowanie
 private void FilterServers()
 {
  //filtrowanie jak w SQL...
  dataViewServers.RowFilter = "Location_City like '"+textBoxCity.Text+"%'";

 }
 //obsłużymy zdarzenie naszego textboxa, aby filtrowało "na żywo": 
 private void textBoxCity.TextChanged(object sender, EventArgs e)
 {

  filterServers();
 }

Rezultat: mamy bardzo fajną, szybką i dynamiczną wyszukiwarkę. przy czym samo wyszukiwanie odbywa się po stronie aplikacji - operując na pobranej już kolekcji. Zawsze możemy dodać kolejne warunki do filtrowania danych.


Podsumowanie

 DataView jest bardzo wygodnym dodatkiem, umożliwiającym szybkie filtrowanie danych po stronie klienta, na pobranym już zbiorze danych. Nie musimy ręcznie filtrować danych, czy też pobierać na nowo zbioru z serwera, co w mniejszym lub większym stopniu wpływa na wydajność pracy.W dodatku DataView działa zaskakująco szybko, a sama metoda filtrowania jest po prostu - wygodna.

poniedziałek, 4 sierpnia 2014

Quick: #naskróty Wyszukiwanie tabel wg nazw kolumn

Każdy, kto rozpoczyna pracę z nieznaną bazą danych, często czuje się jak poszukiwacz karteczek w lesie (gdzie Slenderem jest szef oczekujący szybkich rezultatów "na wczoraj" :) ).

Załóżmy, że nasz szef chciałby abyśmy ("migusiem") odnaleźli informację na temat klienta, a przed nos dostaliśmy jego adres e-mail... i do roboty. Ok, tylko że to jest nasz pierwszy dzień w pracy, a baza liczy kilka tysięcy tabel....

Na podstawie zadania wiemy, że powinniśmy znaleźć tabelę, która posiada kolumnę z adresem e-mail. Ale przecież SQL Management Studio sam w sobie umożliwia wyszukiwanie TABEL, a instalowanie czegokolwiek na komputerze jest monitorowane, poza tym i tak nie posiadamy uprawnień...


...Ale  posiadamy fajny skrypt, który przeszuka strukturę w tabelach systemowych naszej bazy:

SELECT
    sysobjects.name as [tabela],
    syscolumns.name as [nazwa kolumny],
    systypes.name as [typ]
    FROM sysobjects
    JOIN syscolumns ON sysobjects.id = syscolumns.id
    JOIN systypes ON syscolumns.xtype=systypes.xtype
   WHERE sysobjects.xtype='U'
and syscolumns.name like '%mail%'
ORDER BY sysobjects.name,syscolumns.colid


Czemu '%mail%'

Ponieważ powinniśmy założyć, że element którego szukamy może nazywać się:
Email
E_mail
contactemail
ContactEMail
EmailAddress
etc...
A czasami po prostu "mail".Różne szkoły.


Gotowiec wyszukuje tabele, które zawierają kolumnę o wybranej nazwie.

Później wystarczy już sprawdzić w bardziej sensownych tabelach, czy znajdujemy to czego szukamy i w optymalnym (z reguły) przypadku, przesłać maila z wynikami. Oczywiście wszystko zależy od struktury bazy; jeśli np parametry rozwijane są "w dół" (wierszami), albo przechowywany jest XML, wynalazek nie zadziała (chyba że poszukamy po XML, Param, Value, etc). 

...Nie ma za co... :]

X3

czwartek, 31 lipca 2014

Quick: #CodeCleanup: alternatywa dla if_else, czyli ?:

Zapewne często zdarza Wam się tworzyć zmienne i przypisywać im pewne, uwarunkowane "od czegoś" wartości. Czasami są to skomplikowane uwarunkowania, jednak bardzo często warunkowanie jest proste: "jeśli coś, to X, w przeciwnym wypadku Y". Ale do rzeczy:

Jak to działa?

Znając warunkowanie if...else wiemy, że pojawia się warunek sprawdzający. Jeśli warunek zostanie spełniony, wykonywana jest część zaraz pod warunkiem.
Opcjonalnie możemy skorzystać z:
  • else if (jeśli pierwszy warunek nie został spełniony, sprawdzamy inny warunek)
  • else ("w przeciwnym wypadku, jeśli pozostałe warunki nie zostały spełnione").
C# umożliwia zapisanie naszego warunku w prostrzej formie, z wykorzystaniem ? i :

[warunek] ? [jeśli prawda] : [jeśli fałsz]

Kto używa funkcji w popularnym arkuszu kalkulacyjnym, od razu dopatrzy się podobieństwa funkcji "Jeżeli" (If) w tymże programie :)

Oczywiście możemy stworzyć bardziej złożone warunkowanie,np

[warunek] && [warunek2] || [warunek3] ? [jeśli prawda] : [jeśli fałsz]

Możemy też zagnieździć warunki:

[warunek1] ? [warunek2] ? [jeśli spełniony warunek1 i warunek2] : [jeśli spełniony pierwszy warunek] : [jeśli fałsz]

Przykład


Załóżmy, że chcemy sprawdzić, czy jakaś liczba jest podzielna przez 2, a jeśli tak, to zapiszemy sobie do naszej zmiennej odpowiednią wartość:

              int liczba = 23;


1:          bool czyPodzielnaPrzezZero;
2:            if (liczba % 2 == 0)
3:            {
4:                czyPodzielnaPrzezZero = true;
5:            }
6:            else
7:            {
8:                czyPodzielnaPrzezZero = false;
9:            }


Ilość linii do weryfikacji: 9 (lub 7, zależnie od przyjętego standardu). sporo.

oszczędzania czas rozpocząć:

            int liczba = 23;
1:            bool czyPodzielnaPrzezZero = true;
2:            if (liczba % 2 == 0)
3:            {
4:                czyPodzielnaPrzezZero = true;
5:            }


Wynik: 5(4) linii. Nieźle. Ale co jeśli podobnych prostych warunków byłoby więcej? Spoko :) Oszczędzimy jeszcze trochę...  wykorzystamy cudowny ? oraz :


      int liczba = 23;
1:      bool czyPodzielnaPrzezZero = liczba%2==0 ? true : false;


W rezultacie, naszą prostą weryfikację wykonaliśmy w jednej linii, a w dodatku od razu przypisaliśmy do naszej zmiennej wartość (o korzyściach płynących z przypisania od razu wartości do zmiennej napiszę w jednym z kolejnych postów).


Kiedy stosować, kiedy unikać?

Wspomniany "skrót" możemy śmiało wykorzystać przy prostych warunkach, zdecydowanie wpłynie na poprawę czytelności kodu. Przy odrobinie wprawy możliwe będzie znaczne skrócenie tworzonego kodu.

Nie polecam natomiast stosowania bardziej zagnieżdżonego warunkowania. 
Co prawda nic nie stoi na przeszkodzie, abyśmy sprawdzali kilka warunków w jednej linijce, np w Norwegii mocne trunki (wódka, wino) można nabyć dopiero po ukończeniu 21 lat:

            int wiek = 21;
            string alkohol = wiek<18? "Nie sprzedajemy" : wiek<21? "Co najwyżej piwo" : "A pij Pan co chcesz, starość nie radość";



...tylko pytanie, czy taki kod będzie czytelny. Osobiście - odradzam.

Ponadto, jeśli zastosowaliśmy już switch...case, to raczej dobrze zrobiliśmy i nie warto przesiadać się na ? : .

środa, 23 lipca 2014

O stateczności słów kilka, czyli static (zmienne, metody, klasy)

Static - o co chodzi?

Programowanie obiektowe przyniosło pojęcie klas i obiektów. W odróżnieniu od programowania strukturalnego (np C) nie musimy już tworzyć często powielających się zmiennych, tylko wystarczy stworzyć instancję danej klasy (zwaną dalej obiektem).

Czasami jednak możemy odnieść wrażenie, że tworzymy nadmiarowo pewne obiekty tylko po to, żeby raz ich użyć.

Definicja

 Klasa statyczna jest swojego rodzaju odpowiednikiem obiektu - określamy pewną stałą strukturę metod lub zmiennych (stąd nazwa), które dostępne są globalnie, bez konieczności tworzenia obiektów danej klasy.

Zamiast tworzyć nowy obiekt, wystarczy wówczas odwołać się do klasy jak do obiektu,

np
public static class Test
    {

        public static int n = 50;
        public static void Sprawdz()
        {
            //rób coś
        }
    }


Później, w obrębie naszej przestrzeni nazw (namespace) metodę możemy wywołać w następujący sposób (bez konieczności deklarowania obiektu):

...
            Test.Sprawdz();
...


Widoczną zmienną również możemy modyfikować w dokładnie ten sam sposób:

...
            Test.n = 100;
...


Co więcej, jeśli zmienimy wartość zmiennej, zmiana będzie widoczna w obrębie działania naszego programu globalnie.

Przykład - klasa nie statyczna

Załóżmy, że tworzymy prostą aplikację obliczającą pole i obwód figur geometrycznych na potrzeby szkoły podstawowej. Nasz kod może wyglądać mniej więcej tak:

namespace matematykaDlaNajmlodszych
{
    class Program
    {
        public class Mojeliczenie
        {
           
        }

        public class Prostokat
        {

            public Prostokat(int A, int B)
            {
                a = A;
                b = B;
            }


            public int a;
            public int b;


            public int pole(int a, int b)
            {
                return a * b;
            }

            public int obwod(int a, int b)
            {
                return (2 * a) + (2 * b);
            }
        }


        static void Main(string[] args)
        {
            Console.WriteLine("a: {0}\nb: {1}", a, b);
           
           
            Prostokat p = new Prostokat();


            p.a = 2;
            p.b = 3;
 


            int pole = p.pole(p.a, p.b);
            Console.WriteLine("Pole prostokąta (a * b): {0}", pole);
            //Ok, ale co jeśli będę musiał częściej, w innych klasach obliczać pole prostokąta?
            //Przecież obliczenie pola prostokąta się nie zmieni!

            Console.WriteLine("Naciśnij dowolny przycisk aby kontynuować...");
            Console.ReadKey(false);     
        }
    }

}


Jak widzimy, wszystko jest ok, działa. Jest tylko jedna mała kwestia...
Jeśli w innej części  programu będziemy chcieli ponownie obliczyć pole prostokąta (np w obrębie innej klasy), znowu będziemy musieli zadeklarować obiekt klasy Prostokat...


Na szczęście ktoś wpadł na pomysł: Stwórzmy Static.

Zmieńmy zatem metody naszej klasy na statyczne...

        public class Prostokat
        {
            public static int pole(int a, int b)
            {
                return a * b;
            }

            public static int obwod(int a, int b)
            {
                return (2 * a) + (2 * b);
            }
        }


...i mała zmiana w metodzie Main...
        static void Main(string[] args)
        {
            int a = 2;
            int b = 3;
            Console.WriteLine("a: {0}\nb: {1}", a, b);
            //Tym razem metoda jest startyczna,
            //nie ma potrzeby deklarowania obiektu klasy Prostokat do obliczenia pola!
            int pole = Prostokat.pole(a, b);

            Console.WriteLine("Pole prostokąta (a * b): {0}", pole);
            Console.WriteLine("Naciśnij dowolny przycisk aby kontynuować...");
            Console.ReadKey(false);                 
        }


... i świat stał się piękniejszy!

Jak widać w przykładzie, metody statyczne wcale nie muszą znajdować się w klasie statycznej, w naszym przykładzie była to klasa publiczna.

Uwaga: 
Ponieważ nasza klasa Prostokat posiada już statyczną metodę pole(a,b), nie możemy wywołać tej metody z obiektu:

Prostokat p = new Prostokat(2,4);
int pole = p.pole(p.a, p.b);

Od tej pory do metody na obliczenie pola odwołujemy się tak:

            Prostokat p = new Prostokat(2,3);
            int pole = Prostokat.pole(p.a, p.b);


Uwaga #2:
Jeśli cała klasa będzie statyczna, wszystkie jej atrybuty (zmienne) i operatory (funkcje, metody) muszą być statyczne.
Nadal możemy określać poziom dostępu do elementów klasy za pomocą modyfikatorów public i private.

Uwaga #3, #4:
Metody statyczne nie mogą być wirtualne (w końcu jak statyczna to statyczna!)
Klasa statyczna nie może być klasą bazową (

Podsumowanie

Słowo Static wprowadza wiele dobrego, ponieważ dzięki temu nie zawsze musimy tworzyć instancję naszych klas, aby wykonać pewne działania.

Możemy tworzyć uniwersalne klasy, metody lub zmienne, które będą dostępne "od ręki" bez konieczności tworzenia "na chwilę" obiektów. 


wtorek, 22 lipca 2014

O Interfejsach w C# - na chłopski rozum


Programowanie obiektowe przyniosło wiele nowych wynalazków, najbardziej docenionym są oczywiście klasy i obiekty. Zapewne wielu z Was zadaje sobie pytanie:

"Ok, skoro mamy klasy, z których możemy tworzyć obiekty, możemy sobie dziedziczyć itd, to co to są te interfejsy i po co właściwie musiałbym ich używać?"

Postaram się odpowiedzieć na to pytanie.


O Interfejsach w C# słów kilka

Pamiętam jeszcze podczas studiów, jak zdarzało się kiedy studenci interfejsem określali "okienko z przyciskami i polami, nazywanymi fachowo kontrolkami" :) Oczywiście winforms czy WPF jest "interfejsem użytkownika", natomiast służy do czegoś innego niż "interfejs dla programisty" :)

Interfejs jest czymś w rodzaju klasy. Wewnątrz interfejsu znajdują się metody (ale bez ich oprogramowania!) jakie obsługuje.

Niewątpliwie zaletą interfejsów jest fakt, że klasa może dziedziczyć po wielu interfejsach.

Interfejsy implementujemy podobnie jak klasy, zastępując słowo kluczowe class słowem interface.

Jak to bez implementacji metod? W takim razie po co? są od tego metody w klasie...

Jak już wcześniej wspomniałem, interfejs zawiera wyłącznie nazwy metod, których nie implementuje. Sama implementacja metody odbywa się po stronie klasy, która dziedziczy po interfejsie/interfejsach.

Najprościej mówiąc:
Interfejs mówi, CO na pewno ma robić  klasa (jakie czynności powinna realizować, jakie metody udostępniać). 
Metoda mówi JAK daną czynność wykonać.

Interfejs jest swojego rodzaju inspektorem, który mówi programiście: "Hola, nie oprogramowałeś metody w klasie, którą powinna realizować!"

A teraz trochę praktyki.

Tworzymy grę, czyli praktyczne wykorzystanie interfejsów


Załóżmy, że tworzymy prostą grę strategiczną, w której występują:
  1. Ludzie
    1. Żołnierze
    2. Inżynierowie
  2. Budynki
    1. obronne
    2. pozostałe


Żołnierze, pojazdy oraz inżynierowie mogą się poruszać w czterech kierunkach mapy: góra, dół, lewo, prawo.
Przyjmijmy, że Budynki raczej się nie poruszają :) W dodatku część budynków może strzelać (np działka antyrakietowe).


Teraz określimy nasze interfejsy: jeden do poruszania, drugi do strzelania:

        interface IMovement
        {
            void goUP();
            void goDown();
            void goLeft();
            void goRight();
        }

        interface IShoot
        {
            void Shoot(Unit who);
        }




W najprostrzym modelu, możemy podzielić jednostki na ludzi, pojazdy i budynki.
Stwórzmy zatem początkowe: Human, Building, oraz Unit:

        abstract class Unit
        {
            public int Health = 100;
        }

        class Human : Unit
        {
            //...
        }

        class Building : Unit
        {
            //...
        }

do szczęścia przydałoby się podzielić jeszcze ludzi na żołnierzy i inżynierów, oraz rozszerzyć budynki na działka:

        class Soldier : Human
        {
        }

        class Engineer : Human
        {
        }

        class Cannon : Building
        {
        }


jak już ustaliliśmy, ludzie mogą się poruszać. podłączmy zatem interfejs IMovement do klasy Human:

        class Human : Unit, IMovement
        {
        }

Jeśli spróbujemy teraz skompilować nasz kod, otrzymamy informację iż nie zimplementowaliśmy metod do poruszania się naszych jednostek!

Zatem skorygujmy nasz kod

        class Human : Unit, IMovement
        {
            //...
            public void goUP()
            {
                //idź do góry
            }
            public void goDown()
            {
                //idź w dół
            }
            public void goLeft()
            {
                //idź w lewo
            }
            public void goRight()
            {
                //idź w prawo
            }
        }

Teraz kompilacja przejdzie bez problemu.

zmodyfikujmy teraz naszą klasę Cannon:

class Cannon : Building, IShoot
        {
            void Shoot(Unit who)
            {
                //strzelaj do wskazanej jednostki!
            }
        }


i kompilacja... niestety znowu problem. Musimy pamiętać o tym, że metody oparte o interfejs zawsze muszą być publiczne! W końcu jest to funkcjonalność "widoczna" przez programistów, którzy później stworzą obiekt naszej przykładowej klasy Cannon.

poprawmy zatem:

class Cannon : Building, IShoot
        {
            public void Shoot(Unit who)
            {
                //strzelaj do wskazanej jednostki!
            }
        }


i teraz jest dobrze :)


pełny kod:

///////////////////////////////////////////////

abstract class Unit
        {
            public int Health = 100;
        }

        class Human : Unit, IMovement
        {
            //...
            public void goUP()
            {
                //idź do góry  
            }
            public void goDown()
            {
                //idź w dół
            }
            public void goLeft()
            {
                //idź w lewo
            }
            public void goRight()
            {
                //idź w prawo
            }
        }

        class Building : Unit
        {
            //...
        }


        class Soldier : Human, IShoot
        {
            public void Shoot(Unit who)
            {
            }

        }

        class Engineer : Human
        {
        }

        class Cannon : Building, IShoot
        {
            public void Shoot(Unit who)
            {
                //strzelaj do wskazanej jednostki!
            }
        }

        interface IMovement
        {
            void goUP();
            void goDown();
            void goLeft();
            void goRight();
        }

        interface IShoot
        {
            void Shoot( Unit who);
        }

///////////////////////////////////////////////

Teraz zobacz, jak to działa. stwórz obiekty utworzonych klas....

            Soldier soldier1 = new Soldier();
            Building armory = new Building();
            Engineer engineer1 = new Engineer();


... i zobacz, jakie nasze obiekty mogą wykonywać operacje.



Podsumowanie

Interfejsy umożliwiają dokładne określenie funkcji, jakie powinny realizować klasy (jakie powinny posiadać metody).
Jeśli do klasy podłączymy interfejsy, kompilator nie przepuści nas dalej, dopóki nie oprogramujemy naszych klas zdefiniowanymi przez interfejs metodami (które muszą być publiczne).

Bez interfejsów można oczywiście żyć, jednak pozwalają w sposób ładny uporządkować nasz kod. Gdybyśmy na przykład do naszej klasy IShoot dodali metodę ShootAlternative, kompilator nie przepuściłby nas momentu, kiedy wszystkie klasy dziedziczące po IShoot posiadałyby implementację wspomnianej metody.

Oczywiście interfejsy można wykorzystać nie tylko przy tworzeniu gier, ale wszędzie tam, gdzie ważne jest opanowanie tworzonego oprogramowania.

Nie ma sensu stosować interfejsów w przypadku gdy ten pojawi się w najwyżej jednej klasie skromnej aplikacji. Przy większych systemach warto jednak zadać sobie pytanie:

Czy z interfejsami nie będzie bezpieczniej i wygodniej?
 X3me