Pokazywanie postów oznaczonych etykietą nHibernate. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą nHibernate. Pokaż wszystkie posty

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.