Tips’n’Tricks – peverify

Od pewnego czasu walczę z mono cecil z mniejszym i większym powodzeniem, w trakcie walk znajduje na forach różne przydatne podpowiedzi, postanowiłem je zebrać w ramach krótkich wpisów. O mono też się pojawi wpis, ale jak już zrobię to na czym mi zależy i w dodatku będzie działać 🙂
Teraz słów kilka o peverify.exe.

PEVERIFY.EXE

Gdy chcemy się pobawić trochę systemem refleksji w c# czy hakować zasoby, czy w jakikolwiek inny sposób ingerować w skompilowany kod aplikacji pisanej w .net, a następnie mieć pewność że wszystko jest w porządku i nie zakłóciliśmy jej wewnętrznej harmonii warto sprawdzić ją przy pomocy peverify.
Sposób użycia jest banalnie prosty, jako parametr aplikacji podaje się nazwę programu do sprawdzenia i czeka na wynik.
Tak wygląda wynik takiej operacji:

Zakolorowałem część nazwy aplikacji, nigdy nie wiesz czy ktoś się nie wkurzy jak mu wytkniesz jego błędy 😉

Pierwszy widoczny błąd (stack underflow) to akurat moja wina, dodałem do kodu wywołania swojej funkcji, ale zapominałem wcześniej załadować pewien argument i proszę wszystko widać, nawet podaje linijkę gdzie wystąpił błąd. Reszta to rzeczy odziedziczone, co ciekawe widać aplikacja nie musi być idealna by można było z niej korzystać.
Aby uruchomić peverify wystarczy odpalić Visual Studio Command Prompt i działa, oczywista oczywistość można dodać sobie odpowiednie ścieżki do PATH i nie bawić w linie poleceń do msvc.

To tyle, krótko i na temat.

Powershell – początki i prosty skrypt

Ostatnio na spotkaniu wroc.net jeden z wykładów był o powershell, w depeszowym skrócie to dos na sterydach. Postanowiłem dowiedzieć się o nim trochę więcej. Prowadzący powiedział, że na stronie powershell.com jest dostępna darmowa książka i rzeczywiście jest. Dodatkowo warto zapoznać się z dedykowanym edytorem dla power shell, którym jest PowerGUI, pozwala na debugowanie z praca krokową, breakpointy w skrypcie, podglądanie zmiennych i wiele inncyh dobroci, które ułatwiają pracę ze skryptem. Jeszcze żeby trochę zafisiować polecam ładną oprawę graficzną zamiast zwykłej linii poleceń – console2. Można ustawić sobie aby domyślnym interpreterem był powershell i korzystać z dobrodziejstw fikuśnego GUI dla konsolki.
Mój pierwszy skrypt, nie jest programem hello world, czasem trzeba złamać tradycję. Potrzebowałem prostego czegoś, aby móc przejść po plikach w aktualnym katalogu i wykonać na nich jakąś operację. Chciałem także, aby użytkownik mógł dowolnie wracać i przechodzić do kolejnych plików. Tak oto powstał ten skrypt, który umożliwia nawigację za pomocą strzałek na klawiaturze, oraz zakończenie pracy przez wduszenie guzika z napisem ESC.
Tym razem komentarze w kodzie 🙂
Niestety nie mam niczego, co by pokolorowało kod, a sam artystą nie jestem, także dzisiaj wersja notepad.

cls
Write-Host “Press left or right to iterate through xxx files, ESC to stop” -ForegroundColor DarkGreen

# interesuja mnie tylko pliki .xxx
# w powershell nie ma >, <, = odpowiada im -gt, -lt, -eq
for( $i=0; $i -lt (Get-ChildItem -Filter *.xxx).Count; )
{
    $filename = (Get-ChildItem -Filter *.xxx)[$i]

    # pomocniczy output dla uzytkownika, nazwa pliku
    Write-Host $filename

    # uruchomienie aplikacji z aktualnie wybranym plikiem
    # w moim przypadku bl.exe jest aplikacja konsolowa, nie kradnie mi focusa
    # troche gorzej jest gdy aplikacja jest okienkowa,
    # sprawdzcie na przykladzie przegladarki zdjec
    ./bl.exe $filename

    # chyba najwazniejsza czesc: oczekiwanie na wcisniecie guzika
    # trick znalazlem http://technet.microsoft.com/en-us/library/ee692948.aspx
    $c = $host.UI.RawUI.ReadKey(“NoEcho,IncludeKeyDown”)

    # chce zakonczyc tylko ESC, ciagle lewo czy ciagle prawo
    # bedzie korzystac ponownie z pierwszego/ostatniego pliku
    switch(($c).VirtualKeyCode)
    {
        37 # key left
        {
            $i–
            if($i -lt 0 )
            {
                $i=0
            }
        }
        39 # key right
        {
            $i++
            if($i -gt (Get-ChildItem -Filter *.xxx).Count – 1 )
            {
                $i = (Get-ChildItem -Filter *.xxx).Count -1
            }
        }
        27 # ESC key
        {
            $i = (Get-ChildItem -Filter *.xxx).Count
        }
    }
}
# buziaki i pozdrowienia
Write-Host jsthedeveloper pozdrawia

Właśnie zauważyłem, że istnieje także dodatek do VS, który koloruje składnie power shell. Jest to produkt chyba tego samego koleżki co stworzył PowerGui.
Podsumowując: polecam zabawę z power shellem, daje całkiem sporo możliwości, taki .net w skrypcie. Książkę, które jest dostępna także warto przeczytać, poklikać kilka przykładów, zobaczyć jak to się je i co można osiągnąć.
Rzuciłem jeszcze okiem na Wikipedię co tam w dołączonych linkach jest i proszę: pięć części filmideł o tym jak pisać skrypty. Także jak ktoś nie lub za dużo czytać, to może pooglądać.

Tyle w temacie początków.

xUnit vs Event – jak go przetestować?

Rozpoczynam kolejny projekt, który ma przynieść mi chwałę, sławę i pieniądze. Piszę go przy wykorzystaniu TDD (jak zwykle angielska wiki ma więcej do powiedzenia) Wszystko szło ładnie do momentu gdy nie natrafiłem na test w którym chciałem sprawdzić czy klasa którą testuję wywoła event. No bo jak sprawdzić teraz coś co wykona się później?
Na szczęście wujek google zna programistów, którzy znają odpowiedź na takie i inne pytania, w związku z czym szybko znalazłem odpowiedź na swój problem.
Zamieszczę ja tutaj ze stosownymi komentarzami od siebie, plus kilka własnych uwag, które może ktoś uzna za wartościowe. Jest taki oto sobie kod:

  1. // arrange
  2. var cut = new AsyncImageServiceDownloader();
  3. var isp = new ImagesServiceProvider();
  4. const string ServiceAddress = “http://www.digart.pl/”;
  5. const string PageSuffix = “/przegladaj/nowe.html?p=”;
  6. isp.ServiceAddress = ServiceAddress;
  7. isp.PageAddressSuffix = PageSuffix;
  8. cut.AddProvider(isp);
  9. int downloaded = 0;
  10. int canceled = 0;
  11. int suspended = 0;
  12. int resumed = 0;
  13. const uint Requested = 30;
  14. ManualResetEvent synvEvent = new ManualResetEvent(false);
  15.  
  16. // act
  17. cut.AddProvider(isp);
  18. cut.PageDownloaded += (s, e) => { downloaded++; };
  19. cut.PageDownloadCanceled += (s, e) => { canceled++; };
  20. cut.PageDownloadSuspended += (s, e) => { suspended++; };
  21. cut.PageDownloadResumed += (s, e) => { resumed++; };
  22.  
  23. cut.DownloadPageAsync(0, 0, Requested);
  24. for (uint i = 0; i < Requested; ++i)
  25. {
  26.     cut.SuspendDownload(0, i);
  27. }
  28.  
  29. synvEvent.WaitOne(1000);
  30. for (uint i = 0; i < suspended; ++i)
  31. {
  32.     cut.ResumeDownload(0, i);
  33. }
  34.             
  35. synvEvent.WaitOne(15 * 1000);
  36. // assert
  37. Assert.That(downloaded, Is.EqualTo(Requested));
  38.  

Widzimy podział na trzy AAA (nie znalazłem polskiego).
Na początku przygotowuje środowisko, które będę chciał sprawdzić; cut (class under test) to właściwy obiekt, który chce sprawdzić, isp służy do poprawnego zainicjalizowania obiektu cut. Ważny obiekt ManualResetEvent jest deklarowany i tworzony w linii 14 posłuży mi do synchronizacji i czekania na eventy. Linie od 18 do 21 to podpięcie się pod eventy, którymi klasa może rzucić. Choć w tym przypadku interesują mnie tylko dwa: suspended oraz downloaded. Przy użyciu wyrażeń lambda podpinam się pod eventy, a jedyną rzecz jaką robię to zliczam ich wywołania. W tym teście nie interesuje mnie zawartość argumentów.
Skoro wszystko mam już gotowe do działania to ogień! Linia 23 wywołuje asynchroniczne zaciąganie 30 elementów (requested=30). Chwilę potem do akcja wkracza pętla z niecierpiącym zwłoki poleceniem SuspendDownload następnie daję chwilę testowanemu obiektowi na wykonanie kodu odpowiedzialnego za wywołanie odpowiednich eventów. Wszystko dzięki wywołaniu ManualResetEvent.WaitOne(1000) – specjalnie podany jest timeout, ponieważ nigdzie nie będzie zmieniony jego stan. Czas się kończy, testy wykonują się dalej. Kolejne rozkazy; ResumeDownload tyle razy ile razy zdążyłem go zawiesić. I kolejny odpoczynek z timeout 15 sekund, co pozwala spokojnie zakończyć się wszystkim operacjom ściągnięcia.
Na sam koniec sprawdzam czy rzeczywiście udało się ściągnąć tyle ile początkowo żądałem. Voila! To tyle, proste prawda?

Moje dwie uwagi:
– Pamiętaj że w obsłudze eventa możesz samodzielnie zmienić stan ManualEventReset, jeśli z tego skorzystasz nie zapomnij jednak dodać timeout do WaitOne w przeciwnym wypadku, w razie niepowodzenia testów ManualResetEvent zawiesi działanie testów.
– Zastanów się czego dokładnie oczekujesz od twojego cut. Jeżeli cut wywoła PageDownloaded trzy razy to od razu będziesz szczęśliwy? A może chcesz sprawdzić czy cut wywoła PageDownloaded tylko trzy razy, a żadnych innych eventów ani razu.

Jakieś uwagi? Inne sposoby? Literówka?
Wiesz jak mnie znaleźć.
JS

Palcem po mapie – CultureInfo w natarciu

Przygotowania do 70-536 idą sobie idą. Miałem ostatni mały sparing z CultureInfo, zatem trzeba się podzielić wiedzą, którą wyniosłem z tej krótkiej walki.
CultureInfo zawiera wszystkie informacje, których będziemy potrzebować, żeby aplikacja była poprawnie językowa (oczywiście trzeba znać jeszcze język obcy). Zaczynając od tego po której stronie kartki zaczynamy pisać, poprzez znak oddzielający ułamek od części całkowitej, powoli sunąc przez nazwę kalendarza, który jest używany i wreszcie kończąc na znaku, którym oznacza się nieskończoność. To tylko część z informacji, które posiada ta klasa.
Z CultureInfo można skorzystać poprzez podanie go jako parametr IFormatProvider (ToString, string.Format, StringBuilder.AppendFormat) lub poprzez ustawienie go w aktualnym wątku jako aktualną kulturę metodą Thread.CurrentThread.CurrentUICulture = myNewCultureInfo;
Kawałek kodu, dzięki któremu można się bliżej poznać z kulturami z całego świata. Wszystko dzięki wbudowanemu wsparciu dla multi-kuli w windows.

  1. private void Form1_Load(object sender, EventArgs e)
  2. {
  3.     foreach (var ci in CultureInfo.GetCultures(CultureTypes.InstalledWin32Cultures))
  4.     {
  5.         this._avaiableCultures.Items.Add(ci.Name + “, “+ci.EnglishName+”( “+ci.NativeName+” )”);
  6.     }
  7.     this._avaiableCultures.SelectedIndex = 0;
  8. }
  9.  
  10. private void _avaiableCultures_SelectedIndexChanged(object sender, EventArgs e)
  11. {
  12.     string culture = this._avaiableCultures.SelectedItem.ToString().Substring(0,
  13.                      this._avaiableCultures.
  14.                      SelectedItem.ToString().
  15.                      IndexOf(“,”));
  16.  
  17.     StringBuilder sb = new StringBuilder();
  18.     CultureInfo ci = new CultureInfo(culture);
  19.     sb.AppendLine(“CALENDAR:”);
  20.     sb.AppendLine(ci.DateTimeFormat.NativeCalendarName);
  21.     sb.AppendLine(“DAYS:”);
  22.     foreach (var dayName in ci.DateTimeFormat.DayNames)
  23.     {
  24.         sb.AppendLine(dayName);
  25.     }
  26.     sb.AppendLine(“MONTHS:”);
  27.     foreach (var monthName in ci.DateTimeFormat.MonthNames)
  28.     {
  29.         sb.AppendLine(monthName);
  30.     }
  31.  
  32.     sb.AppendFormat(“NUMBERS:nFractions go after: {0}n”, ci.NumberFormat.CurrencyDecimalSeparator);
  33.     sb.AppendFormat(“Currency symbol: {0}n”, ci.NumberFormat.CurrencySymbol);
  34.     sb.AppendFormat(“Infinite symbol: {0}n”, ci.NumberFormat.PositiveInfinitySymbol);
  35.     this._localizedInfo.Text = sb.ToString();
  36.    
  37.     //CultureInfo myNewCultureInfo = new CultureInfo(culture);
  38.     //Thread.CurrentThread.CurrentUICulture = myNewCultureInfo;
  39.  
  40.     this._moneyValue.Text = string.Format(ci, “{0:C}”, 12345.67);
  41.     this._currentTime.Text = DateTime.Now.ToString(ci);
  42.  
  43.     if( ci.TextInfo.IsRightToLeft )
  44.     {
  45.         this._currentTime.RightToLeft = RightToLeft.Yes;
  46.         this._avaiableCultures.RightToLeft = RightToLeft.Yes;
  47.         this._currentTime.RightToLeft = RightToLeft.Yes;
  48.         this._localizedInfo.RightToLeft = RightToLeft.Yes;
  49.         this._moneyValue.RightToLeft= RightToLeft.Yes;
  50.     }
  51.     else
  52.     {
  53.         this._currentTime.RightToLeft = RightToLeft.No;
  54.         this._avaiableCultures.RightToLeft = RightToLeft.No;
  55.         this._currentTime.RightToLeft = RightToLeft.No;
  56.         this._localizedInfo.RightToLeft = RightToLeft.No;
  57.         this._moneyValue.RightToLeft = RightToLeft.No;
  58.     }
  59. }

Komentarz:
@01 – Na załadowanie formy, odczytuje wszystkie wspierane kultury. Następnie wrzucam informacje o nich do combobox.
@12  – Po wybrania innej kultury, sprawdzam jej skróconą nazwę.
@18 – Tworzę nową oraz wypisuje na jej podstawie nazwę kalendarza (@20), wszystki dni(@21), wszystkie miesiące(@26). Na koniec podstawowe jednostki płatnicze(@32). Przed wyjściem sprawdzam, czy w danej kulturze pisze się od lewej czy od prawej (@43) i aktualizuje kontrolki.

A tak wygląda aplikacja w działaniu:

Linq oraz Entity Framework – podstawowe polecenia i przykłady

Muszę się przyznać, że nigdy nie przepadałem za bazami danych. Zawsze uważałem je za nudny temat i nie odczuwałem większej potrzeby zajmowania się nimi. Prawdę mówiąc podobne zdanie miałem o C#, a teraz piszę w C# i uczę się Entity Framework, a w dodatku swoje doświadczenia spisuje i czasem się nimi dzielę.
Dzisiaj trochę o LINQ i Entity Frameworki, będą to raczej suche przykłady jak korzystać z linq, plus moje bardzo osobiste komentarze do nich. Przykłady pochodzą z książki, którą czytam i jeszcze nie wiem czy mogę ją polecić. Zła nie jest, ale jest w niej spora ilość błędów, które potrafią zdenerwować. Nadal jednak ją czytam, więc nie jest aż tak źle.
Entity Framework 4 in action” – bo o niej mowa, jest tworem trzech panów:
– Stefano Mostarda, – Marco De Sanctis
– Daniele Bochicchio

Książka ukazała się nakładem wydawnictwa Manning. Można ją zamówić w polskich księgarniach w cenie od 150 do 200 polskich złotych.Nie będę podawać przykładów na najprostsze zapytanie linq, zacznę od tych ciut trudniejszych, przykłady będą bazować na tych z książki
Przykłady będą się opierać na następującej bazie danych:

Oraz na takim mapowaniu jej do EDM:

Do rzeczy, czyli to co developerzy lubią najbardziej kod:

  • Grupowanie wyników na podstawie więcej niż jednego klucza jest możliwe przy pomocy nowego typu anonimowego, który posłuży za klucz do grupowania:
    1. using (var context = new OrderITEntities())
    2. {
    3.     var result = from o in context.Orders
    4.                  group o by new { o.ShippingAddress.City, o.ShippingAddress.ZipCode };
    5.  
    6.     foreach (var key in result)
    7.     {
    8.         Trace.WriteLine(string.Format(“{0}-{1}”, key.Key.City, key.Key.ZipCode));
    9.         foreach (var item in key)
    10.         {
    11.             Trace.WriteLine(item.OrderId);
    12.         }
    13.     }
    14. }

    Komentarz:
    @04 – Wyniki są grupowane na podstawie klucza {City, ZipCode}
    @06 – Najpierw trzeba przejść się po wszystkich kluczach
    @10 – Teraz można odczytać wyniki zgrupowane dla danego klucza.
    Wynikiem takiej operacji jest:

    Boston-87329
    17
    Chicago-ZipCode
    18
    city-zipc
    21
    City-ZipCode
    24
    fzxcb-xcbc
    46
    Los Angeles-34243
    31
    Los Angeles-54564
    14
    Miami-24323
    15
    Miami-34631
    16
    New York-0000001
    34
    35
    36
    New York-7777
    49
    New York-98765
    29
    30
    Rome-00100
    22
    23
    sc-sz
    32
    sdf-sdf
    47
    Seattle-24332
    13
    Seattle-98765
    12
    v<-cxv 44 Washington-98765 11 xc-dzvcx 45
  • Gdy nie potrzebna jest cała tabela, można skorzystać z projekcji wyników na nowy anonimowy typ, wybierając tylko to czego aktualnie potrzebujemy. Projekcja na przykładzie grupowania
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var r = from o in context.Orders
    4.             group o by o.ShippingAddress.City
    5.             into oGroup
    6.             select new { CityName = oGroup.Key, Items = oGroup };
    7.     foreach (var key in r)
    8.     {
    9.         Trace.WriteLine(string.Format(“{0}: {1}”, key.CityName, key.Items.Count()));
    10.         foreach (var item in key.Items)
    11.         {
    12.             Trace.WriteLine(item.OrderId);
    13.         }
    14.     }
    15. }

    Komentarz:
    @05 – wynik grupowania będzie w oGroup
    @06 – projekcja na nowy anonimowy typ
    @08 – pierwsza iteracja po kluczach
    @11 – kolejna iteracja po zgrupowanych obiektach
    Wyniki operacji:

    Boston: 1
    17
    Chicago: 1
    18
    city: 2
    21
    24
    fzxcb: 1
    46
    Los Angeles: 2
    14
    31
    Miami: 2
    15
    16
    New York: 6
    29
    30
    34
    35
    36
    49
    Rome: 2
    22
    23
    sc: 1
    32
    sdf: 1
    47
    Seattle: 2
    12
    13
    v<: 1 44 Washington: 1 11 xc: 1 45
  • Wybranie krotek spełniających nasze wymagania, a następnie zgrupowanie według adresu wysyłki:
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var result = from o in context.Orders
    4.                  group o by o.ShippingAddress.City
    5.                      into g
    6.                      where g.Count() > 1
    7.                  select g;
    8.  
    9.     foreach (var key in result)
    10.     {
    11.         Trace.WriteLine(string.Format(“{0}: {1}”, key.Key, key.Count()));
    12.         foreach (var order in key)
    13.         {
    14.             Trace.WriteLine(order.OrderId);
    15.         }
    16.     }
    17. }

    Komentarz
    @04 – Wyniki zostaną zgrupowane na podstawia miasta
    @06 – Interesują nas tylko takie krotki, które posiadają więcej niż jedno zamówienie
    Wynik operacji

    city: 2
    21
    24
    Los Angeles: 2
    14
    31
    Miami: 2
    15
    16
    New York: 6
    29
    30
    34
    35
    36
    49
    Rome: 2
    22
    23
    Seattle: 2
    12
    13
  • Sortowanie według zadanego klucza, wykonane przez silnik DB:
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var result = from o in context.Orders
    4.                  orderby o.ShippingAddress.City
    5.                  select o;
    6.  
    7.     foreach (var order in result)
    8.     {
    9.         Trace.WriteLine(string.Format(“{0}-{1}”, order.ShippingAddress.City, order.OrderId));
    10.     }
    11. }

    Komentarze
    @04 – Kluczem sortujący jest miasto
    Wynik operacji:

    Boston-17
    Chicago-18
    city-21
    City-24
    fzxcb-46
    Los Angeles-31
    Los Angeles-14
    Miami-15
    Miami-16
    New York-29
    New York-30
    New York-34
    New York-35
    New York-36
    New York-49
    Rome-22
    Rome-23
    sc-32
    sdf-47
    Seattle-12
    Seattle-13
    v<-44 Washington-11 xc-45
  • Posortowanie wyników według dynamicznie obliczanej wartości, wybranie tylko części do wyniku przy pomocy projekcji:
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var result = from o in context.Orders
    4.                  where o.OrderDetails.Count > 0
    5.                  orderby o.OrderDetails.Sum(d => d.Quantity * (d.UnitPrice – d.Discount))
    6.                  select new
    7.                      {
    8.                          o.OrderId,
    9.                          o.OrderDate,
    10.                          o.ShippingAddress,
    11.                          Total = o.OrderDetails.Sum(d => d.Quantity * (d.UnitPrice – d.Discount))
    12.                      };
    13.     foreach (var item in result)
    14.     {
    15.         Trace.WriteLine(item);
    16.     }
    17. }

    Komentarze:
    @04 – Ta linia odrzuca zamówienia, które nie posiadają żadnych dodatkowych informacji, DB z którą pracuje najwyraźnie pozwala na złożenie zamówienia i nie przypisanie do niego szczegółów
    @05 – Wynik ma być posortowany, wg. nowej cechy zamówienia. Jest ona obliczania dynamicznie
    @06 – Projekcja – do wyniku zostaje dołączona informacja obliczana dynamicznie
    Wynik operacji:

    { OrderId = 16, OrderDate = 2008-11-27 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 1,0000 }
    { OrderId = 15, OrderDate = 2008-11-27 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 2,0000 }
    { OrderId = 46, OrderDate = 2010-04-20 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 4,0000 }
    { OrderId = 47, OrderDate = 2010-04-20 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 4,0000 }
    { OrderId = 21, OrderDate = 2009-02-03 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 18,0000 }
    { OrderId = 18, OrderDate = 2008-11-27 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 48,0000 }
    { OrderId = 34, OrderDate = 2009-10-28 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 50,0000 }
    { OrderId = 35, OrderDate = 2009-10-28 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 50,0000 }
    { OrderId = 36, OrderDate = 2009-10-28 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 50,0000 }
    { OrderId = 22, OrderDate = 2009-02-03 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 50,0000 }
    { OrderId = 23, OrderDate = 2009-02-04 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 50,0000 }
    { OrderId = 49, OrderDate = 2010-04-20 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 56,0000 }
    { OrderId = 30, OrderDate = 2009-08-09 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 90,0000 }
    { OrderId = 31, OrderDate = 2009-08-09 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 100,0000 }
    { OrderId = 12, OrderDate = 2008-11-27 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 100,0000 }
    { OrderId = 29, OrderDate = 2009-08-09 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 135,0000 }
    { OrderId = 13, OrderDate = 2008-11-27 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 152,0000 }
    { OrderId = 32, OrderDate = 2009-08-10 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 201,0000 }
    { OrderId = 17, OrderDate = 2008-11-27 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 254,0000 }
    { OrderId = 11, OrderDate = 2008-11-27 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 255,0000 }
    { OrderId = 24, OrderDate = 2010-05-27 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 270,2000 }
    { OrderId = 14, OrderDate = 2008-11-27 00:00:00, ShippingAddress = OrderIT.Model.AddressInfo, Total = 556,0000 }
  • Sortować można nie tylko główną tabele wyników, ale także wszystkie właściwości do których posiada ona referencje. Jest to możliwe tylko dzięki projekcji wyniku:
    1. using (var context = new Model.OrderITEntities())
    2.             {
    3.                 var result = from o in context.Orders
    4.                              select new
    5.                                         {
    6.                                             o.OrderId,
    7.                                             o.OrderDate,
    8.                                             Details = o.OrderDetails.OrderBy(d => d.Quantity)
    9.                                         };
    10.                 foreach (var item in result)
    11.                 {
    12.                     Trace.WriteLine(“oid: ” + item.OrderId + “, date: ” + item.OrderDate);
    13.                     foreach (var d in item.Details)
    14.                     {
    15.                         Trace.WriteLine(string.Format(“q:{0}, id:{1}”, d.Quantity, d.OrderDetailId));
    16.                     }
    17.                 }
    18.             }

    Komentarz: @06,07 – Wynik ma zawierać wybrane przez nas pola @08 – Details ma zawierać szczegóły dotyczące zamówienia, dodatkowo mają one zostać posortowane według ilości.
    Wynik operacji:

    oid: 11, date: 2008-11-27 00:00:00
    q:5, id:22
    q:5, id:23
    oid: 12, date: 2008-11-27 00:00:00
    q:2, id:24
    oid: 13, date: 2008-11-27 00:00:00
    q:2, id:25
    q:3, id:26
    oid: 14, date: 2008-11-27 00:00:00
    q:2, id:27
    q:5, id:28
    q:8, id:29
    oid: 15, date: 2008-11-27 00:00:00
    q:2, id:30
    oid: 16, date: 2008-11-27 00:00:00
    q:1, id:31
    oid: 17, date: 2008-11-27 00:00:00
    q:2, id:32
    q:2, id:34
    q:5, id:33
    oid: 18, date: 2008-11-27 00:00:00
    q:4, id:35
    oid: 21, date: 2009-02-03 00:00:00
    q:1, id:36
    q:2, id:37
    oid: 22, date: 2009-02-03 00:00:00
    q:5, id:38
    oid: 23, date: 2009-02-04 00:00:00
    q:5, id:39
    oid: 24, date: 2010-05-27 00:00:00
    q:1, id:69
    q:3, id:40
    q:5, id:41
    oid: 29, date: 2009-08-09 00:00:00
    q:45, id:44
    oid: 30, date: 2009-08-09 00:00:00
    q:2, id:45
    oid: 31, date: 2009-08-09 00:00:00
    q:2, id:46
    oid: 32, date: 2009-08-10 00:00:00
    q:13, id:47
    q:45, id:48
    oid: 34, date: 2009-10-28 00:00:00
    q:5, id:50
    oid: 35, date: 2009-10-28 00:00:00
    q:5, id:52
    oid: 36, date: 2009-10-28 00:00:00
    q:5, id:54
    oid: 44, date: 2010-04-20 00:00:00
    oid: 45, date: 2010-04-20 00:00:00
    oid: 46, date: 2010-04-20 00:00:00
    q:2, id:63
    oid: 47, date: 2010-04-20 00:00:00
    q:2, id:64
    oid: 49, date: 2010-04-20 00:00:00
    q:3, id:66
    q:5, id:68
  • Zwykły JOIN jest nudny 🙂 od razu przykład dla tej operacji, na 2 polach, które posłużą za podstawe łączenia tabel:
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var result = from o in context.Orders
    4.                  join c in context.Companies.OfType<Customer>()
    5.                      on new { o.ShippingAddress.City, o.Customer.CompanyId }
    6.                      equals new { c.ShippingAddress.City, c.CompanyId }
    7.                  select o;
    8.  
    9.     foreach (var order in result)
    10.     {
    11.         Trace.WriteLine(string.Format(“oid:{0} date:{1} customer:{2} address:{3}”, order.OrderId,
    12.                         order.OrderDate, order.CustomerId,
    13.                         order.ShippingAddress.Address));
    14.     }
    15. }

    Komentarze
    @04 – Połączenie korzystać z klasy Order oraz Customer. Customer dziedziczy po Companies, trzeba jasno zaznaczyć, że z Companies interesują nas tylko Customer.
    @05 – Połączenia bazować będzie na nowym typie anonimowym, składającym się z City oraz ProductId
    Wynik operacji:

    oid:29 date:2009-08-09 00:00:00 customer:1 address:7th Avenue
    oid:30 date:2009-08-09 00:00:00 customer:1 address:7th Avenue
    oid:32 date:2009-08-10 00:00:00 customer:2 address:sa
    oid:34 date:2009-10-28 00:00:00 customer:1 address:2th street
    oid:35 date:2009-10-28 00:00:00 customer:1 address:2th street
    oid:36 date:2009-10-28 00:00:00 customer:1 address:2th street
  • Chciałem napisać też przykład dla OUTER JOIN, ale coś nie do końca go rozumiem. Nie chce pisać strasznych głupot. Wszystko rozbija się o użycie metody DefaultIfEmpty, nie będę niepotrzebnie wprowadzał fermentu i chaosu. Doczytam i zaktualizuje temat – obiecuje 🙂
  • W przykładach przyjęto metody TPC (Table Per Class) dla obiektów product, show oraz shirt. Przekłada się to na odpowiednie mapowanie w EF, klasy shoe oraz shirt dziedziczą po klasie product. Oczywiście tylko w C#, w sql jest to rozwiązane przy pomocy kluczy obcych. Pisząc zapytanie, które ma pobrać informację o jednym z wybranych produktów, korzystamy z dwóch tabel w sql; product oraz shoe/shirt. Linq nie jest w stanie tego samodzielnie wywnioskować, w związku z czymy należy mu w tym lekko pomóc:
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var result = from p in context.Products.OfType<Shoe>()
    4.                  select p;
    5.     foreach (var product in result)
    6.     {
    7.         Trace.WriteLine(product.GetType().ToString());
    8.     }
    9. }

    Komentarz
    @03 – wybierz tylko obiekty, który są typu Shoe (będą zwracane poprzez proxy, ale dla użytkownika są one przezroczyste i korzysta się jak z normalnej klasy)
    Wynik operacji:

    System.Data.Entity.DynamicProxies.Shoe_7AA4169E6949EE79A10F953F29A44C8C07E207BD569BE65D49A2BA0AAA0A98EB
    System.Data.Entity.DynamicProxies.Shoe_7AA4169E6949EE79A10F953F29A44C8C07E207BD569BE65D49A2BA0AAA0A98EB

    Istnieje także alternatywna metoda, jest ona mniej wydajna, spełnia jednak swoje zadanie:

    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var result = from p in context.Products
    4.                  where p is Shirt
    5.                  select p;
    6.     foreach (var product in result)
    7.     {
    8.         Trace.WriteLine(product.GetType().ToString());
    9.     }
    10. }

    Komentarz
    @03 – Wybierz wszystkie obiekty Product
    @04 – Odfiltruj ze wszystkich, tylko te spełniające założenie.
    Wynik (jest taki sam jak poprzednio, znowu widać klasę proxy, jednak tym razem są to Shirt):

    System.Data.Entity.DynamicProxies.Shirt_9781A3B17068763ADD1262E9DF89ABE0B9179FD9113AA7C50B50B8F1AEE66B55
    System.Data.Entity.DynamicProxies.Shirt_9781A3B17068763ADD1262E9DF89ABE0B9179FD9113AA7C50B50B8F1AEE66B55
    System.Data.Entity.DynamicProxies.Shirt_9781A3B17068763ADD1262E9DF89ABE0B9179FD9113AA7C50B50B8F1AEE66B55
    System.Data.Entity.DynamicProxies.Shirt_9781A3B17068763ADD1262E9DF89ABE0B9179FD9113AA7C50B50B8F1AEE66B55
    System.Data.Entity.DynamicProxies.Shirt_9781A3B17068763ADD1262E9DF89ABE0B9179FD9113AA7C50B50B8F1AEE66B55
  • Może zaistnieć także sytuacja gdy potrzeba będzie znaleźć produkty danego typu, ale zwrócić jest w postaci kolekcji obiektów klasy podstawowej, na to także znajdą się dwa sposoby:
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var r = from p in context.Products
    4.             where (p as Shoe).Sport == “Basket”
    5.             select p;
    6.  
    7.     foreach (var product in r)
    8.     {
    9.         Trace.WriteLine(string.Format(“{0}: {1}”, product.Name, product.Description));
    10.     }
    11. }

    Komentarz
    @03 – Wybierz wszystko
    @04 – Rzutuj na Shoe i spradź czy pole Sport jest równe Basket
    Wynik:

    Basket shoes: Basket shoes

    Wersja alternatywna:

    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var r = (from p in context.Products.OfType<Shoe>()
    4.              where p.Sport == “Basket”
    5.              select p).Cast<Product>();
    6.  
    7.     foreach (var product in r)
    8.     {
    9.         Trace.WriteLine(string.Format(“{0}: {1}”, product.Name, product.Description));
    10.     }
    11. }

    Komentarz
    @03 – Wybierz tylko Shoe
    @04 – Sprawdź warunek
    @05 – Wynik rzutuj na klasą bazową Product
    Wynik:

    Basket shoes: Basket shoes

Doszliśmy do końca zapytań i selekcji. W moich notatkach mam jeszcze małe co nie co na temat funkcji i ich wykorzystania. Dzielą się one na dwa rodzaje, te dostarczone przez EF (funkcje kanoniczne) oraz te dostarczone przez silnik bazodanowy.
Funkcje z EF:
+ działać będą z każdą bazą, która jest wspierana przez EF
– działać będą po stronie klienta
Funkcje z silnika
+ działają po stronie serwera
– silnie uzależniają od implementacji na którą się zdecydowaliśmy
Dwa przykłady:

  • Funkcja działająca w oparciu o EF
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var r = from o in context.Orders
    4.             where EntityFunctions.DiffDays(o.OrderDate, o.ActualShippingDate) > 5
    5.             select o;
    6.  
    7.     foreach (var order in r)
    8.     {
    9.         Trace.WriteLine(string.Format(“OrderId: {0} missing the date”, order.OrderId));
    10.     }
    11. }

    Skromy komentarz
    @04 – Jasno sprecyzowane jest, że sprawdzamy różnicę w dniach.
    Wynik operacji:

    OrderId: 11 missing the date
    OrderId: 49 missing the date
  • Identyczna funkcja, ale działająca w oparciu o DB
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var r = from o in context.Orders
    4.             where SqlFunctions.DateDiff(“d”, o.OrderDate, o.ActualShippingDate) > 5
    5.             select o;
    6.     foreach (var order in r)
    7.     {
    8.         Trace.WriteLine(string.Format(“OrderId: {0} missing the date”, order.OrderId));
    9.     }
    10. }

    Skromy komentarz
    @04 – Należy dodać informację o którą część daty
    Wynik:

    OrderId: 11 missing the date
    OrderId: 49 missing the date

I ostatnie czym chciałem się podzielić, to możliwość wykonania własnej kwerendy. Bez pomocy ze strony LINQ. Może się zdarzyć, że zapytanie stworzone przez LINQ będzie zbyt wolne, a samodzielnie będziemy w stanie napisać je w prostszy i bardziej wydajny sposób. Dodatkowo możliwe jest także dynamicznie zdefiniowanie parametów zapytania. Są dwa podejścia, pierwsze prostsze drugie odrobinę bardziej skomplikowane. Należy pamiętać, że nie wolno ich łączyć. Tak samo jak nie wolno kąpać gremlinów i karmić ich po północy .

  • Pierwszy przykład, to prosta kwerenda, bez szału:
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var r = context.ExecuteStoreQuery<OrderDetailProjection>;
    4.             (“SELECT quantity, unitprice, discount FROM OrderDetail”);
    5.     foreach (var odp in r)
    6.     {
    7.         Trace.WriteLine(string.Format(“quantity:{0},discount:{1},unitprice:{2}”, odp.Quantity, odp.Discount,
    8.                         odp.UnitPrice));
    9.     }
    10. }

    Wynik:

    quantity:5,discount:0,0000,unitprice:1,0000
    quantity:5,discount:0,0000,unitprice:50,0000
    quantity:2,discount:0,0000,unitprice:50,0000
    quantity:2,discount:0,0000,unitprice:1,0000
    quantity:3,discount:0,0000,unitprice:50,0000
    quantity:2,discount:0,0000,unitprice:1,0000
    quantity:5,discount:0,0000,unitprice:50,0000
    quantity:8,discount:12,0000,unitprice:50,0000
    quantity:2,discount:0,0000,unitprice:1,0000
    quantity:1,discount:0,0000,unitprice:1,0000
    quantity:2,discount:0,0000,unitprice:1,0000
    quantity:5,discount:0,0000,unitprice:50,0000
    quantity:2,discount:2,0000,unitprice:3,0000
    quantity:4,discount:0,0000,unitprice:12,0000
    quantity:1,discount:0,0000,unitprice:10,0000
    quantity:2,discount:0,0000,unitprice:4,0000
    quantity:5,discount:0,0000,unitprice:10,0000
    quantity:5,discount:0,0000,unitprice:10,0000
    quantity:3,discount:0,0000,unitprice:3,4000
    quantity:5,discount:0,0000,unitprice:50,0000
    quantity:45,discount:0,0000,unitprice:3,0000
    quantity:2,discount:0,0000,unitprice:45,0000
    quantity:2,discount:0,0000,unitprice:50,0000
    quantity:13,discount:0,0000,unitprice:12,0000
    quantity:45,discount:0,0000,unitprice:1,0000
    quantity:5,discount:0,0000,unitprice:10,0000
    quantity:5,discount:0,0000,unitprice:10,0000
    quantity:5,discount:0,0000,unitprice:10,0000
    quantity:2,discount:1,0000,unitprice:3,0000
    quantity:2,discount:1,0000,unitprice:3,0000
    quantity:3,discount:0,0000,unitprice:2,0000
    quantity:5,discount:0,0000,unitprice:10,0000
    quantity:1,discount:0,0000,unitprice:10,0000
  • Teraz kwerenda z możliwością parametryzacji zapytania, wersja prostsza dynamicznego dodawania wartości dla zapytania. Jest identyczna jak w wywołaniu WriteLine(…):
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var r = context.ExecuteStoreQuery<string>
    4.             (“SELECT Name FROM company WHERE ShippingCity={0} AND billingcity={1}”, “New York”, “paris”);
    5.  
    6.     foreach (var name in r)
    7.     {
    8.         Trace.WriteLine(name);
    9.     }
    10. }

    Wynik:

    Billl Clay
  • Oraz wersja ciut trudniejsza, z wykorzystaniem paramentów ściślej związanych z bazą danych:
    1. using (var context = new Model.OrderITEntities())
    2. {
    3.     var a = new SqlParameter(“p0”, DbType.String) { Value = “New York” };
    4.     var b = new SqlParameter(“p1”, DbType.String) { Value = “Paris” };
    5.     var r = context.ExecuteStoreQuery<string>
    6.            (“SELECT name FROM company WHERE shippingcity= @p0 AND billingcity= @p1”, b, a);
    7.     foreach (var name in r)
    8.     {
    9.         Trace.WriteLine(name);
    10.     }
    11. }

    Wynik:

    Billl Clay

To na razie tyle. Myślę, że wyskrobię jeszcze kilka wpisów dotyczących DB i EF. Przypominam także, że moje wpisy nie są idealne i mogą zawierać błędy. Także nie trzymałbym się tak bardzo kurczowo tego co napisałem. Najlepiej będzie, jeśli sami spróbujecie czy to co napisałem działa oraz popróbujecie zmodyfikować kod, który jest tutaj przedstawiony.
Jak zawsze chętnie przyjmę na klatę wszystkie komentarze, którymi tak chętnie się ze mną dzielicie 😉
Jarek