Быстрый старт с Fluent NHibernate. Часть 1

Часть 1 | Часть 2 | Часть 3 (автомэппинг)

В этот пост войдет обзор материала, представленного мной на заседании MCP клуба 24 декабря 2009, и немного дополненного. Интересен будет всем, кто интересуется ORM технологиями, конструированием DAL и желающим сэкономить время на разработку. Ну, как говорится Let’s begin и все такое 🙂

Начнем прежде всего с начала. Что такое NHibernate? Отправлять вас в Google будет бессмысленно, потому что вы вероятно уже туда отправились 🙂

Вкратце скажу, что NHibernate — это (превосходная) ORM-технология, основанная на двух вещах:

Первая вещь — использование классов бизнес-логики, написанных разработчиком в качестве сущностей (POCO)

POCO = Plain old CLR objects, эти классы, состоят чаще всего из свойств, их назначение — хранение данных.

Вторая вещь — использование XML-файлов для конфигурирования и описания мэппинга.

Что такое мэппинг? Это отражение тех самых классов, написанных нами в объекты (таблицы, отношения) реляционной базы данных.

Основные конкуренты NHibernate:

* LINQ2SQL — простейший способ описать объекты бизнес-логики (сущности), используя дизайнер Microsoft Visual Studio. Основным недостатком является то, что эта вещь не позволяет использовать нам уже готовые классы, написанные ранее. Точнее позволяет с очень большими сложностями, и требует наследования от специализированных классов. Достаточно тяжеловесная, хоть и несколько примитивная.

* Microsoft Entity Framework — серьезный конкурент для NHibernate, но все же несколько отстающий. К примеру, если вы добавили сущности новое свойство, то перестроение схемы базы данных в EF займет куда больше времени. Кроме того, поддержка POCO-классов появилась сравнительно недавно и в бэта версии.

UPDATE: Одним из очень больших недостатков является также ориентация на Microsoft SQL Server у вышеуказанных технологий. Есть конечно адаптеры, написанные энтузиастами, но никто вам не гарантирует нормальной работы. NHibernate поддерживает большое количество современных СУБД и это большой плюс.

Откуда родом NHibernate ?

NHibernate происходит от своей старшей сестренки — технологии Hinerbate, из загадочного мира Java. Это не просто портированная версия, это попытка взгялнуть на некоторые особенности и проблемы иначе, используя возможности .NET, такие как рефлексия и CodeDOM. Но не вдаваясь в детали, все очень похоже, и если вы работали на Java с Hibernate, то вполне сможете разобраться в NHibernate.

А вот для тех, кто разбираться в NHibernate не хочет (в том числе) и посвящена эта статья 😉

В чем проблема ?

У любой технологии есть как плюсы, так и минусы. Вот некоторые минусы NHibernate: сложность в освоении, требуется достаточно высокий уровень, чтобы вникнуть в особенности, использование XML файлов позволяет легко допустить ошибки при написании мэппинг-конструкций, при этом нет контроля типа на этапе компиляции, нет проверки синтаксиса со всеми вытекающими… Но пусть вас это не пугает, как вы увидите из данного материала, указанные недостатки можно обойти очень удобным и красивым способом.

Почему Fluent NHibernate?

Когда я собрался изучать NHibernate (скажу честно, я его так и не изучил до конца), на глаза попалась эта замечательная fluent-технология и это было все, что мне нужно. Потому что это действительно экономит кучу времени, решает поставленные задачи и имеет кучу вкусностей на борту. Будучи надстройкой над NHibernate, эта штука позволяет не задумываться о том, как должны выглядеть XML файлы, здесь вообще нет XML! Контроль типов, автоматическое подключение классов к мэппингу, возможность удобной настройки соединения с базой данных и т.д. и т.п.

В общем мое решение однозначно — ЗА.

Где это взять?

Во-первых вам требуется NHibernate для работы с Fluent NHibernate. Взять можно тут: sourceforge.net/projects/nhibernate/

Во-вторых непосредственно Fluent NHibernate взять тут: fluentnhibernate.org/

Постановка задачи

Итак, у нас есть несколько классов бизнес-логики (сущностей), которые мы собираемся хранить базе данных.

Вот примерно так будут выглядеть все наши классы-сущности:

public class Customer
    {
        public string FirstName { get; set; }
        public string SecondName { get; set; }
        public string Comment { get; set; }
        public List<Order> Orders { get; set; }

        public Customer()
        {
            Orders = new List<Order>();
        }
    }
        

public class Order
    {
        public int OrderNumber { get; set; }
        public string Comment { get; set; }
        public DateTime Date { get; set; }
       
        public int Count { get; set; }
        public Customer Customer { get; set; }

        public Product Product { get; set; }
    }


public class Product
    {
        public string Code { get; set; }
        public int ProductNumber { get; set; }
        public string Name { get; set; }
        public double Price { get; set; }
    }

Что дальше?

Теперь нужно адаптировать классы для работы с NHibernate. Есть ряд требований, одно из которых — использование ключевого слова virtual (требуется для Lazy Loading). Кроме того, требуется ввести идентификатор (UID) для идентификации сущностей в базе. Для этого будем использовать базовый абстрактный класс и от него наследовать основные классы:

public abstract class EntityBase
    {
        public virtual Guid UID { get; set; }
    }

Остальные классы для работы с NHibernate немного изменятся (обратите внимание, вместо List<> появилась конструкция IList<>, потому что NHibernate использует свою внутреннюю реализацию списков):

public class Customer : EntityBase
    {
        public virtual string FirstName { get; set; }
        public virtual string SecondName { get; set; }
        public virtual string Comment { get; set; }
        public virtual IList<Order> Orders { get; set; }

        public Customer()
        {
            Orders = new List<Order>();
        }

        public virtual void AddOrder(Order order)
        {
            order.Customer = this;
            Orders.Add(order);
        }
    }
        

 public class Order : EntityBase
    {
        public virtual int OrderNumber { get; set; }
        public virtual string Comment { get; set; }
        public virtual DateTime Date { get; set; }
       
        public virtual int Count { get; set; }
        public virtual Customer Customer { get; set; }

        public virtual Product Product { get; set; }
    }


 public class Product : EntityBase
    {
        public virtual string Code { get; set; }
        public virtual int ProductNumber { get; set; }
        public virtual string Name { get; set; }
        public virtual double Price { get; set; }
    }

Диаграмма классов при этом останется та же.

Мэппинг

Пришло время создать мэппинг. Как я уже упоминал — NO XML AT ALL — CODE ONLY!

Для сравнения покажу как выглядит пример XML-мэппинга (помните, это ведь все пишется руками !!!!):

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0">
    <class name="NHibernate.Examples.QuickStart.User, NHibernate.Examples" table="users">
        <id name="Id" column="UID" type="String" length="20"> 
            <generator class="assigned" /> 
        </id> 
        <property name="FirstName" column="Name" type="String" /> 
        <property name="SecondName" type="String" /> 
        <property name="Comment" type="String" />
    </class>
</hibernate-mapping>

А вот так будет написано в коде:

(добавим в код ссылку на пространство имен FluentNHibernate.Mapping)

public class CustomerMap: ClassMap<Customer>
    {
        public CustomerMap()
        {
            Id(x => x.UID).GeneratedBy.Guid();
            Map(x => x.Comment);
            Map(x => x.FirstName).Not.Nullable();
            Map(x => x.SecondName);
            HasMany(x => x.Orders)
                .KeyColumns.Add("CustomerID")
                .Inverse()
                .Cascade.All();
        }
    }
        
        
        
public class OrderMap: ClassMap<Order>
    {
        public OrderMap()
        {
            Id(x => x.UID).GeneratedBy.Guid();
            Map(x => x.Comment);
            Map(x => x.Count);
            Map(x => x.Date);
            Map(x => x.OrderNumber);
            References(x => x.Product).Column("ProductID").Cascade.All();
            References(x => x.Customer).Column("CustomerID").Cascade.All();
        }
    }

        
        
 public class ProductMap: ClassMap<Product>
    {
        public ProductMap()
        {
            Id(x => x.UID).GeneratedBy.Guid();
            Map(x => x.Code);
            Map(x => x.Name);
            Map(x => x.Price);
            Map(x => x.ProductNumber);
        }
    }

Итак, теперь разберемся с этими конструкциями. Мы наследуем мэппинги от generic-класса ClassMap<>, содержащего ряд protected-методов, таких как Map<>, References<>, Id<> и тп. Мэппинг построен таким образом, что указываемый класс сохраняется в таблицу с таким же именем (если не указано другое методом Table).

Метод Map указывает, что заданное параметром селектора свойство будет мэппится на конкретное поле таблицы.

Id — указывает на идентификатор (identity) и способ его генерации.

References — отношение, многие-к-одному. Например References(x => x.Product) для мэппинга класса Order говорит о том, что у Order может содержаться несколько экземпляров Product и наоборот, у каждого экземпляра Product есть родительский экземпляр Order.

HasMany — позволяет задать отношение один-ко-многим для родительского класса.

Методы достаточно прозрачны и их имена говорят сами за себя, у вас не должно возникнуть проблем при более детальном закомстве.

Настройка подключения

Чтобы подключить базу данных, мы делаем вот что (база данных должна существовать):

private Configuration Config;
 Config = Fluently.Configure().
                Database(
                    MsSqlConfiguration
                        .MsSql2008
                        .ConnectionString(x => x
                            .Server(@".\SQLEXPRESS")
                            .Database("Orders")
                            .TrustedConnection())
                            .UseReflectionOptimizer())
                .Mappings(m => m.FluentMappings.AddFromAssemblyOf<ProductMap>())
                .BuildConfiguration();

Вот так вот быстро и удобно!

В следующей части мы рассмотрим шаблон IRepository и настройку автомэппинга для наших сущностей.

Часть 1 | Часть 2 | Часть 3 (автомэппинг)

Рейтинг
( Пока оценок нет )
Загрузка ...