среда, 14 октября 2009 г.

Nhibernate & GenericDAO. Как быстро начать работать?

Это моя старая статья на тему Nhibernate & GenericDAO. Написана была в 2007 году кажется. Из названия я думаю понятно о чем это. Все это касается .Net.


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

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

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

  1. public class DataController
  2. {
  3. private ISessionFactory _sessions;
  4.  
  5. public DataController()
  6. {
  7.  Configuration cfg = new Configuration();
  8.  cfg.AddClass(typeof (DataElement));//Указание классов хибера
  9.  cfg.AddClass(typeof(Pins));
  10.  cfg.AddClass(typeof (Properties));
  11.  cfg.AddClass(typeof(Library));
  12.  _sessions = cfg.BuildSessionFactory();//Создание фабрики сессий
  13. }
  14. }
* This source code was highlighted with Source Code Highlighter.


Вот так вот просто мы можем работать с объектами класса DataElement. Я думаю тут все понятно что происходит. Но получается, что такие методы нам нужно писать для всех классов... Желания у всех нет писать это, тем более если классов очень много. Не забываем про методы Insert, Update, Delete. Итого у нас четыре хибер класса, для каждого минимум по четыре метода, итого 16 методов. Лень... Вот тут приходит на помощь DAO + generic. Итак давайте попробуем создать интерфейс общий для всех хибер объектов, словом *type* мы пометим те типы которые являются хибер классами, т.е. это может быть любой из используемых у нас хибер классов. В моей БД, у всех таблиц PK - это int поле.

  1. public interface IGenericDao
  2. {
  3. *type* Get(int id);
  4. *type* Save(*type*obj);
  5. *type* SaveOrUpdate(*type* obj);
  6. void Delete(*type* obj);
  7. }
* This source code was highlighted with Source Code Highlighter.


Мы видим что для всех классов требуется четыре метода (имеется ввиду минимум)... Удивительно... Но как же нам передавать грамотно тип? А вот так:

  1. public interface IGenericDao<T, ID>
  2. {
  3. T Get(ID id);
  4. T Save(T obj);
  5. T SaveOrUpdate(T obj);
  6. void Delete(T obj);
  7. }
* This source code was highlighted with Source Code Highlighter.


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

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

Мы помним что для того что бы взять, сохранить, обновить или удалить данные из БД, нам надо открыть сессию. И еще замечу, что если мы взяли данные, передали их в объект хибер класса, и закрыли сессию, то если у нас отключено Lazy (по умолчанию отключено), данные в объекте будут не доступны. Из за чего это? Из за того что прокси класс ссылается через сессию на требуемые нам данные (что такое прокси класс, читай документацию к хиберу). После закрытия сессии, получается что прокси класс ссылается на недоступную нам ссылку. Т.е. Получается что сессию мы должны хранить. Как же это сделать? Сессия описывается через интерфейс ISession. И берем мы сессию из фабрики сессий. Как же нам красиво это сделать? А вот как:

  1. public class SessionFactory
  2. {
  3. public static void Init() //Инициализация фабрики сессий
  4. {
  5.  Configuration cfg = new Configuration();
  6.  cfg.AddAssembly("Win.Objects"); //Конфигурируем NHibernate. Здесь мы указываем на сборку, в  которой хранятся мои хибер классы.
  7.  sessionFactory = cfg.BuildSessionFactory();
  8. }
  9. private static ISessionFactory sessionFactory; //Объект фабрики сессий, реализованный в хибере
  10. private static ISession threadSession //Сама сессия
  11. {
  12.  get
  13.  {
  14.   return (ISession)CallContext.GetData("THREAD_SESSION"); //Сессию мы храним в контексте, вот так работать с контекстом
  15.  }
  16.  set
  17.  {
  18.   CallContext.SetData("THREAD_SESSION", value);
  19.  }
  20. }
  21. public static ISession GetSession() //Метод возвращающий нам сессию.
  22. {
  23.  ISession session = threadSession; //Берем сессию из контекста
  24.  if (session == null) //Смотрим "метрва" ли она
  25.  {
  26.   session = sessionFactory.OpenSession(); //Через фабрику сессий открываем сессию
  27.   threadSession = session; //Записываем ее в контекс
  28.  }
  29.  return session; //Возвращаем
  30. }
  31. }
* This source code was highlighted with Source Code Highlighter.


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

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

У нас есть интерфейс, через который мы реализуем DAO, у нас есть класс для работы с сессией, теперь нам осталось написать методы CRUD(Create, Read, Update, Delete) для работы через generic. Так это происходит:

  1. public class GenericImpl<T, ID> : IGenericDao<T, ID> //Реализуем интерфейс IGenericDao
  2. {
  3. private ISession session //Здесь метод на взятие сессии
  4. {
  5.  get
  6.  {
  7.   return SessionFactory.GetSession(); //Используем нашу фабрику сессий.
  8.  }
  9. }
  10. private Type _type = typeof (T); //Тип хибер класса, с которым работаем.
  11. public T Get(ID id) //Метод взятия данных
  12. {
  13.  try
  14.  {
  15.   T result = (T) session.Load(_type, id); //Говорим что возвращаем тип T и загружаем его используя сессию через метод Load
  16.   return result; //Возвращаем
  17.  }
  18.  catch (HibernateException e)
  19.  {
  20.   throw e;
  21.  }
  22. }
  23. public T Save(T obj)
  24. {
  25.  try
  26.  {
  27.   session.Save(obj);
  28.   return obj;
  29.  }
  30.  catch (HibernateException e)
  31.  {
  32.   throw e;
  33.  }
  34. }
  35. public T SaveOrUpdate(T obj)
  36. {
  37.  session.SaveOrUpdate(obj);
  38.  return obj;
  39. }
  40. public void Delete(T obj)
  41. {
  42.  session.Delete(obj);
  43. }
  44. }
* This source code was highlighted with Source Code Highlighter.


И опять все до смешного просто... Теперь мы можем создавать GenericDao для любого нашего хибер класса. Теперь мы можем использовать класс GenericImpl для работы с любым нашим объектом. Как это сделать? Вот так

  1. GenericImpl<Library> libdao = new GenericImpl<Library>(); //Вот инструмент для работы с объектами хибер класса Library
  2. Library lib = new Library(); //Создаем объект хибер класса
  3. lib.Name = "Новая библиотека"; //Заполняем
  4. libdao.Save(lib);//Так сохраняем
  5. libdao.Get(1);//берем
  6. libdao.Delete(lib);//Удаляем
  7. libdao.SaveOrUpdate(lib);//Обновляем
* This source code was highlighted with Source Code Highlighter.


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

4. Фабрика DAO

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

  1. public interface IDataElementDao : IGenericDao<DataElement,int>{}
  2. public interface ILibraryDao : IGenericDao<Library, int> { }
  3. public interface IPinsDao : IGenericDao<Pins, int> { }
  4. public interface IPropertiesDao : IGenericDao<Properties, int>{ }
* This source code was highlighted with Source Code Highlighter.


Минус конечно что для каждого класса надо объявлять свой интерфейс, но зато не надо писать CRUD методы. Дальше мы сделаем интерфейс для фабрики DAO, которая будет нам поставлять уже готовые интерфейсы Dao для каждого хибер класса:

  1. public interface IDaoFactory
  2. {
  3. IDataElementDao GetDataElementDao();
  4. ILibraryDao GetLibraryDao();
  5. IPinsDao GetPinsDao();
  6. IPropertiesDao GetPropertiesDao();
  7. }
* This source code was highlighted with Source Code Highlighter.


Я думаю все понятно что это. И что бы было все совсем красиво, создадим классы, которые будут реализовывать IGenericDAO для каждого отдельного хибер класса:

  1. public class HDataElement : GenericImpl<DataElement,int> IDataElementDao{}
  2. public class HLibrary : GenericImpl<Library,int> ILibraryDao{}
  3. public class HPins : GenericImpl<Pins,int> IPinsDao{}
  4. public class HProperties : GenericImpl<Properties, int> IPropertiesDao{}
* This source code was highlighted with Source Code Highlighter.


Получается что мы должны реализовать интерфейс DAO для каждого хибер класса, и мы реализуем его наследовав базовый GenericImpl с указанием хибер класса. И реализуем фабрику DAO:

  1. public class FactoryDao : IDaoFactory
  2. {
  3. public IDataElementDao GetDataElementDao()
  4. {
  5.  return new HDataElement();
  6. }
  7. public ILibraryDao GetLibraryDao()
  8. {
  9.  return new HLibrary();
  10. }
  11. public IPinsDao GetPinsDao()
  12. {
  13.  return new HPins();
  14. }
  15. public IPropertiesDao GetPropertiesDao()
  16. {
  17.  return new HProperties();
  18. }
  19. }
* This source code was highlighted with Source Code Highlighter.


Вот так мы получаем готовые объекты DAO для работы с любым хибер классом. Как этим пользоваться? Вот так:

  1. IDaoFactory fact = new FactoryDao(); //Создаем фабрику дао
  2. ILibraryDao libdao = fact.GetLibraryDao();//берем дао для Library
  3. Library lib = new Library();
  4. lib.Name = "Новая библиотека";
  5. libdao.Save(lib);
  6. libdao.Get(1);
  7. libdao.Delete(lib);
  8. libdao.SaveOrUpdate(lib);
  9. IDataElementDao eldao = fact.GetDataElementDao();//Берем ДАО для DataElement и можем дальше работать с ним.
* This source code was highlighted with Source Code Highlighter.


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

2 комментария:

  1. Добрый день.
    Вроде все делал по руководству.
    При запуске вылетает с ошибкой "В экземпляре не заданна ссылка на объект" в строке " private static ISessionFactory sessionFactory; //Объект фабрики сессий, реализованный в хибере"

    ОтветитьУдалить
  2. Приветствую.
    Пришлите пожалуйста исходники на мое мыло. Мне так сложно помочь. :-)
    kup.vadim@gmail.com

    ОтветитьУдалить