?

Log in

No account? Create an account

Previous Entry | Next Entry

Всем привет. Что такое 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();//Создание фабрики сессий

        }
 }
      
****************************************************************************************
В конструкторе мы видим что создается объект класса 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();//закрываем сессию
            }
        }
****************************************************
Вот так вот просто мы можем работать с объектами класса 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);
    }
***********************************
Мы видим что для всех классов требуется четыре метода (имеется ввиду минимум)... Удивительно... Но как же нам передавать грамотно тип? А вот так:
*************************************
public interface IGenericDao<T, ID>
    {
        T Get(ID id);
        T Save(T obj);
        T SaveOrUpdate(T obj);
        void Delete(T obj);
    }
*************************************
Здесь мы начинаем использовать шаблон класса. Даже два. Первый (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; //Возвращаем
        }
    }
***************************************************
Вот так все просто. Получается что сессия хранится в контексте, когда она нам нужна, мы вызываем метод 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);
        }
    }
**************************************
И опять все до смешного просто... Теперь мы можем создавать 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);//Обновляем
*******************************
Ну красиво, неправда ли? Но на этом мы не остановимся. Мне кажется что вам может не понравится то как создается 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> { }
*************************************
Минус конечно что для каждого класса надо объявлять свой интерфейс, но зато не надо писать CRUD методы. Дальше мы сделаем интерфейс для фабрики DAO, которая будет нам поставлять уже готовые интерфейсы Dao для каждого хибер класса:
*************************************
public interface IDaoFactory
    {
        IDataElementDao GetDataElementDao();
        ILibraryDao GetLibraryDao();
        IPinsDao GetPinsDao();
        IPropertiesDao GetPropertiesDao();
    }
*************************************
Я думаю все понятно что это. И что бы было все совсем красиво, создадим классы, которые будут реализовывать 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{}
**************************************
Получается что мы должны реализовать интерфейс DAO для каждого хибер класса, и мы реализуем его наследовав базовый GenericImpl с указанием хибер класса. И реализуем фабрику DAO:
**************************************
ublic 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();
        }
    }
**************************************
Вот так мы получаем готовые объекты 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 и можем дальше работать с ним.
**************************************

ЗАКЛЮЧЕНИЕ

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

Comments

vadim_kup
9 апр, 2008 07:08 (UTC)
Хотя сейчас вот придумал еще один путь. Опробую его и напишу про оба пути. Но смысл сводится к тому что, опять таки ручками писать много.

Latest Month

Октябрь 2008
Вс Пн Вт Ср Чт Пт Сб
   1234
567891011
12131415161718
19202122232425
262728293031 
Разработано LiveJournal.com
Designed by Tiffany Chow