Nhibernate. DAO + Generic

    Всем привет. Что такое GenericDao, вы должны знать, но если не знаете:
    DAO( сокр. от англ. Data Access Objects) — объекты для доступа к данным.
    Generic означает что мы будем использовать шаблоны классов (generic), доступные в C#, для создания такого интерфейса, который позволит работать со всеми нашими классами NHibernate.

    1. Создание интерфейса GenericDao


    Итак приступим. В прошлой статье я говорил как настроить NHibernate. Какой должен быть файл настроек приложения и т.д.
    Для начала рассмотрим то, как же происходит работа с NHibernate (далее хибер, хибернейт), для доступа к БД.
    Разберем класс, который нам позволяет настроить NHibernate для работы с нашими объектами, этот класс мы просто рассмотрим, нигде использовать его не будем:
    public class DataController
    {

    private ISessionFactory _sessions;

    public DataController()
    {
    Configuration cfg = new Configuration();
    cfg.AddClass(typeof (DataElement));//Указание классов хибера
    cfg.AddClass(typeof(Pins));
    cfg.AddClass(typeof (Properties));
    cfg.AddClass(typeof(Library));
    _sessions = cfg.BuildSessionFactory();//Создание фабрики сессий

    }
    }


    * This source code was highlighted with Source Code Highlighter.

    В конструкторе мы видим что создается объект класса Configuration, который нам следует сконфигурировать определенным образом. Мы указываем какие хибер классы мы имеем. После этого строим фабрику сессий. После чего фабрика сессий позволяет нам открывать сессию для работы именно с теми классами которые мы указали. Далее рассмотрим как же нам, скажем, сохранить данные в БД:
    public DataElement SaveElement(DataElement el)
    {
    ISession session = _sessions.OpenSession();//Октрываем сессию
    try
    {
    session.Save(el); //Сохраняем элемент
    return el; //Возвращаем его, для получения его PK, если PK у нас генерируется

    }
    catch (HibernateException e)
    {
    throw e;
    }
    finally
    {
    session.Close();//закрываем сессию
    }
    }


    * This source code was highlighted with Source Code Highlighter.

    Вот так вот мы можем работать с объектами класса DataElement. Но получается, что такие методы нам нужно писать для всех классов… Желания у всех нет писать это, тем более если классов очень много. Не забываем про методы Insert, Update, Delete. Итого у нас четыре хибер класса, для каждого минимум по четыре метода, итого 16 методов. Лень…
    Вот тут приходит на помощь DAO + generic.
    Итак давайте попробуем создать интерфейс общий для всех хибер объектов, словом *type* мы пометим те типы которые являются хибер классами, т.е. это может быть любой из используемых у нас хибер классов. В моей БД, у всех таблиц PK — это int поле.
    public interface IGenericDao
    {
    *type* Get(int id);
    *type* Save(*type*obj);
    *type* SaveOrUpdate(*type* obj);
    void Delete(*type* obj);
    }


    * This source code was highlighted with Source Code Highlighter.

    Мы видим что для всех классов требуется четыре метода (имеется ввиду минимум)… Но как же нам передавать грамотно тип? А вот так:
    public interface IGenericDao<T, ID>
    {
    T Get(ID id);
    T Save(T obj);
    T SaveOrUpdate(T obj);
    void Delete(T obj);
    }


    * This source code was highlighted with Source Code Highlighter.

    Здесь мы начинаем использовать шаблон (дженерик тип) класса. Даже два. Первый (T) это шаблон для хибер класса, второй(ID) это шаблон для типа которым является PK. Т.е. мы сможем брать поля из БД, не только по PK с типом int, но и скажем string.
    Вот мы создали интерфейс GenericDao, теперь надо создать класс который будет реализовывать этот интерфейс. Но для начала вспомним о сессиях.

    2. Создание фабрики сессий


    Мы помним что для того что бы взять, сохранить, обновить или удалить данные из БД, нам надо открыть сессию. И еще замечу, что если мы взяли данные, передали их в объект хибер класса, и закрыли сессию, то если у нас отключено Lazy (по умолчанию отключено), данные в объекте будут не доступны. Из за чего это? Из за того что прокси класс ссылается через сессию на требуемые нам данные (что такое прокси класс, читай документацию к хиберу). После закрытия сессии, получается что прокси класс ссылается на недоступную нам ссылку. Т.е. Получается что сессию мы должны хранить. Как же это сделать? Сессия описывается через интерфейс ISession. И берем мы сессию из фабрики сессий. Как же нам это сделать? А вот как:
    public class SessionFactory
    {
    public static void Init() //Инициализация фабрики сессий
    {
    Configuration cfg = new Configuration();
    cfg.AddAssembly(”Win.Objects”); //Конфигурируем NHibernate. Здесь мы указываем на сборку, в которой хранятся мои хибер классы.
    sessionFactory = cfg.BuildSessionFactory();
    }
    private static ISessionFactory sessionFactory; //Объект фабрики сессий, реализованный в хибере
    private static ISession threadSession //Сама сессия
    {
    get
    {
    return (ISession)CallContext.GetData(”THREAD_SESSION”); //Сессию мы храним в контексте, вот так работать с контекстом
    }
    set
    {
    CallContext.SetData(”THREAD_SESSION”, value);
    }
    }

    public static ISession GetSession() //Метод возвращающий нам сессию.
    {
    ISession session = threadSession; //Берем сессию из контекста

    if (session == null) //Смотрим “метрва” ли она
    {
    session = sessionFactory.OpenSession(); //Через фабрику сессий открываем сессию
    threadSession = session; //Записываем ее в контекс
    }

    return session; //Возвращаем
    }
    }


    * This source code was highlighted with Source Code Highlighter.

    Получается что сессия хранится в контексте, когда она нам нужна, мы вызываем метод GetSession(), там проверяется “Жива” ли сессия или нет, если нет — создаем новую, если да — возвращаем “живую”. Что касается инициализации, то ее надо вызвать один раз в любом месте своей программы, но до того как начали использовать сессию. Скажем в конструкторе главной формы. Вот и все. Разобрались… Теперь посмотрим на реализацию интерфейса IGenericDao.

    3. Реализация IGenericDao


    У нас есть интерфейс, через который мы реализуем DAO, у нас есть класс для работы с сессией, теперь нам осталось написать методы CRUD(Create, Read, Update, Delete) для работы через generic. Так это происходит:
    public class GenericImpl<T, ID> : IGenericDao<T, ID> //Реализуем интерфейс IGenericDao
    {
    private ISession session //Здесь метод на взятие сессии
    {
    get
    {
    return SessionFactory.GetSession(); //Используем нашу фабрику сессий.
    }
    }

    private Type _type = typeof (T); //Тип хибер класса, с которым работаем.

    public T Get(ID id) //Метод взятия данных
    {
    try
    {
    T result = (T) session.Load(_type, id); //Говорим что возвращаем тип T и загружаем его используя сессию через метод Load
    return result; //Возвращаем
    }
    catch (HibernateException e)
    {
    throw e;
    }
    }

    public T Save(T obj)
    {
    try
    {
    session.Save(obj);
    return obj;
    }
    catch (HibernateException e)
    {
    throw e;
    }
    }

    public T SaveOrUpdate(T obj)
    {
    session.SaveOrUpdate(obj);
    return obj;
    }

    public void Delete(T obj)
    {
    session.Delete(obj);
    }
    }


    * This source code was highlighted with Source Code Highlighter.

    И опять все до смешного просто… Теперь мы можем создавать GenericDao для любого нашего хибер класса. Теперь мы можем использовать класс GenericImpl для работы с любым нашим объектом.
    GenericImpl<Library, int> libdao = new GenericImpl<Library, int>(); //Вот инструмент для работы с объектами хибер класса Library
    Library lib = new Library(); //Создаем объект хибер класса
    lib.Name = “Новая библиотека”; //Заполняем
    libdao.Save(lib);//Так сохраняем
    libdao.Get(1);//берем
    libdao.Delete(lib);//Удаляем
    libdao.SaveOrUpdate(lib);//Обновляем


    * This source code was highlighted with Source Code Highlighter.

    Ну красиво, неправда ли? Но на этом мы не остановимся. Мне кажется что вам может не понравится то как создается DAO. Попробуем облегчить себе жизнь. Как? Создадим фабрику DAO для каждого хибер класса.

    4. Фабрика DAO


    Что мы будем делать? Мы создадим для каждого класса свое DAO, уникальное, но при этом мы не напишем больше ни одного метода CRUD. Как так сделать? Просто. Но по порядку…
    Мы создадим интерфейс DAO для каждого хибер класса, просто наследуя его от базового IGenericDao, но с указанием того с каким хибер классом мы будем работать:
    public interface IDataElementDao : IGenericDao<DataElement,int>{}
    public interface ILibraryDao : IGenericDao<Library, int> { }
    public interface IPinsDao : IGenericDao<Pins, int> { }
    public interface IPropertiesDao : IGenericDao<Properties, int> { }


    * This source code was highlighted with Source Code Highlighter.

    Минус конечно что для каждого класса надо объявлять свой интерфейс, но зато не надо писать CRUD методы. Дальше мы сделаем интерфейс для фабрики DAO, которая будет нам поставлять уже готовые интерфейсы Dao для каждого хибер класса:
    public interface IDaoFactory
    {
    IDataElementDao GetDataElementDao();
    ILibraryDao GetLibraryDao();
    IPinsDao GetPinsDao();
    IPropertiesDao GetPropertiesDao();
    }


    * This source code was highlighted with Source Code Highlighter.

    Я думаю все понятно что это. И что бы было все совсем красиво, создадим классы, которые будут реализовывать IGenericDAO для каждого отдельного хибер класса:
    public class HDataElement : GenericImpl<DataElement,int>, IDataElementDao{}
    public class HLibrary : GenericImpl<Library,int>, ILibraryDao{}
    public class HPins : GenericImpl<Pins,int>, IPinsDao{}
    public class HProperties : GenericImpl<Properties, int>, IPropertiesDao{}


    * This source code was highlighted with Source Code Highlighter.

    Получается что мы должны реализовать интерфейс DAO для каждого хибер класса, и мы реализуем его наследовав базовый GenericImpl с указанием хибер класса. И реализуем фабрику DAO:
    public class FactoryDao : IDaoFactory
    {
    public IDataElementDao GetDataElementDao()
    {
    return new HDataElement();
    }

    public ILibraryDao GetLibraryDao()
    {
    return new HLibrary();
    }

    public IPinsDao GetPinsDao()
    {
    return new HPins();
    }

    public IPropertiesDao GetPropertiesDao()
    {
    return new HProperties();
    }
    }


    * This source code was highlighted with Source Code Highlighter.

    Вот так мы получаем готовые объекты DAO для работы с любым хибер классом. Как этим пользоваться? Вот так:
    IDaoFactory fact = new FactoryDao(); //Создаем фабрику дао

    ILibraryDao libdao = fact.GetLibraryDao();//берем дао для Library
    Library lib = new Library();
    lib.Name = “Новая библиотека”;
    libdao.Save(lib);
    libdao.Get(1);
    libdao.Delete(lib);
    libdao.SaveOrUpdate(lib);

    IDataElementDao eldao = fact.GetDataElementDao();//Берем ДАО для DataElement и можем дальше работать с ним.


    * This source code was highlighted with Source Code Highlighter.

    ЗАКЛЮЧЕНИЕ


    Хочется добавить что если есть не решенные вопросы, то озвучивайте их и тогда может быть ваш вопрос попадет в тему следующих статей.
    Для тех кому и этого мало, то идем СЮДА. Здесь описано как еще больше облегчить наши проблемы с использованием Spring FrameWork и его SpringAOP. Вещь крайне полезная, но очень прихотливая.
    Ну вот и все. Надеюсь всем все понятно. Претензии как всегда все принимаются и будут услышаны. Надеюсь пригодится.)

    P.S.: Мой первый топик на хабре. Надеюсь что статейка понравится. Всем привет!:-)
    Поделиться публикацией

    Похожие публикации

    Комментарии 2

      0
      Добро пожаловать на Хабр! Видите, вы и без волонтеров сюда попали ;)
      Вот только выравнивание в коде поехало — читать тяжело.
        0
        Приветствую! :-) Обязательно учту ваши замечания. Но чуть позже… :-(

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое