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;

Reklamy

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.

5 Maj 2013

XPO jak Yeti

Filed under: How Can I Take ... — Tagi: , , — Beniamin Zaborski @ 18:50

XPO czyli eXpressPersistent Objects to komercyjny ORM firmy DevExpress. W naszym kraju to nieco egzotyczny produkt i jest z nim trochę jak z mitycznym Yeti – wszyscy słyszeli, widziało niewielu. Z czego wynika ta niezbyt duża popularność tego ORM-a? Czy winne jest temu to, że trzeba za niego płacić? Jakie ma wady, a jakie są jego zalety? Na te pytania jak i na kilka innych postaram się odpowiedzieć w tym wpisie.

Chciałbym zaznaczyć, że nie jestem ani nie byłem w jakikolwiek sposób związany z firmą DevExpress. Postaram się dość obiektywnie, ale jednak w jakimś stopniu przez pryzmat mojego doświadczenia, bliżej przyjrzeć XPO. Dzięki ukończeniu projektu wykorzystującego XPO jak i dzięki moim wcześniejszym doświadczeniom z innymi ORM-ami jak NHibernate czy Entity Framework, mogę spojrzeć na XPO z dużo szerzej perspektywy.

Swego czasu popełniłem wpis na blogu dotyczący Entity Framework. Chciałbym się odnieść właśnie do tego wpisu, co umożliwi w jakimś stopniu porównanie XPO z Entity Framework. Użyję dokładnie tego samego modelu klas, który wykorzystałem we wspomnianym wpisie. Oto on w postaci diagramu klas UML:

Obrazek

Model klas to ta część która z punktu biznesowego powinna nas interesować najbardziej. Sytuacja idealna to taka w której model nie ma świadomości o istnieniu ORM-a. Niestety jak się okazuje nie każdy ORM daje nam to o czym marzymy. Do tego tematu jeszcze wrócimy w dalszej części.

Z przedstawionego modelu klas możemy wywnioskować, że będziemy musieli uporać się z mapowaniem: typów prostych jak string czy bool, typów referencyjnych, relacji one-to-many czy many-to-many. Celowo pominąłem kwestię mapowania dziedziczenia, gdyż we wpisie do którego się odnoszę również nie było o tym mowy.

Zanim przystąpimy do mapowania klas na tabele bazy danych trzeba wspomnieć o dwóch głównych koncepcjach obsługi operacji CRUD jakie daje nam XPO. Pierwsza koncepcja zakłada, że klasy encji biznesowych będą dziedziczyć po specjalnych klasach bazowych XPO. W tym momencie włącza się nam czerwona lampka … grhhh. Z powodów oczywistych takie podejście odrzucamy już na wstępie. Pisałem o tym kiedyś w tym wpisie.
Na szczęście (to się dopiero okaże) dokumentacja wspomina także o odmiennym podejściu zwanym session-less. Dzięki temu nasze encje biznesowe pozostaną POCO. To cieszy i jest na plus dla XPO. Oznaczać to będzie także, że wykonywanie operacji CRUD na obiektach będzie się odbywało za pomocą obiektu sesji, a nie jak w pierwszej koncepcji za pomocą dedykowanych do tego celu metod wewnątrz obiektów. No wszystko logiczne – przecież dokładnie tego oczekiwaliśmy.

Pora najwyższa zabrać się wreszcie za mapowanie klas na tabele bazy danych. Jedynym oficjalnym sposobem mapowania są atrybuty. Oczywiście zaletą takiego rozwiązania jest wygoda. Bezwzględnie wadą w takim podejściu jest „zabrudzenie” modelu „obcymi” atrybutami. Trochę żal że producent nie zdecydował się na dostarczenie alternatywnej i „czystszej” metody mapowania. Konkurencja w postaci twórców NHibernate czy Entity Framework przemyślała to zdecydowanie bardziej.
Kolejna wada XPO, chociaż nie jest to typowa przypadłość tego ORM-a, to potrzeba odwzorowywania kolekcji poprzez specjalny typ dostarczany przez XPO czyli XPCollection. W tej kwestii Entity Framework wymiata! Mając to na uwadze już wiemy, że będziemy musieli zmodyfikować nasz model klas zastępując typy wszystkich kolekcji IList typem XPCollection.

Aby poinformować XPO o tym iż dana klasa ma być mapowana na tabelę bazy danych należy oznaczyć ją atrybutem Persistent z opcjonalnym parametrem określającym nazwę tabeli w bazie danych. Dokładnie ten sam atrybut można zaaplikować dowolnej właściwości podając opcjonalnie nazwę kolumny tabeli. Mapowanie właściwości typów prostych jak i referencyjnych jest banalne. A co z kolekcjami? Tu zaczynają się schody. Oczywiście pomijając to, że wszystkie nasze kolekcje muszą być typu XPCollection (to już zaakceptowaliśmy – czyżby?), to musimy te kolekcje jeszcze zainicjować. Na nieszczęście potrzebny do tego będzie nam obiekt sesji. Hmmm … zatem stwierdzenie session-less wydaje się być trochę na wyrost. Niestety tak jest i to boli!

Poniżej przedstawiam kompletny model klas dostosowany do wymogów XPO wraz z pełnym mapowaniem:

    [Persistent(„Komputery”)]
    public class Komputer : BusinessEntity
    {
        public Komputer() : base() { }
        public Komputer(Session session) : base(session) { }
        [Persistent(„Nazwa”), Size(120)]
        public string Nazwa { get; set; }
        [Persistent(„AdresIP”), Size(14)]
        public string AdresIP { get; set; }
        [Association(„Uzytkownik-Komputery”)]
        public Uzytkownik Uzytkownik { get; set; }
    }
    [Persistent(„Role”)]
    public class Rola : BusinessEntity
    {
        public Rola() : base() { }
        public Rola(Session session) : base(session)
        {
            this.uprawnienia = new XPCollection<Uprawnienie>(session, this, session.GetClassInfo<Rola>().GetMember(„Uprawnienia”));
        }
        [Persistent(„Nazwa”), Size(120)]
        public string Nazwa { get; set; }
        private XPCollection<Uprawnienie> uprawnienia;
        [Association(„Role-Uprawnienia”)]
        public XPCollection<Uprawnienie> Uprawnienia
        {
            get { return uprawnienia; }
        }
    }
    [Persistent(„Uprawnienia”)]
    public class Uprawnienie : BusinessEntity
    {
        public Uprawnienie() : base() { }
        public Uprawnienie(Session session) : base(session)
        {
            this.role = new XPCollection<Rola>(session, this, session.GetClassInfo<Uprawnienie>().GetMember(„Role”));
        }
        [Persistent(„Nazwa”), Size(120)]
        public string Nazwa { get; set; }
        private XPCollection<Rola> role;
        [Association(„Role-Uprawnienia”)]
        public XPCollection<Rola> Role
        {
            get { return role; }
        }
    }
    [Persistent(„Uzytkownicy”)]
    public class Uzytkownik : BusinessEntity
    {
        public Uzytkownik() : base() { }
        public Uzytkownik(Session session) : base(session)
        {
            this.komputery = new XPCollection<Komputer>(session, this, session.GetClassInfo<Uzytkownik>().GetMember(„Komputery”));
        }
        [Persistent(„Login”), Size(120)]
        public string Login { get; set; }
        [Persistent(„CzyAktywny”)]
        public bool CzyAktywny { get; set; }
        [Persistent(„Rola”)]
        public Rola Rola { get; set; }
        private XPCollection<Komputer> komputery;
        [Association(„Uzytkownik-Komputery”), Aggregated]
        [Delayed]
        public XPCollection<Komputer> Komputery
        {
            get { return komputery; }
        }
    }

Na powyższym listingu można dostrzec coś o czym jak dotąd nie wspomniałem – atrybut Association. Jest to atrybut który musi być zaaplikowany na obu końcach relacji one-to-many oraz many-to-many. Mało tego, gdy chcemy dodać kolekcję elementów pewnego typu do jednej z klas zostaniemy zmuszeni do dodania do klasy będącej elementem tej kolekcji wskazania na obiekt zawierający tą kolekcję. Przykładem może być właściwość Uzytkownik w klasie Komputer, której tak naprawdę nie chciałem tam mieć. Nie wygląda to zbyt dobrze, gdyż płacimy często dość wysoką cenę za dostosowywanie modelu do wymagów XPO. Mnie osobiście nie łatwo się z tym pogodzić i zaliczam to do jednych z najpoważniejszych ograniczeń XPO.

Wspomnieć trzeba jeszcze o atrybucie Aggregated, który jest w pewnym stopniu odpowiednikiem właściwości cascade z NHibernate. Nałożenie tego atrybut na właściwość typu referencyjnego lub kolekcję oznaczać będzie, że obiekty te będą zapisywane wraz z zapisem obiektu głównego. W takim wypadku nie będzie wymagany zapis np. poszczególnych elementów kolekcji.

XPO obsługuje także mechanizm lazy-loading, czyli ładowanie właściwości na żądanie przy pierwszym dostępnie do nich. Zachowanie XPO w tym względzie jest jednak zupełnie odmienne od tego które znamy z Entity Framework. W XPO domyślnie lazy-loding jest wyłączony i aby wskazać, że dana właściwość ma być doładowywana na żądanie należy oznaczyć ją atrybutem Delayed.

Bardzo bogato wygląda natomiast pobieranie danych w XPO. Mamy tu do dyspozycji tzw. operatory kryteriów, LINQ czy nawet zapytania SQL. Mnie najbardziej chyba ucieszył mechanizm Linq-to-XPO, chociaż muszę przyznać, że operatory kryteriów – po opanowaniu ich – także dają spore możliwości.

Aby rozpocząć manipulowanie obiektami trzeba zainicjować wspominaną już tutaj niejednokrotnie sesję. Sposobów na to jest kilka, ale jednym z prostszych jest utworzenie DataLayer, a następnie przekazanie go do konstruktora sesji, jak poniżej:

IDataLayer dataLayer = XpoDefault.GetDataLayer(connectionString, AutoCreateOption.DatabaseAndSchema);
Session session = new Session(dataLayer);

Nie napisałem dotąd nic o tworzeniu samej bazy danych oraz tworzeniu obiektów wewnątrz niej jak tabele, klucze główne, klucze obce, itp. Podczas tworzenia obiektu dataLayer, XPO pozwala nam określić jego zachowanie w kwestii automatycznego tworzenia obiektów w bazie danych.

Zachowanie XPO wymuszamy z pomocą typu wyliczeniowego AutoCreateOption:

DatabaseAndSchema – tworzy bazę danych oraz pełny schemat bazy nawet z kluczami obcymi

None – nie robi nic, tzn. nie tworzy automatycznie bazy danych, ani żadnych obiektów w niej

SchemaOnly – tworzy tylko schemat w bazie danych, ale tylko jeśli baza istnieje

SchemaAlreadyExists – nie weryfikuje schematu bazy pod kątem zgodności z aktualnym stanem obiektów

W sytuacji gdy całą pracę związaną z tworzeniem schematu bazy danych przerzucimy na XPO to warto zaznaczyć, że XPO całkiem sprawnie radzi sobie z modyfikacjami schematu na podstawie wprowadzonych zmian w modelu klas.

Pełny projekt prostej aplikacji konsolowej na której bazuje ten opis wykonanej w C# dla VS 2012 można pobrać stąd.

Podsumowując moje odczucia co do XPO to na pewno do zalet zaliczył bym prostotę tworzenia mapowania poprzez atrybuty, mnogość sposobów pobierania danych oraz dobry mechanizm tworzenia i aktualizacji schematu bazy danych. Główne wady to obsługa kolekcji i podejście session-less (trochę jakby oszukane).

Względnie nieduża popularność XPO jako ORM-a wynikać może właśnie z tych kilku ale jednak znaczących wad. Poza tym odnoszę wrażenie, że producent traktuje XPO jako dodatek do całej reszty swoich produktów, a nie główny produkt który chce sprzedawać.

Po bliższym zapoznaniu się z XPO sami musicie zdecydować czy jest to ORM na którym możecie oprzeć Wasz następny projekt. Napewno jest warty zainteresowania, ale być może sprawdzi się tylko w pewnej specyficznej grupie projektów.

3 kwietnia 2012

Chcesz być Git?

Filed under: Codzienne dylematy modelarza — Tagi: , — Beniamin Zaborski @ 20:06

W tym poście chciałbym odnieść się do jakże popularnego ostatnio tematu rozproszonych systemów kontroli wersji. Jedne z najczęściej dziś wymienianych to oczywiście tytułowy Git, ale także Mercurial czy Bazaar. Na wstępie zaznaczę że to nie będzie typowy tutorial o Git-cie, a raczej moje luźne przemyślenia na jego temat.

Jako developer wychowany na scentralizowanych systemach kontroli wersji głównie SourceSafe czy SVN, nauczyłem się że nie należy od nich wymagać wiele (szczególnie od tego pierwszego). Stawiając poprzeczkę odpowiednio nisko wszystko było w porządku. Checkout działa, checkin działa – pełna radość!

Pomimo ogólnie panującego przekonania, że SourceSafe nie jest git udawało się jakoś z nim współpracować nawet z obszernymi projektami w kilkunastoosobowych zespołach. Wszystko się zmienia po dokładniejszym poznaniu Git-a. Jego możliwości wręcz oszałamiają, a do tego wszystkiego to po prostu działa.

Po pierwsze należy przyswoić to, że Git to system rozproszony. Co to właściwie oznacza? Mianowicie tyle, że nie ma jednego centralnego repozytorium, a jest ich wiele, dowolnie wiele… W praktyce oczywiście pewnie jedno z repozytoriów będzie pełnić rolę takiego „centralnego”, ale nikt na nim nie będzie bezpośrednio pracował. Być może będzie z niego pobierał źródła jakiś system continous integration aby wykonać builda wersji.

Idea „rozproszoności” opiera się na tym, że każdy z developerów przed przystąpieniem pracy tworzy sobie lokalnie kopię repozytorium. Uwaga jest to kopia zawierająca pełną historię. Ta operacja tworzenia lokalnej kopii według nomenklatury Git nazywana jest klonowaniem (clone). Następnie praca odbywa się na lokalnym repozytorium, łącznie z commitami które są robione również lokalnie. Przeniesienie zmian z repozytoriów lokalnych do „centralnego” wykonujemy za pomocą push. Typowy cykl pracy mógłby wyglądać tak:

1) Utworzenie repozytorium „centralnego”

git init

2) Utworzenie kopii lokalnych repozytoriów

git clone

3) Praca z plikami w repozytoriach lokalnych

git add .

4) Zatwierdzanie zmian w repozytoriach lokalnych

git commit

5) Przeniesienie zmian do repozytorium „centralnego”

git push

6) goto 3

Jednym z założeń autora Git-a tj. Linusa Torvaldsa było to aby ten system był szybki. Rzeczywiście jest. Co prawda wersja Windowsowa, która działa na emulowanym środowisku POSIX Cygwin jest nieco wolniejsza od tej *nix-owej, ale nie można jej niczego zarzucić pod kątem wydajnościowym.

Git-a klasycznie obsługujemy z linii komend. Wiem, że teraz wielu z Was powie, że to jakieś nieporozumienie. Moja pierwsza reakcja była podobna – ech co się ze mną dawnym *nix-owcem stało:). Uwierzcie mi, że obsługa Git-a nawet za pomocą linii komend jest naprawdę przyjemna! Dosyć intensywnie rozwijają się narzędzia do współpracy z Git-em. Już dziś mamy dobrze rozwinięty i stabilny TortoiseGit, który integruje się z powłoką Windows. Poza tym są dodatki do Visual Studio pozwalające cieszyć się Git-em w tym środowisku. Tu należy zwrócić uwagę na GitExtensions oraz Git Source Control Provider. Oba te dodatki może nie powalają swoim wyglądem, ale nadrabiają na pewno funkcjonalnością.

Nie wspomniałem jeszcze o wyśmienicie działającym tworzeniu rozgałęzień oraz ich łączeniu. To ogromna zaleta Git-a. Teraz już nie trzeba się tego bać. Można śmiało:

1) Utworzyć nową gałąź

git branch nazwa_gałęzi

2) Pracować z plikami

git add .

3) Zatwierdzać zmiany

git commit

4) Przełączać się między rozgałęzieniami

git branch master

5) Łączyć rozgałęzienia

git merge nazwa_gałęzi

 

Repozytorium „centralne” może znajdować się gdzieś w systemie plików na zdalnie udostępnionym zasobie, albo można do tych celów zainstalować dedykowany serwer. Jest ich kilka, obsługujących własny protokół git. Patrząc z punktu widzenia developera w systemach Windows ciekawe rozwiązanie jako serwer Git to Bonobo Git Server. To aplikacja ASP.NET MVC 3 hostowana na IIS. Instalacja jest szybka i bezproblemowa a do tego wygodny i prosty interfejs Web do zarządzania repozytoriami, użytkownikami, itp.

Polecam! Bądź Git!

30 Maj 2011

Entity Framework code-first – pierwsze koty za płoty

W ostatnim czasie pojawiło się kilka zmian w Entity Framework obok których nie sposób przejść obojętnie. Od dawna przyglądam się rozwojowi tego O/R mappera, ale jak dotąd nigdy nie byłem jeszcze tak podekscytowany jak teraz. Co takiego wywołuje ową ekscytację? Code-first! Tak to jest to. Z pełną świadomością mogę powiedzieć, że dla mnie code-first = Entity Framework. Wszystko co jest poza tym w EF już może nie istnieć. Ale postarajmy się to nieco uporządkować.

Entity Framework dostarcza trzy różne podejścia do odwzorowywania encji modelu domeny na relacyjne obiekty bazy danych:

– database-first: zaczynamy od utworzenia bazy danych, a EF automatycznie generuje klasy modelu na podstawie struktury bazy danych

– model-first: zaczynamy od utworzenia klas modelu w dedykowanym designerze i mapujemy je na istniejącą bazę danych lub EF generuje bazę na podstawie stworzonego modelu

– code-first: zaczynamy od utworzenia klas modelu wprost w kodzie i mapujemy na obiekty bazy danych.

W dalszej części zamierzam skupić się na tym ostatnim podejściu. Według mojego może odrobinę subiektywnego odczucia jest to jedyny wartościowy sposób użycia EF. Kontrowersyjne? Otóż nie do końca. Patrząc przez pryzmat programisty używającego przez kilka lat NHibernate (ostrzegałem, że może nie być obiektywnie) takie podejście wydaje się być zupełnie naturalne.

Rozpoczynam od utworzenia obiektów modelu domeny – obiektów POCO rzecz jasna – nie myśląc na tym etapie o projektowaniu relacji bazy danych (Domain Driven Development). W następnej kolejności tworzę odpowiednią strukturę bazy danych i mapuję na nią klasy modelu. Entity Framework potrafi sam wygenerować strukturę bazy danych na podstawie klas modelu stosując analogiczne nazewnictwo do nazw klas i ich właściwości. Wygodne rozwiązanie, jednak życie pokazuje, że mało praktyczne szczególnie w dużych projektach. Każdy z nas ma jakieś przyzwyczajenia co do nazewnictwa obiektów w bazie danych – ja również i one się nie pokrywają z konwencjami nazewniczymi dostarczanymi przez EF. Przyzwyczajenia owszem można zmienić, co jednak gdy korporacja posiada ścisłe reguły co do nazywania obiektów w bazie danych? Głową muru się nie przebije, stąd potrzeba elastycznego mechanizmu konfiguracji mapowania. Na początku się obawiałem na jak wiele pozwoli nam tutaj Microsoft. Niepotrzebnie, bo za pomocą fluent API można naprawdę dużo. Nie jest to co prawda tak dużo jak oferuje NHibernate, ale nie powinno to wpłynąć na wygodę używania Entity Framework Code-First.

Korzystając z podejścia code-first mamy trzy sposoby na mapowanie obiektowo-relacyjne. Po pierwsze, wspomniane już, automatyczne generowanie struktury bazy danych; po drugie mapowanie przy pomocy atrybutów z przestrzeni nazw System.Data.Annotations; i po trzecie preferowane przeze mnie ze względu na swoje możliwości – fluent API.

Nie uważam, aby mapowanie przy pomocy atrybutów było złym rozwiązaniem, po prostu fluent API wydaje mi się lepsze. Dzięki temu mogę odseparować model od bazy danych i stworzyć go bardziej „czystym”. Pod tym względem zdecydowanie jestem purystą.

Przeanalizujmy sposób użycia fluent API na podstawie projektu prostej aplikacji konsolowej stworzonej w Visual Studio, którą można pobrać stąd. Trywialny model domeny (mocno anemiczny) posiada jedynie cztery klasy. Nie w tym rzecz jednak. Różne relacje między tymi klasami obrazują to co najważniejsze w kontekście konfiguracji mappingu. Projekt pokazuje jak zmapować relacje między obiektami typu: many-to-one (Uzytkownik->Rola), one-to-many (Uzytkownik->Komputer) czy many-to-many (Rola->Uprawnienie).

Przejdźmy do tworzenia wspomnianego modelu domeny, dodając do projektu cztery następujące klasy:

public class Komputer

{

public long? Id { get; set; }

public string Nazwa { get; set; }

public string AdresIP { get; set; }

}

public class Uprawnienie

{

public long? Id { get; set; }

public string Nazwa { get; set; }

public IList<Rola> Role { get; set; }

}

public class Rola

{

public long? Id { get; set; }

public string Nazwa { get; set; }

public IList<Uprawnienie> Uprawnienia { get; set; }

}

public class Uzytkownik

{

public long? Id { get; set; }

public string Login { get; set; }

public bool CzyAktywny { get; set; }

public Rola Rola { get; set; }

public IList<Komputer> Komputery { get; set; }

}

Kolejnym krokiem jest wygenerowanie stosownej struktury tabel w bazie danych zgodnie z przyjętą przez siebie/organizację konwencją. Pomijam opis tego procesu, bo może być wykonany na wiele sposobów. Począwszy od „ręcznego” utworzenia tabel, aż po skorzystanie z własnych dedykowanych do tego narzędzi. Niejednokrotnie jest tak, że otrzymujemy już gotową strukturę bazy danych od programisty baz danych. W załączonym projekcie znajduje się skrypt SQL tworzący wymaganą strukturę bazy danych (dla MS SQL Server).

Mamy model domeny w postaci klas, mamy strukturę bazy danych – czas przejść do mapowania. Zanim jednak to zrobimy, dodajmy jeszcze connection string do naszej bazy danych do pliku konfiguracyjnego aplikacji app.config:

<connectionStrings>

<clear/>

<addname=ConnStr

connectionString=Data Source=(local);Initial Catalog=EF_CODE_FIRST_SAMPLE_DB;Integrated Security=SSPI;

providerName=System.Data.SqlClient />

</connectionStrings>

Kluczowym elementem aplikacji z wykorzystaniem Entity Framework code-first jest klasa DbContext z przestrzeni nazw System.Data.Entity. Pełni ona rolę wzorca Repository, a także jednocześnie Unit Of Work. To dzięki tej klasie możemy wykonywać na encjach modelu wszelkie operacie CRUD w ramach transakcji. Oznacza to, że naszym kolejnym krokiem jest utworzenie właśnie klasy dziedziczącej po DbContext. Zróbmy to:

public class ModelContext : DbContext

{

public ModelContext() : base() { }

public ModelContext(string stringNameOrConnectionString) : base(stringNameOrConnectionString) { }

}

Użyłem tu przeciążenia konstruktora, które pozwoli nam podać nazwę connection stringa z pliku konfiguracyjnego lub wprost podać connection string do bazy danych.

Kolejna bardzo istotna klasa, funkcjonująca nierozerwalnie z DbContext, to DbSet<>. Reprezentuje kolekcję encji danego typu w ramach kontekstu jaki definiuje nam klasa DbContext. Dodajmy zatem stosowne kolekcje encji do klasy ModelContext:

public DbSet<Rola> Role { get; set; }

public DbSet<Uzytkownik> Uzytkownicy { get; set; }

W tym momencie możemy przejść już do samego mapowania z użyciem fluent API. W związku z tym w klasie ModelContext musimy przesłonić metodę OnModelCreating:

protected override void OnModelCreating(DbModelBuilder modelBuilder)

{

base.OnModelCreating(modelBuilder);

}

To jest właśnie to miejsce w którym definiujemy konfigurację mappingu. Możemy to zrobić wprost w tej metodzie lub lepszym pomysłem będzie wydzielenie mappingu poszczególnych encji do dedykowanych klas. Sprawdza się to szczególnie w większych projektach. W tym celu należy utworzyć klasę dziedziczącą po EntityTypeConfiguration<> dla każdej encji:

public class KomputerMapping : EntityTypeConfiguration<Komputer>

{

public KomputerMapping()

: base()

{

this.HasKey(e => e.Id).Property(e => e.Id).HasColumnName(„ID_KOMPUTERA”);

this.Property(e => e.Nazwa).HasColumnName(„NAZWA”);

this.Property(e => e.AdresIP).HasColumnName(„ADRES_IP”);

this.ToTable(„KOMPUTERY”);

}

}

public class UprawnienieMapping : EntityTypeConfiguration<Uprawnienie>

{

public UprawnienieMapping()

: base()

{

this.HasKey(e => e.Id).Property(e => e.Id).HasColumnName(„ID_UPRAWNIENIA”);

this.Property(e => e.Nazwa).HasColumnName(„NAZWA”);

this.ToTable(„UPRAWNIENIA”);

}

}

public class RolaMapping : EntityTypeConfiguration<Rola>

{

public RolaMapping()

: base()

{

this.HasKey(e => e.Id).Property(e => e.Id).HasColumnName(„ID_ROLI”);

this.Property(e => e.Nazwa).HasColumnName(„NAZWA”);

this.HasMany<Uprawnienie>(u => u.Uprawnienia).WithMany(u => u.Role).Map(

m => m.ToTable(„UPRAWNIENIA_DLA_ROLI”)

.MapLeftKey(„ID_ROLI”)

.MapRightKey(„ID_UPRAWNIENIA”)

);

this.ToTable(„ROLE”);

}

}

public class UzytkownikMapping : EntityTypeConfiguration<Uzytkownik>

{

public UzytkownikMapping()

: base()

{

this.HasKey(e => e.Id).Property(e => e.Id).HasColumnName(„ID_UZYTKOWNIKA”);

this.Property(e => e.Login).HasColumnName(„LOGIN”);

this.Property(e => e.CzyAktywny).HasColumnName(„CZY_AKTYWNY”);

this.HasRequired(e => e.Rola).WithMany().Map(p=>p.MapKey(„ID_ROLI”));

this.HasMany(e => e.Komputery).WithOptional().Map(p => p.MapKey(„ID_UZYTKOWNIKA”)).WillCascadeOnDelete();

this.ToTable(„UZYTKOWNICY”);

}

}

To jest kompletny mapping dla naszego modelu domeny. Przyjrzyjmy się kluczowym elementom. Wywołanie HasKey() wskazuje które property jest primary key w naszej encji, natomiast Property() umożliwia zmapowanie konkretnej właściwości na konkretną kolumnę w tabeli bazy danych. Użyłem wywołania HasColumnName(), aby wskazać inną nazwę kolumny w tabeli – nieodpowiadającą konwencji nazewniczej EF. To wszystko okraszone jest wywołaniem ToTable(), gdzie wskazujemy nazwę tabeli odpowiadającą encji. Mapowanie klucza głównego i właściwości typów prostych wygląda na niezbyt skomplikowane. Czas przejść do mapowania relacji między obiektami.

Na pierwszy ogień many-to-one czyli zwykła referencja do innego typu encji z naszego modelu. Dobrym przykładem jest powiązanie użytkownika z rolą. Klasa Uzytkownik posiada referencję do klasy Rola, przy czym jest to referencja wymagana. Mapujemy to za pomocą wywołania HasRequired() wskazując nazwę właściwości przechowującej referencję do klasy Rola. W związku z tym, że także w tym wypadku konwencja nazewnicza klucza obcego w tabeli nie odpowiada tej z EF należy wskazać nazwę. Robimy to przy pomocy wywołania MapKey() podając nazwę klucza obcego w tabeli. Kompletny przykład mapowania takiej relacji wygląda następująco:

this.HasRequired(e => e.Rola).WithMany().Map(p=>p.MapKey(„ID_ROLI”));

W sytuacji kiedy property przechowujące referencję nie jest wymagane, to zamiast HasRequired() użyjemy HasOptional().

Innym typem relacji jest kolekcja czyli one-to-many. Przeanalizujmy to na przykładzie kolekcji elementów Komputer w klasie Uzytkownik. Do zmapowania listy używamy wywołania HasMany() podając jako parametr property – za pomocą wyrażenia lambda oczywiście – które wskazuje naszą kolekcję. W związku z tym, iż ta właściwość nie jest wymagana używamy WithOptional(). Jak się wszyscy domyślają w przeciwnym wypadku użylibyśmy WithRequired(). Wywołanie Map() z MapKey() wygląda już znajomo i używamy go by wskazać klucz obcy w tabeli zawierającej rekordy będące elementami listy. Dodatkowo na końcu znajduje się wywołanie metody WillCascadeOnDelete(). Cóż to takiego? Nic innego jak wskazanie czy podczas usunięcia elementu nadrzędnego ma być wywołane kaskadowe usunięcie elementów podrzędnych, czyli tych w naszej kolekcji. Cały przykład wygląda następująco:

this.HasMany(e => e.Komputery).WithOptional().Map(p => p.MapKey(„ID_UZYTKOWNIKA”)).WillCascadeOnDelete();

Na koniec najbardziej skomplikowana relacja czyli many-to-many. Przykładem takiego powiązania jest wzajemna relacja między obiektami Rola i Uprawnienie. Rola posiada kolekcję elementów Uprawnienie i vice versa, tj. Uprawnienie posiada kolekcję elementów Rola. Aby zmapować takie powiązanie na postać relacyjną potrzebujemy dodatkowej tabeli. Ta tabela przechowuje pary identyfikatorów roli i uprawnienia. Dobrze, aby kluczem głównym takiej tabeli była owa para identyfikatorów. Mapowania wykonujemy z jednej ze stron np. z encji Rola. Spójrzmy na przykład:

this.HasMany<Uprawnienie>(u => u.Uprawnienia).WithMany(u => u.Role).Map(

m => m.ToTable(„UPRAWNIENIA_DLA_ROLI”)

.MapLeftKey(„ID_ROLI”)

.MapRightKey(„ID_UPRAWNIENIA”)

);

Wywołujemy generyczną wersję metody HasMany, podając jako parametr generyczny typ elementu kolekcji, a jako parametr wywołania właściwość klasy przechowującą kolekcję tych elementów. Następnie w wywołaniu WithMany podajemy wskazanie w drugą stronę tj. w tym przypadku na kolekcję elementów Rola. Ostatni element mapowania relacji wiele do wielu i najważniejszy to wskazanie wspomnianej tabeli powiązania w Map(). Nazwę tabeli podajemy w wywołaniu ToTable i wskazujemy nazwy kolumn kluczy obcych do obu typów encji/tabel. Należy dodać, że left key to klucz obcy wskazujący na typ którego mapping opisujemy, a right key to klucz obcy wskazujący na typ elementów kolekcji.

Tak by wyglądał przyznaje, że dość pobieżny opis mapowania encji w moim prostym projekcie. Uważam, że fluent API jest na tyle intuicyjne i samo-opisujące iż czysty kod wielu z Was najlepiej pomoże uchwycić to wszystko.

Ostatnim krokiem jest wskazanie, które z klas „konfiguracyjnych” definiują mapping których encji. To bardzo proste – w metodzie OnModelCreating dodajemy:

modelBuilder.Configurations.Add(newRolaMapping());

modelBuilder.Configurations.Add(newUzytkownikMapping());

modelBuilder.Configurations.Add(newKomputerMapping());

modelBuilder.Configurations.Add(newUprawnienieMapping());

Zachęcam jeszcze do przeanalizowania kompletnego projektu zamieszczonego tutaj.

Na koniec nie mógłbym nie podkreślić tego, iż trzymam kciuki za dalszy rozwój Entity Framework właśnie w tym kierunku – jedynym słusznym kierunku.

23 czerwca 2010

Prośby zostały wysłuchane, czyli POCO w Entity Framework.

Filed under: Codzienne dylematy modelarza — Tagi: , , — Beniamin Zaborski @ 23:13

Jakiś czas temu pisałem na blogu czemu nie lubię Entity Framework, narzekałem i wytykałem błędy (zresztą nie byłem w tym sam). W tym momencie muszę przyznać, że Microsoft stanął na wysokości zadania i „naprawił” to co mnie najbardziej irytowało w Entity Framework. Tak – teraz encje są obiektami POCO, a sam Entity Framework ma cechy O/R Mappera w stylu persistence ignorant. Wszystko jest w najlepszym porządku. Dla tych, którzy nie widzą problemu w używaniu O/R Mappera, który nie jest persistence ignorant mam dobrą wiadomość, bo nadal EF może działać po „staremu” i domyślnie tak działa.

Aby jednak móc skorzystać z tego cudu jakim jest persistence ignorant w EF, w Visual Studio należy doinstalować odpowiedni generator. Ta operacja w Visual Studio 2010 jest dziecinnie prosta, gdyż wystarczy po utworzeniu modelu EF z menu kontekstowego designera encji wybrać „Add Code Generation Item…” -> Online Templates -> Templates -> Database -> „ADO.NET C# POCO Entity Generator”. Po zainstalowaniu dodatku zobaczymy w naszym solution pliki szablonów *.tt oraz związany z nimi custom tool TextTemplatingFileGenerator.

Dzięki szablonom i generatorowi możemy się cieszyć encjami POCO w EF. Obiekty encji teraz to czyste obiekty, które nie dziedziczą już ze specyficznej klasy bazowej jak to w „starym” EF było. Dodać należy jeszcze tylko tyle, że działa tu lazy loading oraz change tracking.

Jest dobrze, ale czy nie może być lepiej? Zawsze może! W związku z tym zawsze można wprowadzić modyfikacje do szablonów generatora, aby przystosować generowany kod jeszcze bardziej do własnych potrzeb. Zachęcam do zapoznania się z POCO Entity Generator dla Entity Framework. Hmm … może to właśnie EF kiedyś zastąpi NH w moich projektach (?).

20 marca 2010

Dlaczego NHibernate jest wciąż moim ulubionym O/R Mapperem?

Filed under: Codzienne dylematy modelarza — Tagi: , — Beniamin Zaborski @ 11:24

W zasadzie pytanie powinno brzmieć: dlaczego Entity Framework szybko nie stanie się moim ulubionym O/R Mapperem? Po kilkuletniej przygodzie z NHibernate postanowiłem, jakiś czas temu, sprawdzić jak się ma konkurencja. Po szumnych zapowiedziach Microsoftu o inwestowaniu ogromnych sił i środków w rozwój ich flagowego, jak się okazuje, O/R Mappera sięgnąłem po Entity Framework. Coś musi być na rzeczy bo w „branży” dwie litery EF stały się mocno popularne – wręcz trendy. Zachęcony tym całym szumem wokół tej technologii, a także mając w pamięci prelekcję Julii Lerman z C2C’09 stworzyłem swój pierwszy projekt w EF. Hmm … ładny designer encji;). Tu się wszystko skończyło – dawno nie byłem tak rozczarowany! Dwie kwestie rozłożyły wszystko:

  • obiekty domeny nie są POCO
  • O/R mapper nie jest „Persistence Ignorance”

Obie te dwie cechy mają ze sobą wiele wspólnego. Przede wszystkim mój kod z użyciem EF posiada zbyt wiele zależności do konkretnych technologii łamiąc zasady architektury aplikacji biznesowych jak i wielu wzorców projektowych. Oczywistym mogło by się wydawać to, iż O/R Mapper nie powinien wymagać dziedziczenia encji po własnych klasach. Tak samo oczywiste jest to, że domena aplikacji nie powinna mieć świadomości o mechanizmie zapisu/pobierania danych. A jednak! Bez sarkazmu i zupełnie szczerze mówiąc, nie rozumiem tak wielkiej ignorancji twórców EF. Przyglądam się i nadal kibicuję EF mając nadzieję, że kolejne wersje przyniosą znaczące zmiany w tych dwóch kwestiach. Tymczasem zostaję przy sprawdzonym NHibernate.

30 listopada 2009

Error Provider

Filed under: How Can I Take ... — Tagi: , , , — Beniamin Zaborski @ 19:51

W poniższym artykule chciałbym poruszyć kwestię związana z walidacją obiektów. Oczywiste jest, że walidacja jest niezbędnym elementem każdej dobrze zaprojektowanej aplikacji biznesowej. Jednak nie sam sposób walidowania obiektów jest przewodnim tematem tego artykułu. Chciałbym przedstawić pewien dość oczywisty sposób informowania użytkownika o błędach. Do napisania tego artykułu zainspirowało mnie pytanie mojego znajomego … właśnie na podobny temat. Okazuje się, że nie wszystko jest tak oczywiste jak sądzimy. Na wstępie chciałbym zaznaczyć, iż artykuł dotyczy aplikacji Window Forms.
Większość z nas pewnie widziała w aplikacjach ASP.NET ikonki przy polach edycyjnych informujące o wymagalności danego pola lub zbyt dużej ilość znaków – ogólnie o niespełnionych regułach walidacyjnych. Podobny efekt możemy uzyskać w aplikacjach Window Forms. Tu kolejna dobra wiadomość – za pomocą standardowych kontrolek i to w łatwy sposób. Odpowiedzialna za to jest kontrolka o nazwie ErrorProvider. Poniższy zrzut ekranu przedstawia wspomnianą kontrolkę w akcji.

Niespełniona reguła walidacyjna na wskazanym property obiektu powoduje wyświetlenie ikony przy odpowiedniej kontrolce. Szczegóły niespełnionych reguł są wyświetlane w tooltipie. Wiemy zatem co chcemy uzyskać, teraz pytanie jak?

Zacznę może od samego obiektu który podlega walidacji. W aplikacjach biznesowych jest to z reguły jakiś DataTransferObject. Kontrolka ErrorProvider potrzebuje, aby dostarczyć do niej informacji o niespełnionych regułach walidacyjnych. Ta informacja to zwykły string czyli komunikat który ma się wyświetlić użytkownikowi. Odpowiedzialny za to jest interfejs IDataErrorInfo.

Interfejs jest dość prosty bo posiada tylko dwie właściwości:

  • string Error { get; }
  • string Item[string columnName] { get; }

Jak się okazuje dalej implementacja tego interfejsu w naszej klasie DataTransferObject również nie jest trudna. Zakładam, że nasz DataTransferObject posiada już sam mechanizm walidowania np. wykorzystujący całkiem poręczny Application Validation Block pochodzący z Enterprise Library. Nie pozostaje teraz nic innego jak zaimplementować IDataErrorInfo w naszej klasie DataTransferObject.

public abstract class DataTransferObject : IDataTransferObject, IValidatable, IDataErrorInfo
{
// pominięta część kodu klasy nieistotna w omawianym kontekście
string IDataErrorInfo.Error
{
get
{
if (!IsValid)
return BrokenValidationRules.GetDescriptionBrokenRules();
else
return String.Empty;
}
}

string IDataErrorInfo.this[string columnName]
{
get
{
string result = string.Empty;
if (!IsValid)
{
IBrokenValidationRule brokenRule = BrokenValidationRules.GetBrokenRule(columnName);
if (brokenRule != null)
result = brokenRule.Description;
}
return result;
}
}
}

Właściwość Error zwraca nam listę komunikatów o niespełnionych regułach na wskazanym obiekcie. Indekser zwraca nam natomiast listę komunikatów niespełnionych reguł dla wskazanej właściwości obiektu. Parametr columnName określa nazwę właściwości obiektu.

Przedstawiona klasa nie posiada pełnej implementacji, a jedynie te elementy które są istotne w omawianym kontekście. Oczywiste jest, że klasa musi mieć w tym wypadku kolekcję niespełnionych reguł walidacyjnych, która jest odpowiednio uzupełniana podczas wywołania walidacji. Na tej kolekcji zresztą bazują zaimplementowane właściwości z interfejsu IDataErrorInfo. W ten sposób chciałem pokazać, iż sam mechanizm walidacji jest zupełnie niezależny od implementacji IDataErrorInfo i po wprowadzeniu kilku własnych interfejsów może być łatwo wymieniany na inny.

Teraz możemy zająć się już interfejsem użytkownika. Pierwsze co należy zrobić to położyć na widoku kontrolkę ErrorProvider, np. przeciągając z toolboxa. Kolejnym krokiem jest zbindowanie naszego obiektu (dziedziczącego z DataTransferObject) z kontrolkami i przypisanie tego obiektu jako źródła danych do właściwości DataSource kontrolki ErrorProvider. Jeśli dodajemy kontrolkę ErrorProvider nie poprzez designera warto pamiętać o ustawieniu właściwości ContainerControl na odpowiednią formę/kontrolkę.

Sama kontrolka ErrorProvider posiada jeszcze kilka ciekawych właściwości jak: BlinkRate (częstotliwość pulsowania), BlinkStyle (kiedy ma pulsować ikonka) oraz Icon.

Powodzenia w implementacji wizualizacji wyników walidacji we własnej aplikacji w ten jakże prosty sposób.

16 listopada 2009

O2O Mapping

Filed under: How Can I Take ... — Tagi: , , , — Beniamin Zaborski @ 23:46

Zainspirowany jednym z postów na blogu Macieja Aniserowicza, postanowiłem bliżej przyjrzeć się zagadnieniu mapowania object-to-object. Wydaje się to być idealny lek na „głupie” mapowanie property poprzez przepisywanie każdego po kolei. A i owszem jest. Szczególnie przydatne przy mapowaniu obiektów domeny do obiektów DTO. Nie wiem ilu z Was robi/robiło to właśnie w ten tzw. „głupi” sposób, ale ja muszę przyznać że … mhm. Przykre, jak czasem nie zauważamy tak prostych rozwiązań pozwalających zaoszczędzić sporo czasu i zbędnych linii kodu. AutoMapper bo to o nim właściwie pisał Maciej to porządne narzędzie, które powinno zainteresować każdego programistę .NET. Na pierwszy rzut oka wygląda zachwycająco, ale to chyba bardziej idea tego typu mapowania jest bardziej zachwycająca niż sam w sobie produkt. Narzekam? Nie w sumie to nie. AutoMapper jest ok, ale niektóre rozwiązania w nim mnie nie odpowiadały. Sprawdziłem inne narzędzia tego typu jak choćby Glue i … ja potrzebowałem czegoś naprawdę trywialnego, bo sama idea jest trywialna. Długo nie myśląc stwierdziłem, że te kilka klas to napisze sam w taki sposób, aby mnie odpowiadały w 100%. I stało się – od pomysłu, po implementację. Czego potrzebowałem? Niczego więcej jak mapowania ad-hoc z możliwością zdefiniowania własnych reguł ustawiania wartości właściwości w obiekcie docelowym.
Oto przykład mówiący wszystko za siebie:


User user = new User();
user.Id = Guid.NewGuid();
user.Login = „Jan”;
user.Password = „Secret”;
user.FirstName = „Jan”;
user.LastName = „Kowalski”;
user.RoleName = „Admins”;

var mapper = new ObjectMapper<User, UserDTO>();
mapper
  .AddRule(„FullName”, s => s.FirstName + ” ” + s.LastName)
  .AddRule(„Login”, s => s.Login)
  .AddRule(„Password”, s=>SecurityUtils.Md5(s.Password));
mapper.Ignore(„RoleName”);
UserDTO userDTO = mapper.Map(user);

Kod źródłowy do wglądu zamieszczam tutaj. Tak na marginesie cały pomysł jak i jego implementacja powstała podczas jednego z programów rozrywkowych z tępym żartem granego w pewnej stacji na trzy litery w poniedziałkowe wieczory. A więc jak sądzę wszystko to trwało nie więcej jak 60 minut (z reklamami) – tłumaczę się na zaś jakby jednak coś nie działało;).

« Newer PostsOlder Posts »

Blog na WordPress.com.