Wstęp

NHibernate jest rozwiązaniem ORM dla platformy .NET. Dostarcza środowisko do mapowania obiektowo-relacyjnego dla tradycyjnego, relacyjnego modelu bazy danych. Jego podstawową funkcją jest mapowanie z klas platformy .NET do tabeli baz danych oraz od typów danych CLR do typów danych SQL.

Artykuł ten jest pisany dla osób początkujących dlatego też zostanie poruszona w nim funkcjonalność pozwalająca na ładowanie obiektu biznesowego z bazy danych oraz zapisywanie zmian dokonanych na tym obiekcie z powrotem do bazy danych.

Przygotowana zostanie aplikacja, który w najprostszy możliwy sposób pokaże jak skonfigurować i używać NHibernate. Aplikacja będzie tworzyła obiekt Car i przechowywała go w tabeli Car. Pozwoli również na dokonanie różnych operacji takich jak: pobieranie i usuwanie obiektów.


Baza danych

W pierwszym kroku przygotujemy bazę danych. Posłużymy się tutaj narzędziem: SQL Server 2014. Tak wygląda przygotowana przez nas baza danych zawierająca jedną tabelę:

SQL Server 2014 - przykładowa baza danych

Tabela Car składa się z trzech kolumn: CarId, Brand oraz Model.
CarId powinno być kluczem głownym tabeli. Nie zapomnijcie włączyć Identity Specification we właściwościach kolumny:

SQL Server - klucz główny tabeli


Aplikacja konsolowa

Przejdźmy teraz do utworzenia projektu aplikacji konsolowej. Z punktu widzenia architektury nie jest to najlepsze rozwiązanie, jednak dla przykładu w zupełności wystarczy. Dodajemy nową klasę do naszego projektu:

namespace nHibernate
{
    class Car
    {
        public virtual int CarId { get; set; }
        public virtual string Brand { get; set; }
        public virtual string Model { get; set; }
    } 
}
Jedną z najmocniejszych zalet NHibernate jest fakt, że nie ma potrzeby tworzenia specjalnych interfejsów klas biznesowych. Obiekty te nie są świadome mechanizmu używanego do ładowania i zapisywania. Jednakże, wymagane jest utworzenie właściwości zdefiniowanych jako wirtualne (virtual). Definicja taka jest niezbędna, gdyż dzięki temu NHibernate będzie w stanie utworzyć proxy, tj. pośrednik pomiędzy warstwą bazy danych a naszą aplikacją.


Mapowanie pliku XML

W przypadku braku specyfikacji dotyczącej obiektów do mapowania, ktoś powinien pokierować procesem tłumaczenia z bazy danych do obiektu biznesowego i odwrotnie. Może to zostać osiągnietę poprzez mapowanie pliku XML lub poprzez zastosowanie atrybutów i właściwości. W naszej aplikacji użyjemy pliku do mapowania aby nasza klasa była jak najbardziej przejrzysta.

Dodajemy nowy plik XML do projektu. Plik ten będzie używany jako plik mapujący. Nazwa pliku musi być zdefiniowana jako: Car.hbm.xml. Obie klasy <nazwa>.cs oraz plik mapujący <nazwa>.hbm.xml powinny być w tym samym folderze i <nazwa> powinna być taka sama. Dodajmy poniższą treść do naszego pliku:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="NHibernateWprowadzenie" assembly="NHibernateWprowadzenie">
<class name="Car" table="Car">
    <id name="CarId" column="CarId">
        <generator class="identity" />
    </id>
    <property name="Brand" column="Brand" />
    <property name="Model" column="Model" />
</class>
</hibernate-mapping>
Struktury pliku XML nie będę tłumaczył. Wydaje się dosyć przejrzysta. Należy pamiętać, aby w przypadku innej nazwy projektu sprawdzić czy namespace oraz assembly mają prawidłowe nazwy. W ustawieniach pliku XML należy jeszcze ustawić Build Action jako Embedded Resource:

NHibernate - Embedded Resource


Konfiguracja

Dodajemy nową konfigurację do pliku app.config:

<?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="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
    <property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
    <property name="query.substitutions">hqlFunction=SQLFUNC</property>
    <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
    <property name="connection.connection_string">
        Data Source=PAWEL;Initial Catalog=nHibernate;Integrated Security=True
    </property>
    <!--Jeżeli poniższa właściwość będzie ustawiona na 'true' w konsoli zobaczycie wykonywane zapytania-->
    <property name="show_sql">false</property>
    <mapping assembly="NHibernateWprowadzenie" />
    </session-factory>
  </hibernate-configuration>
</configuration>
Pamiętacje o zmianie connection.connection_string na połączenie do swojej bazy danych oraz mapping assembly="" na odpowiednią nazwę.


Użycie kodu

Baza danych została przygotowana, pliki mapujące zostały utworzone, pora przejść do użycia kodu. W poniższej sekcji zostaną przedstawione operacje konfiguracji, pobierania, zapisywania czy kasowania danych w skróconej wersji. Komplenty kod wraz ze szczegółowym opisem zostanie zaprezentowany pod koniec artykułu. Dowiedzie się m.in. o wymaganych referencjach czy sposobie nawiązywania połączenia z bazą danych.

Przygotowanie konfiguracji:
// sprawdzamy czy przy uruchamianiu aplikacje sesje są otwarte
// jeżeli tak należy je zamknąć
if (mySession != null && mySession.IsOpen)
{
    mySession.Close();
}
if (mySessionFactory != null && !mySessionFactory.IsClosed)
{
    mySessionFactory.Close();
}
// Inicjowanie NHibernate
myConfiguration = new Configuration();
myConfiguration.Configure();
mySessionFactory = myConfiguration.BuildSessionFactory();
mySession = mySessionFactory.OpenSession();
Dodawanie danych:
// Dodanie wcześniej przygotowanych danych
using (mySession.BeginTransaction())
{
    mySession.Save(myInitialObjects[0]);
    mySession.Save(myInitialObjects[1]);
    mySession.Transaction.Commit();
}
Pobieranie danych:
// Wyświetlenie wszystkich danych z tabeli Car
using (mySession.BeginTransaction())
{
    // Poniżej trworzymy kryteria pobierania danych z tabeli
    ICriteria criteria = mySession.CreateCriteria<Car>();
    IList<Car> list = criteria.List<Car>();
    // Gdybyśmy chcieli zdefiniować warunki wyszukiwania wystarczy zrobić to w poniższy sposób
    // IList<Car> list = criteria.List<Car>().Where(a => a.CarId > 3).ToList();
    foreach (var item in list)
    {
        Console.WriteLine("Id: {0}, Marka: {1}, Model: {2}", item.CarId, item.Brand, item.Model);
    }
}
Usuwanie danych:
// Usunięcie wybranego rekordu
using (mySession.BeginTransaction())
{
    mySession.Delete(myInitialObjects[0]);
    mySession.Transaction.Commit();
}
Console.WriteLine("Rezultat operacji: Usnięto - Id: {0}, Marka: {1}, Model: {2}",
    myInitialObjects[0].CarId, myInitialObjects[0].Brand, myInitialObjects[0].Model);
Komplenty kod:
using NHibernate;
using NHibernate.Cfg;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
namespace NHibernateWprowadzenie
{
    class Program
    {
        static void Main(string[] args)
        {
            NHibernateExample NHibernate = new NHibernateExample();
        }
    }
    class NHibernateExample
    {
        // Pola związane z NHibernate 
        private Configuration myConfiguration;
        private ISessionFactory mySessionFactory;
        private ISession mySession;
        private Car[] myInitialObjects;
        private Car[] myFinalObjects;
        public NHibernateExample()
        {
            Configuration();
            NextAction();
        }
        public void Configuration()
        {
            // sprawdzamy czy przy uruchamianiu aplikacje sesje są otwarte
            // jeżeli tak należy je zamknąć
            if (mySession != null && mySession.IsOpen)
            {
                mySession.Close();
            }
            if (mySessionFactory != null && !mySessionFactory.IsClosed)
            {
                mySessionFactory.Close();
            }
            // Inicjowanie NHibernate
            myConfiguration = new Configuration();
            myConfiguration.Configure();
            mySessionFactory = myConfiguration.BuildSessionFactory();
            mySession = mySessionFactory.OpenSession();
            // Przygotowanie przykładowanych danych
            PrepareCarData();
            Console.WriteLine("Konfiguracja przebiegła pomyślnie..");
            Console.WriteLine();
        }
        public void PrepareCarData()
        {
            myInitialObjects = new Car[2];
            myFinalObjects = new Car[2];
            // Dodajemy dwa samochody
            Car car1 = new Car();
            car1.Brand = "Audi";
            car1.Model = "100";
            myInitialObjects[0] = car1;
            Car car2 = new Car();
            car2.Brand = "Audi";
            car2.Model = "RS6";
            myInitialObjects[1] = car2;
        }
        public void DeleteData()
        {
            // Usunięcie wszystkich rekordów
            using (ISession session = mySessionFactory.OpenSession())
            {
                SqlConnection con = session.Connection as SqlConnection;
                SqlCommand cmd = new SqlCommand("Delete from Car", con);
                cmd.ExecuteNonQuery();
            }
            Console.WriteLine("Rezultat operacji: Dane zostały usunięte z tabeli Cars");
            Console.WriteLine();
            NextAction();
        }
        public void DeleteSpecifiedData()
        {
            // Usunięcie wybranego rekordu
            using (mySession.BeginTransaction())
            {
                mySession.Delete(myInitialObjects[0]);
                mySession.Transaction.Commit();
            }
            Console.WriteLine("Rezultat operacji: Usnięto - Id: {0}, Marka: {1}, Model: {2}",
                myInitialObjects[0].CarId, myInitialObjects[0].Brand, myInitialObjects[0].Model);
            Console.WriteLine();
            NextAction();
        }
        public void AddData()
        {
            // Dodanie wcześniej przygotowanych danych
            using (mySession.BeginTransaction())
            {
                mySession.Save(myInitialObjects[0]);
                mySession.Save(myInitialObjects[1]);
                mySession.Transaction.Commit();
            }
            Console.WriteLine("Rezultat operacji: Dane zostały poprawnie dodane do tabeli Cars");
            Console.WriteLine();
            NextAction();
        }
        public void GetAllData()
        {
            // Wyświetlenie wszystkich danych z tabeli Car
            using (mySession.BeginTransaction())
            {
                // Poniżej trworzymy kryteria pobierania danych z tabeli
                ICriteria criteria = mySession.CreateCriteria<Car>();
                IList<Car> list = criteria.List<Car>();
                // Gdybyśmy chcieli zdefiniować warunki wyszukiwania wystarczy zrobić to w poniższy sposób
                // IList<Car> list = criteria.List<Car>().Where(a => a.CarId > 3).ToList();
                foreach (var item in list)
                {
                    Console.WriteLine("Id: {0}, Marka: {1}, Model: {2}", item.CarId, item.Brand, item.Model);
                }
                Console.WriteLine();
                NextAction();
            }
        }
        public void NextAction()
        {
            Console.WriteLine("Wybierz następne działanie, które chcesz wykonać: ");
            Console.WriteLine("1: Skasowanie danych z tabeli Car");
            Console.WriteLine("2: Dodanie danych do tabeli Car");
            Console.WriteLine("3: Wyświetlenie wszystkich danych z tabeli Car");
            Console.WriteLine("4: Opuścić aplikację: ");
            Console.Write("Twój wybór: ");
            int actionId = Convert.ToInt32(Console.ReadLine());
            Console.WriteLine();
            switch (actionId)
            {
                case 1:
                    DeleteData();
                    break;
                case 2:
                    AddData();
                    break;
                case 3:
                    GetAllData();
                    break;
                case 4:
                    System.Environment.Exit(0);
                    break;
                default:
                    break;
            }
            Console.ReadKey();
        }
    }
}