poniedziałek, 30 września 2013

nHibernate logowanie operacji

Bardzo często chcemy wiedzieć co dzieje się w naszej aplikacji. Podstawową formą śledzenia aplikacji jest logowanie informacji do pliku/bazy lub innego nośnika z którego można następnie je odtworzyć i analizować.

nHibernate świetnie w tej materii współpracuje z biblioteką log4net. Bibliotece tej poświęcę osobny post - w skrócie umożliwia ona tworzenie logów. Konfiguracja lo4net możne zostać zapisana w pliku konfiguracyjnym aplikacji jak i bezpośrednio w kodzie.

Aby logować to co dzieje się w nHibernate należy dodać do solucji biblioteki. Można tego dokonać z poziomu NuGet:


Dzięki użyciu NuGet log4net zostanie wstępnie skonfigurowany w app.config. Po uruchomieniu aplikacji korzystającej z nHibernate, na ekranie konsoli zostaną wypisane informacje z aktualnie wykonywanych czynności w nHibernate:


Dodatkowo wchodząc w ustawienia session factory możemy dodać parametr show_sql. Parametr ten doda w trybie logowania na konsolę dane wartości przekazywanych parametrów do zapytania SQL:

Code:
  <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2" >
    <session-factory>
      <property name="dialect">NHibernate.Dialect.MsSql2012Dialect</property>
      <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
      <property name="connection.connection_string">Server=localhost;initial catalog=AdventureWorksLT2012;Integrated Security=True</property>
      <property name="show_sql">true</property>
      <mapping assembly="AdventureWorksSampleHibernateLogging" />
    </session-factory


W tym poście przedstawiłem w jaki sposób logować na konsolę. Nic nie stoi na przeszkodzie aby te dane logowane były do bazy czy pliku. Wybór należy do nas.

Plik z solucją: http://sdrv.ms/17kyth8

niedziela, 29 września 2013

Generowanie kluczy głównych w nHibernate

Każda tabela w bazie danych powinna posiadać kolumnę zwaną kluczem głównym. Dzięki tej kolumnie jesteśmy w stanie jednoznacznie odróżnić wiersze w bazie danych.
nHibernate posiada kilka generatorów kluczy głównych. Możemy je podzielić na dwie kategorie:
  • takie, które wymagają odpytania bazy danych po wstawieniu rekordu do bazy o wygenerowany id
  • takie, które wstawiają id wraz z wierszem
Pierwszy sposób jest niekorzystny, gdyż aby pobrać wartość id klucza należy wywołać dodatkowe zapytanie do bazy danych - zwiększamy obciążenie na bazie danych, jednocześnie zmniejszając wydajność aplikacji.

Pierwsza grupa zawiera następujące generatory:
  • identity -zwraca id wygenerowane przez bazę
  • select - używa zapytania SELECT aby pobrać id rekordu (potrzebny jest w takim wypadku klucz naturalny)
  • sequence-identity - używany dla baz obsługujących sekwencje
  • trigger-identity - zwraca id wygenerowane przez trigger
  • native - w zależności od bazy danych (np. dla MS SQL identity, dla Oracle - sequence-identity)
Druga grupa jest szersza i zawiera więcej możliwości:
  • hilo - użyty jest algorytm Hi/Lo - rezerwowany jest zakres identyfikatorów numerycznych. Po skończeniu danego zakresu rezerwowany jest kolejny. Jest to bezpieczne rozwiązanie, gdyż użyta jest w bazie dodatkowa tabela przechowująca rezerwowane zakresy
  • guid - wykorzystuje GUID
  • guid.comb - łączy 10 bajtowy GUID wraz z 6 bajtową datą. Zmniejsza to poziom fragmentacji indeksu
  • guid.native - pobiera GUID z bazy (wymaga to odwołania do bazy dnaych)
  • uuid.hex - tworzy GUID numeryczny
  • uuid.string - zapisuje GUID w postaci binarnej - w takiej postaci jest nieczytelny dla człowieka 
  • counter - licznik inicjalizowany z zegara systemowego; nie można go stosować w rozwiązaniach gdzie baza jest współdzielona 
  • increment - rozwiązanie podobne do poprzedniego - wartość początkowa licznika pobierana jest na starcie jako MAX(Id) i inkrementowana w czasie działania programu. Nie nadaje się w rozwiązaniach gdzie jest współdzielona baza danych.
  • sequence - korzysta z sekwencji dostępnych w takich bazach jak Oracle i PostgreSql
  • seqhilo - łączy algorytm Hi/Lo z sekwencjami w celu poprawienia wydajności
  • foreign - używa klucza z innej encji - najczęściej wykorzystywany w przypadku relacji one-to-one
Każdy sposób ma swoje wady i zalety. Najbezpieczniejszą metodą jest GUID, który ze względu na swoją długość zapewnia unikalność pomiędzy wieloma instancjami aplikacji korzystającymi z tej samej bazy danych.
Jeżeli budujemy prostą aplikację, a z bazy będzie korzystała tylko jedna aplikacja możemy spokojnie jako klucza głównego użyć typu całkowitoliczbowego (integer).

nHibernate " is not mapped"

Wyjątek z tematu "[Class] is not mapped" jest jednym z częściej występujących błędów podczas pracy z nHibernate. Związane jest to głównie z tym, że większość pracy wykonujemy manualnie wprowadzając nazwy klas i mapowań jako zwykłe ciągi znaków (string) co przekłada się na ilość błędów.

Przyczyn tego wyjątku można szukać w kilku miejscach. Miejsca w których można popełnić błędy podzieliłem na 4 obszary:
  1. App/web config
  2. Plik mapowania klasy na tabelę w bazie
  3. Tworzenie sesji
  4. Zapytania


1. App/web .config
W pliku tym ustawiamy podstawowe właściwości fabryki sesji. Jednym z ważniejszych parametrów jest ten, mówiący gdzie znajdują się mapowania klas do tabel:


Code:
  <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2" >
    <session-factory>
      <property name="dialect">NHibernate.Dialect.MsSql2012Dialect</property>
      <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
      <property name="connection.connection_string">Server=localhost;initial catalog=AdventureWorksLT2012;Integrated Security=True</property>
      <mapping assembly="nHibernateCreatingMapping" />
    </session-factory>
  </hibernate-configuration>

Po wystąpieniu tego błędu sprawdzamy więc w/w plik i szukamy czy znajduje się tam linijka mówiąca w którym assembly znajdują się mapowania.


2. Plik mapowania klasy na tabelę w bazie
W tym pliku można popełnić szereg błędów:


Code:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="nHibernateCreatingMapping" namespace="nHibernateCreatingMapping.Domain">
  <class name="ProductDescription" table="SalesLT.ProductDescription">

Rzeczy które sprawdzamy:
  • czy nazwa assembly jest na pewno poprawna
  • czy namespace się zgadza
  • czy w nazwie klasy nie popełniliśmy błędu
  • czy we właściwościach pliku zaznaczyliśmy Build Action na Embedded Resource
Jak widać możliwość popełnienia 4 błędów - całkiem sporo.


3. Tworzenie sesji:


Code:
            ISessionFactory sessionFactory = new Configuration().Configure().BuildSessionFactory();
            var session = sessionFactory.OpenSession();

Tutaj także można popełnić niewinny błąd i przez pośpiech możemy zapomnieć o wywołaniu metody Configure przed stworzeniem obiektu fabryki sesji.


4. Zapytania
Jeżeli do tworzenia zapytań wykorzystujemy HQL, należy pamiętać że w zapytaniach używamy nazwy klasy a nie tak jak by się mogło wydawać - tabeli.


Code:
var productDescriptions = session.CreateQuery("from ProductDescription").List<ProductDescription>();



Wiele osób porzuca nHibernate z powodu problemów konfiguracyjnych. Po kilku dniach używania biblioteki można w łatwy sposób identyfikować miejsca gdzie potencjalnie popełniliśmy błąd. Aby ułatwić sobie pracę i przy okazji używać silnie typowanych danych można skorzystać z fluent nHibernate - ale o tym w osobnym poście.

sobota, 28 września 2013

nHibernate - początki

Do ORM nHibernate podchodziłem już kilkakrotnie. Może wynikało to ze względu na to, że całą konfigurację wykonywało się w XML, a może to że nie wspierał w pełni LINQ.
Od czasów kiedy chciałem się nim zająć wiele się zmieniło w tej kwestii i obecnie wersja 3.3 zapewnia bardzo wiele z tego, co kiedyś wydawało się niemożliwe.
Parę faktów:
Aby rozpocząć korzystanie z biblioteki należy pobrać albo z sourceforge spakowane binarki, albo za pomocą NuGet-a ściągnąć pliki do projektu:



Po dodaniu referencji do biblioteki warto dodać pliki schemy, aby ułatwić sobie pisanie mappingów. W tym celu dodajemy do solucji folder Schema i wrzucamy do niego pliki schemy udostępnione wraz z nHibernate:


Aby pliki mapowania mogły działać i podpowiadać składnię w Visual Studio, należy je dodać do obsługiwanych plików schemy. Aby to zrobić wchodzimy w którykolwiek plik ze schemą, w meni XML wybieramy opcję XML->Schemas... i wskazujemy miejsce gdzie znajdują się pliki:


Teraz możemy napisać pierwsze mapowanie. W celach pokazowych posłużę się bazą MS SQL 2012 AdventureWorksLT2012. W bazie tej zawarta jest tabela ProductDescription do której stworzę przykładowe mapowanie. 

Najpierw stworzymy klasę która odpowiada tabeli w bazie danych:



Code:
using System;

namespace nHibernateCreatingMapping.Domain
{
 public class ProductDescription
 {
  public virtual int ProductDescriptionID { get; protected set; }
  public virtual string Description { get; set; }
  public virtual Guid Rowguid { get; protected set; }
  public virtual DateTime ModifiedDate { get; set; }
 }
}

Teraz stworzymy plik mapujący klasę do tabeli w bazie danych:


Code:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="nHibernateCreatingMapping" namespace="nHibernateCreatingMapping.Domain">
  <class name="ProductDescription" table="SalesLT.ProductDescription">
    <id name="ProductDescriptionID">
      <generator class="identity" />
    </id>
    <property name="Description" />
    <property name="Rowguid" />
    <property name="ModifiedDate" />
  </class>
</hibernate-mapping>


Bardzo ważne: w właściwościach pliku z mapowaniem należy ustawić parametr Build Action na Embedded Resource (o innych możliwościach napiszę w osobnym poście).

Kolejną czynnością będzie dodanie konfiguracji do app.config w celu ustawienia podstawowych opcji nHibernate:


Code:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
  </configSections>
  <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2" >
    <session-factory>
      <property name="dialect">NHibernate.Dialect.MsSql2012Dialect</property>
      <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
      <property name="connection.connection_string">Server=localhost;initial catalog=AdventureWorksLT2012;Integrated Security=True</property>
      <mapping assembly="nHibernateCreatingMapping" />
    </session-factory>
  </hibernate-configuration>
  <connectionStrings>
    <add name="AdventureWorks" connectionString="Data Source=localhost;Integrated Security=true;Initial Catalog=AdventureWorksLT2012" providerName="System.Data.SqlClient"/>
  </connectionStrings>
  <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>    
</configuration>


Po ustawieniu wszystkich parametrów możemy przystąpić do tworzenia sesji nHibernate i odpytywania bazy danych:


Code:
            ISessionFactory sessionFactory = new Configuration().Configure().BuildSessionFactory();
            var session = sessionFactory.OpenSession();
            var productDescriptions =
                session.Query<ProductDescription>()
                    .Where(x => x.ProductDescriptionID > 10 && x.ProductDescriptionID < 100);
            foreach (var productDescription in productDescriptions)
            {
                Console.WriteLine("{0} {1} {2}", productDescription.ProductDescriptionID,
                    productDescription.ModifiedDate, productDescription.Description);
            }

Rezultat:



W kolejnych opiszę dokładniej mechanizmy rządzące nHibernate, bardziej zaawansowane przypadki mapowania i inne ciekawe aspekty tej biblioteki.

niedziela, 15 września 2013

Spring.net DI?

Do tej pory w większości projektów używałem jednego z dwóch kontenerów DI: Unity lub Ninject. Kontenery te są wygodne i pozwalają na dużą swobodę konfiguracji. Nowa praca, nowe wyzwania - nowy kontener - Spring.net. Jak się sprawdza w codziennej pracy w porównaniu do poprzednich?

Najpierw instalacja - tutaj z pomocą przychodzi NuGet - w wyszukiwarce wpisujemy słowo kluczowe "spring". Pierwszy wynik czyli Spring.Core to interesujący nas składnik:






Referencje dodane czas na działanie. Aby coś testować, należy przygotować pewnego rodzaju grunt. Do testów tworzymy prosty interfejs:


Code:
    public interface ICalculator
    {
        int Add(int a, int b);
        int Subtract(int a, int b);
    }

oraz klasę implementującą:


Code:
    class Calculator : ICalculator
    {
        public int Add(int a, int b)
        {
            return a + b;
        }

        public int Subtract(int a, int b)
        {
            return a - b;
        }
    }


Pierwsza zmiana w stosunku do poprzednich kontenerów DI jest taka, iż Spring.net pozwala na konfigurację wyłącznie w XML (według dokumentacji ma zostać dodana w przyszłości możliwość konfiguracji za pomocą atrybutów). Jest to z jednej strony i zaleta i wada. W mojej ocenie brak możliwości mapowania w kodzie czy też za pomocą atrybutów delikatnie skreśla tę bibliotekę z listy współczesnych frameworków DI.
Warto w tym momencie pobrać xsd springa w celu wsparcia IntelliSense. Z menu XML wybieramy opcję Schemas... i wskazujemy gdzie znajduje się plik xsd:


Dzięki temu posunięciu tworzenie xml-a będzie trochę prostsze i popełnimy mniej błędów.
Definicje obiektów możemy przechowywać w pliku XML app.config lub dowolnym innym. Przechowanie ich w app.config jest chyba najwygodniejsze - wszystkie ustawienia trzymamy w jednym pliku a nie rozrzucamy po kilku. Najłatwiejszym sposobem jest dodanie sekcji konfiguracyjnej spring i następnie odczytanie jej w aplikacji. Przykładowa rejestracja sekcji:


Code:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <sectionGroup name="spring">
      <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/>
      <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
    </sectionGroup>
  </configSections>

  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>

  <spring>
    <context>
      <resource uri="config://spring/objects"/>
    </context>
    <objects xmlns="http://www.springframework.net">
      <object id="calculator" type="SpringNetSample.Calculator, SpringNetSample" />
    </objects>
  </spring>
</configuration>


Samo pobranie w kodzie obiektu przebiega w następujący sposób:

Code:
            IApplicationContext applicationContext = ContextRegistry.GetContext();
            ICalculator calculator = (Calculator) applicationContext.GetObject("calculator");
            Console.WriteLine(calculator.Add(1,2));


Podsumowując prosty przykład obrazujący jak użyć Spring.net. Niestety brak możliwości konfiguracji za pomocą kodu/atrybutów nie pozwala go uznać za nowoczesny kontener DI. Z drugiej strony, zmuszając programistę do używania XML ułatwia późniejszą konfiguracje administratorom systemu.
Kolejnym minusem jest brak metod generycznych pozwalających pobrać typ który chcemy dostać - można to oczywiście obejść za pomocą metod rozszerzających.

Podsumowując - spełnia swoją rolę jako kontener DI, jednak technologicznie pozostał w epoce .NET 2.0

Nie testuję tutaj wydajności kontenera. Moim zdaniem nie ważne czy kontener tworzy 1000 obiektów na sekundę a drugi 1050, gdyż ważniejsze są, moim zdaniem, możliwości i wygoda korzystania z danego kontenera. Szybkość w przypadku użycia kontenerów DI nie ma dużego znaczenia.

Problem z instalacją Update 3 Visual Studio 2012 - KB2707250

Korzystając z wolnej chwili chciałem uaktualnić VS 2012 SP2 do wersji SP3.
Niestety w momencie uruchomienia instalatora zostało wyświetlone okienko z informacją o błędzie:

Visual Studio 2012 Update 3 (KB2707250) has stopped working. A problem caused the program to stop working correctly. Please close the program.
No cóż, błąd zdarza się. Pomyślałem, że pewnie przez ilość dodatków do VS, moja instalacja po prostu się popsuła i trzeba ją naprawić. Po wejściu do Panelu Sterowania i naciśnięciu klawisza Change otrzymałem ten sam błąd, który jest przedstawiony powyżej.
Czyli jednym słowem nie mogłem ani zainstalować ani tym bardziej odinstalować VS :)
Ponieważ o błędzie nie znalazłem za dużo informacji w sieci, a sama treść też nie mówi za dużo, postanowiłem poszukać innego sposobu.
Na msdn znalazłem artykuł, opisujący dostępne przełączniki podczas instalowania VS.
Bingo! Jeden z nich:
/Log Filename Specifies a location for the log file
Pozwala na logowanie informacji podczas instalacji VS. Nie czekając dłużej odpaliłem konsolę i poczekałem na utworzenie pliku z logiem. Dzięki zalogowaniu informacji odnalazłem przyczynę moich problemów:






Po zobaczeniu tego wpisu, przypomniałem sobie, że kiedyś modyfikowałem machine.config. Wszedłem pod wskazany folder, przywróciłem oryginalny plik. Instalator się uruchomił i byłem w stanie zainstalować poprawki :)

Morał jest taki, że warto w erze myszkologi pamiętać, że istnieje stara dobra konsola :)

Hostowanie serwisu REST, przetwarzanie wiadomości, tworzenie klientów

Aby ułatwić hostowanie serwisów opartych o REST została przedstawiona klasa WebServiceHost. Klasa ta zawiera potrzebne bindingi.
Jedyne co musimy podać to typ serwisu oraz adres pod którym będzie dostępny:


Code:
            var webServiceHost = new WebServiceHost(typeof(Service1), new Uri(@"http://localhost/RestService/"));
            webServiceHost.Open();
            Console.WriteLine("Press key to close");
            Console.Read();
            webServiceHost.Close();


Aby napisać klienta serwisu REST można skorzystać z klasy WebChannelFactory<T>. Kod tworzenia jest wręcz identyczny jak w przypadku połączenia z serwisem SOAP:


Code:
            var webChannelFactory = new WebChannelFactory<IService1>(new Uri("http://localhost/RestService/"));
            var channel = webChannelFactory.CreateChannel();
            List<Person> persons = channel.GetPersons();
            foreach (var person in persons)
            {
                Console.WriteLine("{0} {1}", person.Id, person.Name);
            }



Warto jeszcze w tym miejscu wspomnieć o klasie WebOperationContext. Klasa ta umożliwia dostęp do aktualnie przetwarzanego requestu w metodach serwisu. Możemy dzięki temu pobrać następujące dane:
  • URI wywołania
  • nagłówki 
  • ETag
  • format danych
  • długość wiadomości
Przykładowe wywołanie podczas wykonywania metody serwisu: