Projektowanie, Programowanie, Codzienność – BeniaminZaborski.com

28 stycznia 2014

Z LINQ jak po sznurku

Filed under: How Can I Take ... — Tagi: , , , , — Beniamin Zaborski @ 22:56

Każdy programista .NET słyszał, zna i zapewne używa LINQ. To bezsprzecznie świetny mechanizm. Świetny jest do tego stopnia, że teraz to środowisko Java przeszczepia go z .NET do siebie, a nie odwrotnie … ghhh. Sorry Javowcy taki teraz mamy klimat:).

Hola, hola … można by powiedzieć, bo w naszym fajnym LINQ można się rozbić o ścianę! Co gdy chce sobie dynamicznie złożyć zapytanie?

Pokażę to na podstawie trywialnego przykładu. Mamy listę obiektów:

var kontrahenci = new List<Kontrahent>() {
 new Kontrahent { Nazwa = "Kontrahent 1", Symbol="KWA1", CzyAktywny = true },
 new Kontrahent { Nazwa = "Kontrahent 2", Symbol="KSL2", CzyAktywny = false },
 new Kontrahent { Nazwa = "Kontrahent 3", Symbol="KWE2", CzyAktywny = false },
 new Kontrahent { Nazwa = "Kontrahent 4", Symbol="KNO2", CzyAktywny = true }
 };

i chcemy dynamicznie złożyć i wykonać poniższe zapytanie:

kontrahenci.Where(k => k.CzyAktywny && k.Symbol.Contains("2")).ToList();

Jak to zwykle bywa jest parę opcji, np.:

1. Użycie dynamic expressions:

 ParameterExpression kontrahentParam = Expression.Parameter(typeof(Kontrahent), "k");
 Expression leftCzyAktywnyExpression = Expression.Property(kontrahentParam, "CzyAktywny");
 Expression rightCzyAktywnyExpression = Expression.Constant(false);
 Expression czyAktywnyExpression = Expression.Equal(leftCzyAktywnyExpression, rightCzyAktywnyExpression);
 Expression leftSymbolExpression = Expression.Property(kontrahentParam, "Symbol");
 Expression rightSymbolExpression = Expression.Constant("2", typeof(string));
 Expression symbolExpression = Expression.Call(leftSymbolExpression, typeof(string).GetMethod("Contains", new[] { typeof(string) }), rightSymbolExpression);
 Expression andExpression = Expression.AndAlso(czyAktywnyExpression, symbolExpression);
 var whereExpression = Expression.Lambda<Func<Kontrahent, bool>>(andExpression, kontrahentParam).Compile();
 kontrahenci.AsQueryable().Where(whereExpression).ToList();

Wygląda okrutnie, a to przecież banalny przykład. Pomyślcie co się dzieje w tych naprawdę złożonych? Musi boleć.

2. Dynamic Expression API i zapytanie ze stringa:

kontrahenci.AsQueryable().Where("CzyAktywny == @0 and Symbol.Contains(@1)", false, "2").ToList();

Teraz lepiej? No myślę, że zdecydowanie!

A przed tym wszystkim wystarczy tylko:

1. Install-Package DynamicQuery

2. using System.Linq.Dynamic;

18 stycznia 2014

WCF – logowanie błędów

Może część z Was zastanawiała się kiedyś jak wprowadzić własny mechanizm logowania błędów w WCF? Ja miałem potrzebę implementacji takiego mechanizmu logowania i podzielę się jedną z prostszych opcji.

O co właściwie chodzi?

Mianowicie walczymy o to, aby po stronie serwera hostującego nasz serwis WCF była możliwość zapisu do logów błędów występujących w tymże serwisie. Wiemy, że domyślnie wszystkie wyjątki jakie wystąpią w serwisach WCF nie są wysyłane do klienta. Wiemy także, że to domyślne zachowanie możemy łatwo zmienić ustawiając includeExceptionDetailInFaults="true". Może na etapie developingu (tfu co za słowo) jest to wygodne, ale na pewno w systemie produkcyjnym … hmm no cóż powiedzmy, że klienta nie do końca interesują szczegóły wyjątku i cały stack trace:).  W systemie produkcyjnym chcemy mieć szczegóły błędów w naszym logu … czymkolwiek on będzie.

Z pomocą przychodzi nam prosty interfejs IErrorHandler z przestrzeni System.ServiceModel.Dispatcher. Zaimplementujmy go:

public class CustomErrorHandler : IErrorHandler
{
    public bool HandleError(Exception error)
    {
        Logger.LogError(error); // Tutaj użyj swojego loggera!
        return true;
    }

    public void ProvideFault(Exception error, MessageVersion version, ref Message msg)
    {

    }
}

To wszystko? Tak, a właściwie prawie:). Metoda HandleError to miejsce logowania błędu. Ale to jednak za mało gdyż jak się pewnie domyślacie teraz trzeba jakoś poinformować WCF-a, żeby użył naszej klasy CustomErrorHandler. Z pomocą przychodzi sam WCF, a mianowicie mechanizm rozszerzeń WCF-a, konkretniej mechanizm rozszerzeń zachowania serwisu BehaviorExtensionElement. A zatem:

public class CustomErrorHandlerExtension : BehaviorExtensionElement, IServiceBehavior
{
    public override Type BehaviorType
    {
        get { return GetType(); }
    }

    protected override object CreateBehavior()
    {
        return this;
    }

    private IErrorHandler GetInstance()
    {
        return new CustomErrorHandler();
    }

    void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
    {
    }

    void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        IErrorHandler errorHandlerInstance = GetInstance();
        foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
        {
            dispatcher.ErrorHandlers.Add(errorHandlerInstance);
        }
    }

    void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {

    }
}

Dwa najistotniejsze elementy powyższego kodu został wyróżnione, tj. instancjonowanie naszego CustomErrorHandler oraz zarejestrowanie go.

Czy teraz to już wszystko? Prawie :). Teraz przenosimy się do pliku konfiguracyjnego naszego serwera (w wypadu self-hosting to będzie pewnie usługa Windows, a w wypadku hostowania na IIS to jakaś aplikacja Web).

W ramach <system.serviceModel> dodajemy:

<extensions>
 <behaviorExtensions>
   <add name="customErrorHandler" type="TwojaAplikacja.CustomErrorHandlerExtension, TwojeAssembly" />
 </behaviorExtensions>
</extensions>

Rozszerzyliśmy WCF o nasz error handler, teraz pozostaje tylko go użyć. W ramach sekcji behavior naszego serwisu dodajemy <customErrorHandler/>.

Teraz to już naprawdę wszystko i działa!

17 stycznia 2014

Temporal data – czyli w jakim departamencie Pani pracowała w maju 2008?

Dziś chciałbym spojrzeć na dane w nieco innym świetle niż robimy to na co dzień. Nie ważne czy tworzymy duży rozproszony system czy prostą aplikację klient-serwer zawsze pojawia się kontekst danych.  Podstawowym repozytorium danych we współczesnych aplikacjach, szczególnie biznesowych, są relacyjne bazy danych. Jak zwykle postrzegamy te dane? Jak o nich myślimy?

W klasycznym podejściu nie rozpatrujemy danych w kontekście czasu. Załóżmy, że mamy tabelę przechowującą pracowników. Wiemy, że dane pracownika X mogą się zmieniać na przestrzeni tygodni, miesięcy, lat…Co w sytuacji gdy chcielibyśmy nagle cofnąć się w czasie do konkretnego punktu z przeszłości i obejrzeć naszego pracownika? Hmm … no nic! W klasycznym systemie nie możemy czegoś takiego zrobić, ponieważ dane nie posiadają pojęcia czasu. Inaczej mówiąc system przechowuje bieżące dane, które są takie same w każdym punkcie czasu.

Co zatem z tym zrobić? Tutaj pojawia się pojęcie: temporal database. To baza danych w której dane posiadają kontekst czasu. Mówię tutaj o bazach danych, ale pojęcie „czasowości” danych należy postrzegać znacznie szerzej. Na wyższym poziomie abstrakcji także możemy wprowadzić czasowość, tj. np. w obiektach naszego modelu domeny.

Aspekt „czasowości” danych możemy rozpatrywać w co najmniej na dwa sposoby, a zależy to od wymagań naszego systemu. Mianowicie nasze dane mogą posiadać atrybut ValidTime, który wprowadza kontekst czasu w sensie obowiązywania danych w świecie rzeczywistym. Inaczej mówiąc określa on w jakim czasie konkretna wersja naszych danych jest prawdziwa z punktu widzenia świata rzeczywistego.  Drugi sposób to wprowadzenie atrybutu TransactionTime, który również określa dane w czasie, ale interpretacja jest tu nieco inna. To podejście ma zastosowanie gdy interesuje nas raczej czas rejestracji danych w systemie, ale nie moment zaistnienia jakiegoś faktu w rzeczywistym świecie. Aby to nieco rozjaśnić, to musimy sobie uzmysłowić, że fakt zarejestrowania danych w systemie mógł nastąpić później niż faktyczna zmiana tych danych w realnym świecie. Stąd podział na takie dwa przypadki.

Istnieje jeszcze trzeci sposób będący kombinacją dwóch poprzednich nazywany bitemporal.

Wprowadźmy oś czasu i rozpatrzmy przykład prostego obiektu Pracownik posiadającego dwa pola: ImieNazwisko, Departament.

2004-08-01: Id = 1, ImieNazwisko = „Janina Nowak”, Departament = „PR”

2005-11-15: Id = 1, ImieNazwisko = „Janina Nowak”, Departament = „HR”

2009-08-08: Id = 1, ImieNazwisko = „Janina Kowalska”, Departament  = „HR”

2013-01-01: Id = 1, ImieNazwisko = „Janina Kowalska”, Departament  = „IT”

Teraz naszego pracownika możemy rozpatrywać w kontekście czasu. Załóżmy, że powyższe daty odnoszą się do świata rzeczywistego, a nie momentu zarejestrowania tych faktów w systemie. Wiemy że nasz byt rozpoczął pracę w 2004 roku w departamencie PR i wiemy w jakim departamencie pracował w maju 2008. Dodatkowo wiemy, że nasz pracownik, a raczej pracowniczka w międzyczasie zmieniła nazwisko.

Wprowadziliśmy dodatkowy atrybut ValidTime określający czas od jakiego obowiązuje konkretna wersja danych tego samego pracownika.

Zastanówmy się zatem jak przechowywać tego typu dane w relacyjnej bazie danych. Sposobów jest kilka, ale niewątpliwie korzystne wydaje się być wprowadzenie okresu czasu. Dzięki okresowi czasu tj. atrybutom ValidTimeFrom i ValidTimeTo możemy wydajniej i prościej wyszukiwać dane w naszym systemie. Ciężar wtedy przenosi się na zapis danych. I tak np. nasz pracownik będzie wyglądał teraz tak:

ValidTimeFrom = 2004-08-01, ValidTimeTo =   2005-11-14, Id = 1, ImieNazwisko = „Janina Nowak”, Departament = „PR”

ValidTimeFrom = 2005-11-15, ValidTimeTo =  2009-08-07, Id = 1, ImieNazwisko = „Janina Nowak”, Departament = „HR”

ValidTimeFrom = 2009-08-08, ValidTimeTo =  2012-12-31, Id = 1, ImieNazwisko = „Janina Kowalska”, Departament = „HR”

ValidTimeFrom = 2013-01-01, ValidTimeTo =  NULL, Id = 1, ImieNazwisko = „Janina Kowalska”, Departament = „IT”

Kluczem identyfikującym nasz obiekt (jego konkretną wersję w czasie) jest jego Id i ValidTimeFrom. Pole ValidTimeTo jest tylko polem pomocniczym ułatwiającym dostęp do danych.

Łatwo możemy pobrać dane obowiązujące w konkretnym punkcie czasu:

SELECT ImieNazwisko, Departament FROM Pracownicy WHERE Id = 1 AND ‚2008-05-01’ BETWEEN ValidTimeFrom AND ValidTimeTo

Jak widać zostało tu poczynione założenie, że wszystkie dane z całego zakresu czasu znajdują się w jednej tabeli. Innym rozwiązaniem jest przechowywanie danych bieżących w jednej tabeli, a danych archiwalnych w drugiej np. Pracownicy_Archiwum. Ten drugi wariant jednakże ma sens w sytuacji używania TransactionTime w naszym systemie. Mianowicie, gdy nasz system ma obowiązek rejestrowania historii zmian poczynionych na danych, ale sięga do nich sporadycznie – wybierzmy wariant nr 2.

A co z gotowymi rozwiązaniami?

Co prawda pojęcie temporal tables pojawiło się w standardzie SQL:2011, ale obecnie jeszcze niewiele baz danych obsługuje tą funkcjonalność. Jeśli używacie MS SQL Server – zapomnijcie! Jedną z ciekawszych implementacji, moim zdaniem, proponuje IBM w DB2 od wersji 10.

A co z modelem domeny?

Świat zareagował oczywiście na potrzeby odwzorowania danych temporal także w modelu obiektowym. Powstało kilka wzorców projektowych odnoszących się mniej lub bardziej do tego zagadnienia. Sam Martin Fowler zaangażował się w ten temat i omawia kilka z nich tutaj.

Słowem podsumowania dodam, że póki co programiści związani z technologiami Microsoft (.NET, SQL Server) muszą pokusić się o własne implementacje.

Moim celem nie było dostarczenie konkretnego i uniwersalnego rozwiązania, bo takie pewnie nie istnieje, a raczej nakreślenie problemu i zachęcenie do zgłębienia wiedzy na ten temat.

Blog na WordPress.com.