Oct 22, 2009

NHibernate mappings. Или каталог маппингов

Тут буду собирать полезные статьи относящиеся к маппингу NHibernate.
  • Наследование.
  • property – подробности про маппинг свойств.
  • component – если есть желание иметь вложенные классы в сущностях. Например чтобы у пользователя был адрес в виде объекта и обращаться  к нему можно было как user.Address.Street.
  • one-to-one – связь один к одному.
  • any – для связи таблиц, которые не связаны явно. (не знаю как это описать более точно, надо читать :) ).
  • many-to-any – расширение any маппинга.
  • join – аналог one-to-one, но позволяет замапить несколько таблиц в один объект.
  • dynamic-component – позволяет вынести набор свойств объекта в Dictionary. Возможно редко используемые свойства есть смысл туда вынести, чтобы не засорять сущность.
  • set – один из наиболее часто используемых элементов для маппинга коллекций.
  • Named queries <query/> and <sql-query/> – если нужно встраивать свой sql код в маппинги.
  • map – никогда не использовал сам. Но это еще один вариант организации связей между сущностями. Когда посмотрю возможно опишу более подробно.
  • list – главное отличие list от set – list может содержать повторяющиеся записи. Это значит что если вы попытаетесь в set добавить элемент, который уже содержится в коллекции (проверяется на равенство при помощи метода Equals()), то элемент не будет добавлен. Если же последнее действие проделать с list, то элемент может быть добавлен.

Далее рассмотрим наиболее часто встречающиеся структуры:

  1. Дерево. По поводу дерева еще один пост про то, как его эффективно вытаскивать из базы

Каталог буду пополнять по мере поступления :).

Oct 21, 2009

NHibernate transactions. Почему рекомендуют все запросы выполнять внутри транзакций

Если выполнить для доменной модели (описана тут) следующий код без транзакции:

Category category = new Category
     {
          DisplayName = "Our first category"

     };
Product product = new Product
     {
         Description = "First product description",
         Name = "First product",
         Price = 1
     };
Order order = new Order(product)
     {
         Customer = "customer"

     };
Global.CurrentSession.SaveOrUpdate(category);
Global.CurrentSession.SaveOrUpdate(product);

То SqlProfiler покажет следующее:

image

Как вы видите операции логин/логаут выполняются после каждого запроса. Если же использовать код в global.asax, как описано ранее, то получим следующий результат:

image

И это только на 3х запросах, в реальных же приложениях это может существенно повлиять на производительность.

Oct 20, 2009

NHibernate для начинающих. Часть 3. Связи

Линки на остальные статьи

Изначально этот пост задумывался как описания каждого типа связи. Я собирался описать как мапить many-to-many, many-to-one, one-to-many и т.д. Но начав писать, я понял что такой информации и так достаточно в сети. Поэтому пост больше получился про то, как построить доменную модель для связанных сущностей. Данный подход наверняка можно распространить и на любое другое нормальное ORM средство.

В предидущих постах были рассмотрены базовые возможности NHibernate. В них рассматрился один класс Product, который соответствовал одной таблице в базе данных. Но естественно для полноценных бизнес приложений требуется набор объектов, которые взаимодействуют между собой. Например рассмотрим модель, которую можно было бы увидеть у какого нибуть электронного магазина. Нужен список товаров, категории товаров и возможность выполнить заказ. Пусть это приведет к следующей структуре классов:

  Диаграмма классов

На этой схеме изображено следующее:

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

Для хранения данной доменной модели была построена следующая база данных:

 Схема базы данных

Исходный текст классов таков:

public class Category  
{     
    private ISet<Product> products;     
    public Category()     
    {         
        products = new HashedSet<Product>();     
    }
    public virtual string DisplayName { get; set; }     
    public virtual int Id { get; protected set; }     
    public virtual ReadOnlyCollection<Product> Products     
    {        
        get { return new ReadOnlyCollection<Product>(new List<Product>(products));  }     
    }     
    public virtual void AddProduct(Product product)     
    {        
        if(product == null) throw new ArgumentNullException("product");     
        if(!products.Contains(product))         
        {
            product.AddCategory(this);            
            products.Add(product);        
        }     
    }     
    public virtual void RemoveProduct(Product product)     
    {        
        if(product == null) throw new ArgumentNullException("product");
        if(products.Contains(product))
        {            
            products.Remove(product);
            product.RemoveFrom(this);
        }
     }
}
 

Следует обратить внимание на следующие вещи:

  • Поле Id – protected. Это необходимо для защиты данных. Если нечайно поменять Id то можно обновить данные другого объекта, а не ожиемого
  • Все поля и методы должны быть виртуальными. Это необходимо для работы NHiberante
  • В конструкторе инициализируется колекция продуктов, таким образом никогда не будет NullReferenceException
  • Колекция продуктов представлена как ReadOnlyCollection. Такой подход оставляет нам возможность получить полный контроль над логикой добавления и удаления продуктов внутри категории
  • При добавлении продукта в категорию продукту автоматически добавляется эта категория. Это обеспечивает согласованность доменной модели (не может быть такого случая, что в колекции у категории продукт есть, а у продукта нет информации о том, что он содержится в категории)
  • Проверка приходящих параметров. Нельзя пробовать добавлять пустые ссылки на продукты
public class Product
{
     private ISet<Category> categories;
     public Product()
     {
        categories = new HashedSet<Category>();
        Orders = new HashedSet<Order>();
     }
     public virtual int Id { get; protected set; }
     public virtual string Name { get; set; }
     public virtual string Description { get; set; }
     public virtual double Price { get; set; }
     public virtual ReadOnlyCollection<Category> Categories
     {
        get
        {
           return new ReadOnlyCollection<Category>(new List<Category>(categories));
        }
     }
     public virtual ISet<Order> Orders { get; protected set; }
     public virtual void AddCategory(Category category)
     {
         if(category == null)
             throw new ArgumentNullException("category");
         if(!categories.Contains(category))
         {
             categories.Add(category);
             category.AddProduct(this);
         }
      }
      public virtual void RemoveFrom(Category category)
      {
         if(category == null)
             throw new ArgumentNullException("category");
         if(categories.Contains(category))
         {
             categories.Remove(category);
             category.RemoveProduct(this);
         }
       }
}  

Так же как и у категории мы обеспечиваем полный контроль над добавлением и удалением элементов из колекции. Но мы не делаем коллекцию заказов ReadOnly (но по прежнему инициализируем пустой колекцией для отсутствия NullReferenceException в нашем коде). Далее будет рассмотрено почему.

public class Order
{
      protected Order() { }
      public Order(Product product)
      {
         if(product == null)
             throw new ArgumentNullException("product");
         product.Orders.Add(this);
         Product = product;
      }
      public virtual int Id { get; protected set; }
      public virtual Product Product{get;protected set;}
      public virtual int NumberOfItems { get; set; }
      public virtual string Customer { get; set; }
}  

Важно выделить следующее

  • protected констуктор необходим для работы NHiberante
  • Тут можно увидеть почему колекция заказов осталась открытой. Чтобы добавить туда новый элемент необходимо сначала его создать. Но заказ не может существовать без продукта, что и отражено единственном открытом конструкторе
  • Product для заказа содержит protected сеттер. Это сделано для того, чтобы нельзя было у созданного заказа заменить продукт, который покупался.
  • Заказов без продукта существовать не должно. Соответственно при удалении продукта или же удалении связи между продуктом и заказом заказ должен быть удален. Как этого достичь будет показано далее

Для правильной работы рекомендуется всегда оборачивать запросы в транзацкии. Для этого следует изменить соответствующие методы в global.asax:

private ITransaction Transaction;
protected void Application_BeginRequest(object sender, EventArgs e)
{
      CurrentSession = SessionFactory.OpenSession();
      Transaction = CurrentSession.BeginTransaction();
}
protected void Application_EndRequest(object sender, EventArgs e)
{
      if (Server.GetLastError() == null)
      {
         Transaction.Commit();
      }
      else
      {
         Transaction.Rollback();
      }
      if (CurrentSession != null)
         CurrentSession.Dispose();
}   

И наконец маппинги:

public class CategoryMap: ClassMap<Category>
{
      public CategoryMap()
      {
          Table("Categories");
          Id(x => x.Id).GeneratedBy.Native();
          Map(x => x.DisplayName);
          HasManyToMany(x => x.Products)
                             .Access.CamelCaseField()
                             .Cascade.SaveUpdate()
                             .AsSet()
                             .ParentKeyColumn("CategoryId")
                             .ChildKeyColumn("ProductId")
                             .Table("[Products.Categories]");
      }
 }
public class ProductMap : ClassMap<Product>  
{
      public ProductMap()
      {
         Table("Products");
         Id(x => x.Id).GeneratedBy.Native();
         Map(x => x.Description);
         Map(x => x.Name);
         Map(x => x.Price);
         HasManyToMany(x => x.Categories)
                           .Access.CamelCaseField()
                           .Table("[Products.Categories]")
                           .ParentKeyColumn("ProductId")
                          .ChildKeyColumn("CategoryId")
                          .AsSet()
                          .Inverse()
                          .Cascade.SaveUpdate();
         HasMany(x => x.Orders)     
                      .KeyColumn("ProductId")     
                      .Inverse() 
                      .Cascade.AllDeleteOrphan();
      }
  }
  public class OrderMap:ClassMap<Order>
  {
   public OrderMap()
   {
    Table("Orders");
    Id(x => x.Id).GeneratedBy.Native();
    Map(x => x.NumberOfItems);
    Map(x => x.Customer);
    References(x => x.Product, "ProductId");
   }
  }

На что обратить внимание

  • У продукта и категории стоит Cascade.SaveUpdate() для колекции категорий и продукта соответственно. Это значит что сохранение продукта вызывает сохранение категории и наоборот. Чуть более подробно про каскадирование можно почитать тут
  • У колекций продукта стоит Inverse. Это означает что за сохранение связи между категорией и продуктом будет отвечать категория. Более подробно тут
  • У продукта на список заказов стоит Cascade.AllDeleteOrphan(). Это обязывает Nhibernate удалять записи, которые лишились родителя (дополнительно тут)
  • Для many-to-many установлен Access.CamelCaseField(). Это означает, что NHibernate будет использовать приватное поле для работы, это позволяет нам сделать коллекцию продуктов ReadOnlyCollection

Итак теперь можно поработать с полученной моделью:

Category category = new Category
    {
        DisplayName = "Our first category"
    };
Product product = new Product
    {
        Description = "First product description",
        Name = "First product",
        Price = 1
    };
category.AddProduct(product);
Global.CurrentSession.SaveOrUpdate(category); 

Эта часть кода заставит NHiberante выполнить следующий набор sql запросов:

INSERT INTO Categories (DisplayName) VALUES (@p0); select SCOPE_IDENTITY() @p0=N'Our first category'  
INSERT INTO Products (Description, Name, Price) VALUES (@p0, @p1, @p2); select SCOPE_IDENTITY() @p0=N'First product description',@p1=N'First product', @p2=1  
INSERT INTO [Products.Categories] (CategoryId, ProductId) VALUES (@p0, @p1) @p0=1,@p1=1  

Как можно видеть NHiberante сам позаботился о порядке сохранения объектов, и заполнении связующей таблицы.

Теперь проверим связь продуктов и заказов:

Product product = Global.CurrentSession.Get<Product>(1); 
var order = new Order(product)
 {
    NumberOfItems = 5,
    Customer = "customer"
 }; 
Global.CurrentSession.SaveOrUpdate(product); 

Это заставит NHiberante выполнить следующие запросы:

SELECT product0_.Id as Id2_0_, product0_.Description as Descript2_2_0_, product0_.Name as Name2_0_, product0_.Price as Price2_0_  
FROM Products product0_ 
WHERE product0_.Id=@p0,@p0=1  

SELECT orders0_.ProductId as ProductId1_, orders0_.Id as Id1_, orders0_.Id as Id3_0_, orders0_.NumberOfItems as NumberOf2_3_0_, orders0_.Customer as Customer3_0_, orders0_.ProductId as ProductId3_0_  
FROM Orders orders0_  
WHERE orders0_.ProductId=@p0,@p0=1  

INSERT INTO Orders (NumberOfItems, Customer, ProductId) VALUES (@p0, @p1, @p2); select SCOPE_IDENTITY(), @p0=5,@p1=N'customer',@p2=1  

(id могут быть другими).

Проверим сценарий в котором удаление связи между продуктом и заказом должно вызывать удаление заказа (это достигается при помощи all-delete-orphan). Например рассмотрим следующий код:

Product product = Global.CurrentSession.Get<Product>(1);
product.Orders.Clear(); 
Global.CurrentSession.SaveOrUpdate(product);

Он выполнит следующие запросы:

SELECT product0_.Id as Id2_0_, product0_.Description as Descript2_2_0_, product0_.Name as Name2_0_, product0_.Price as Price2_0_  
FROM Products product0_ 
WHERE product0_.Id=@p0, @p0=1  

SELECT orders0_.ProductId as ProductId1_, orders0_.Id as Id1_, orders0_.Id as Id3_0_, orders0_.NumberOfItems as NumberOf2_3_0_, orders0_.Customer as Customer3_0_, orders0_.ProductId as ProductId3_0_ 
FROM Orders orders0_  
WHERE orders0_.ProductId=@p0, @p0=1  

DELETE FROM Orders WHERE Id = @p0 @p0=2  
DELETE FROM Orders WHERE Id = @p0 @p0=3 
DELETE FROM Orders WHERE Id = @p0 @p0=4  

Были удалены все заказы, которые потеряли связь с продуктом. Самое приятное в таком подходе что в дальнейшем вы работаете только с доменной моделью вообще не заботясь о том, как NHiberante будет управлять сохранением коллекций.

Пример кода

Oct 16, 2009

Agent smith plugin

Я думаю многие используют такой плагин для вижуал студии как Resharper. Он делает жизнь на много проще и про него писать нет смысла, но вот есть к Resharpery отличный плагин. Называется Agent Smith. Он автоматически проверяет правильность написания слов в коде. Крайне полезная штука для отлавливания опечаток в коде.

Еще одной функцией этого плагина является проверка правил именования. Например что приватные поля должны называться в стиле _myField. Но в Resharper 4.5 уже есть подобная функциональность, и чтобы они друг другу не мешали в агенте её можно выключить:

resharperOptions

Oct 15, 2009

Атрибут DebuggerDisplay

Это одна из моих любимых возможностей по улучшению процесса дебага. Рассмотрим следующий пример. Пусть есть объект Product:

public class Product 
{ 
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
    public virtual string Description { get; set; }
    public virtual double Price { get; set; } 
}

Если навести на него в debug режиме, то мы увидим следующее:

productNotExpanded C одной стороны все нормально, если навести на плюсик, то получим полную информацию:

productExpanded  А теперь посмотрим как будет выглядеть список этих объектов:

listOfProductUgly Соответственно чтобы найти в этом списке продукт с Id = 5 приходится последовательно разворачивать каждый (особенно если список не отсортирован).

Для удобочитаемости объекта в дебаге можно воспользоваться атрибутом DebuggerDisplay. Он позволяет изменять вид отображения объекта. Например если добавить следующий атрибут к классу Product:

[DebuggerDisplay("Id = {Id}, Name= {Name}, Price = {Price}")]
public class Product 
//... далее описание класса

В фигурных скобках указываются интересующие нас свойства. Результат:

listOfProductNice Этой краткой информации вполне достаточно чтобы понять что находится в коллекции и найти там нужный элемент.

Oct 7, 2009

NHibernate для начинающих. Часть 2. Варианты маппингов

Линки на остальные статьи

Любое бизнес приложение содержит набор некоторых объектов, комбинация который представляет собой доменную модель приложения. Например для электронного магазина такими доменными объектами могут быть: Product, Category, Order и т.д.

Для NHibernate необходимы знания о вашей доменной модели для сохранения и извлечения соответствующих объектов. Для этого используется подход называемый data mapper.

В этой статье будут рассмотрены доступные на данный момент варианты маппингов  NHibernate. Будут рассмотрены:

  • Xml файлы – базовый способ маппинга, все остальные способы сводятся к получению таких xml файлов.
  • Аттрибуты – каждому полю или классу добавляются специальные аттрибуты, которые в дальшем преобразовываются в xml.
  • Fluent маппинг – маппинг при помощи лямбда выражений.

Рассмотрим каждый из них подробнее.

1 Xml файлы

Базовый способ маппинга был показан в первой статье. Могу выделить лишь следущюее:

Преимущества:

  • Самый первый из разработанных в NHibernate, соответственно в интернете множество можно найти примеров.
  • Достаточно .net 2.0

Недостатки:

  • Отсутствие валидации во время компиляции
  • Невозможность переименовать поля при помощи Refactor (в вижуал студии есть отличная возможность для удобного переименовывания полей и методов. Для этого на нужном методе клацаем правой кнопкой, далее в меню выбираем Refactor->Rename. Эта опция переименует метод и обновит все вывозы этого метода или свойства в проекте)
  • Отсутствеие полноценного intellisense (есть xsd схема для маппингов, но имена полей вашей сущности показывать все равно не будет)

2 Аттрибуты

Рассмотрим пример из первой части, только воспользуемся маппингами с помощью аттрибутов. Скачать нужную для этого библиотеку можно тут. После того, как добавлена ссылка на NHibernate.Mapping.Attributes класс Product (описан в первой части) должен выглядеть следующим образом:

[Class(Table = "Products", Name = "InheritanceCore.Product")]
public class Product
{
 [Id(0, Column = "Id", Type = "int", Name="Id")]
 [Generator(1, Class = "native")]
 public virtual int Id { get; protected set; }

 [Property(Name = "Name", Column = "Name", Type = "String")]
 public virtual string Name { get; set; }

 [Property(Name = "Description", Column = "Description", Type = "String")]
 public virtual string Description { get; set; }

 [Property(Name = "Price", Column = "Price", Type = "Double")]
 public virtual double Price { get; set; }
}

Также надо изменить Global.asax следующим образом:

protected static ISessionFactory CreateSessionFactory()
{
 var config = new Configuration().Configure(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "hibernate.config"));
 HbmSerializer.Default.Validate = true;
 var assembly = Assembly.GetAssembly(typeof (Product));
 HbmSerializer.Default.HbmAssembly = assembly.FullName;
 HbmSerializer.Default.HbmNamespace = typeof(Product).Namespace;
 var stream = HbmSerializer.Default.Serialize(assembly);
 config.AddInputStream(stream);
 return config.BuildSessionFactory();
}

Вот и все, остальное остается с первой части без изменений. Таким образом:

Преимущества:

  • Сущности и маппинги находятся рядом
  • Наличие некоторого intellisense

Недостатки

  • Наличие строковых литералов. (Свойства Name, Type). Из-за этого невозможно использовать Refactor и полноценно переименовывать свойства классов
  • Отсутствие валидации во время компиляции. На самом деле можно все атрибуты повесить на одно свойство, результат будет такой же
  • Доменные объекты засоряются огромным количеством аттрибутов, которые усложняют читабельность кода


3 Fluent маппинг

Это наиболее новый способ маппинга.
Рассмотрим как с его помощью замапить тот же самый класс Product.

В этом случае надо изменить метод в global.asax следующим образом:


protected static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
               .Database(MsSqlConfiguration.MsSql2005.ConnectionString(@"your connection string"))
               .Mappings(m => m.FluentMappings.AddFromAssemblyOf<product>())
               .BuildSessionFactory();
}

Для маппинга в проект добавляется еще один класс. Как правило его называют <ClassName>Map, т.е. у для примера Product это будет класс ProductMap. Его содержимое:

public class ProductMap : ClassMap<Product>
{
 public ProductMap()
 {
    Table("Products");
    Id(x => x.Id).GeneratedBy.Native();
    Map(x => x.Description).Column("Description");
    Map(x => x.Name).Column("Name");
    Map(x => x.Price).Column("Price");
 }
}

Тут мы имеем полный intellisense, и все прелести проверки время компиляции.

Jquery date picker и форматы дат

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

1. Добавляем календарь следующим образом:

$(".datePick").datepicker({ 
    dateFormat: '<%= ConstantHelper.JQUERY_DATE_FORMAT %>'
});

2. ConstantHelper

CultureInfo.CurrentCulture.DateTimeFormat
 .ShortDatePattern.Replace("M", "m")
    .Replace("yy", "y");

Добавленный таким образом календарь будет выдавать даты в формате текущей культуры, и они будут совпадать с .net форматами.

Oct 6, 2009

NHibernate для начинающих. Часть 1.

Линки на остальные статьи

В этой статье будет описано как сделать простое веб приложение, использующее NHibernate для работы с базой данных.

Начнем с того что где можно скачать и почитать.

· http://nhforge.org/ - сайт NHibnerate. Там можно найти википедии по многим вещам связанным NHibernate, википедию, ссылки на гугл группы, блоги и т.д.

· http://sourceforge.net/projects/nhibernate/files/NHibernate/ - тут можно скачать бинарники и исходники NHibernate.

· http://www.manning.com/kuate/ - книга по NHibernate. Если постараться можно найти эл. вариант.

· http://ayende.com/Blog/archive/2006/10/27/HowToGetStartedWithNHibernate.aspx - набор ссылок на статьи по NHibernate для начинающих

· http://jasondentler.com/blog/2009/09/part-8-daos-repositories-or-query-objects - там вверху есть ссылки на предидущие этой статьи. В них описываются часто возникаемые вопросы, такие как управление транзакциями, реализация паттерна репозиторий и прочее.

Итак начнем. Для демонстрации возможностей создадим простую ASP.NET страницу, с помощью которой можно будет просматривать, редактировать и удалять записи из базы данных.

Все операции в NHibernate выполняются через ISession. Экземпляр этого объекта можно получить через ISessionFactory. Создание объекта ISession очень быстро, в отличии от ISessionFactory поэтому ISessionFactory должен создаваться один раз и использоваться как Singleton объект на протяжении работы приложения .

Пусть мы хотим отобразить список товаров. Создадим в базе данных следующую таблицу:

products table

Скрипт создания:

CREATE TABLE [dbo].[Products](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NOT NULL,
[Description] [nvarchar](250) NULL,
[Price] [float] NOT NULL,
CONSTRAINT [PK_Products] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
ON [PRIMARY]

И создадим класс, который будет отвечать одной записи в этой базе данных. Для этого добавляем новый проект. Пусть он называется InheritanceCore (этот проект я буду использовать для дальнейших постов). Содержимое класса Product:

public class Product
{
public virtual int Id { get; protected set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual double Price { get; set; }
}

Обратите внимание на 2 вещи:

1. Все поля должы быть virtual – это требование необходимо для lazy load и для отслеживания NHibnerate’ом всех изменений в объектах.

2. Поле Id содержит protected сеттер. Это сделано для того, чтобы никто случайно не заменил id объекта. Немного информации по этому поводу можно получить тут.

Для того, чтобы получить объект ISessionFactory добавим файл Global.asax. Его код должен выглядеть следующим образом:

public static ISessionFactory SessionFactory = CreateSessionFactory();
protected static ISessionFactory CreateSessionFactory()
{
return new Configuration().Configure(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "hibernate.config")).BuildSessionFactory();
}
public static ISession CurrentSession
{
get { return (ISession)HttpContext.Current.Items["current.session"]; }
set { HttpContext.Current.Items["current.session"] = value; }
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
CurrentSession = SessionFactory.OpenSession();
}
protected void Application_EndRequest(object sender, EventArgs e)
{
if (CurrentSession != null)
CurrentSession.Dispose();
}

Для компиляции кода нужно добавить ссылки на все dll, находящиеся в папке Required_Bins скачаного Nhibernate contrib.

Тут происходит следующее. Поскольку поле SessionFactory статическое, то инициализируется оно только один раз. На begin request и end request создается и удаляется сессия для работы с NHibernate. Так же можно увидеть что для конфигурации используется файл hibernate.config. Добавим этот файл в корень прилоежния.

Содержимое этого файла:

<?xml version="1.0" encoding="utf-8"?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.connection_string">Data Source=10.10.5.101\sqlexpress;Database=nhibernate;UID=nhibernate;pwd=nhibernate;</property>
<property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
<property name="proxyfactory.factory_class">NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu</property>
<mapping assembly="InheritanceCore" />
</session-factory>
</hibernate-configuration>

Настройки интуитивно понятные, кроме одной: proxyfactory.factory_class. Эта настройка добавлена начиная с Nhibernate версии 2.1.0, информацию о ней можно найти тут, для успешной работы нужно будет добавить ссылку на сборку NHibernate.ByteCode.LinFu, которая находится в папке Required_For_LazyLoading того же Nhibernate contrib.

Осталось последнее, рассказать Nhibernate’у какие классы есть у нас в приложении. Для этого есть несколько подходов, о них я напишу позже. В этом самом простом приложении воспользуемся наиболее старым но понятным подходом – xml файлы. Добавим в проект новый файл Product.hbm.xml, и поставим ему Build Action – Embedded Resource (иначе NHibernate не получит нужных ему маппингов). Содержимое этого файла:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="InheritanceCore"
namespace="InheritanceCore">
<class name="InheritanceCore.Product" table="Products" xmlns="urn:nhibernate-mapping-2.2">
<id name="Id" column="Id" type="Int32">
<generator class="native"/>
</id>
<property name="Name" column="Name" type="String"/>
<property name="Description" column="Description" type="String"/>
<property name="Price" column="Price" type="Double"/>
</class>
</hibernate-mapping>

Данный маппинг говорит о том, что есть класс, который соответствует таблице Products, содержит идентификатор с именем Id, значение которого генерируется при помощи базы данных (дополнительную информацию про id можно найти тут). И содержит 3 свойства, 2 текстовых, и 1 числовое.

На данном этапе проект должен выглядеть следующим образом:

clip_image004

Теперь выполним CRUD операции.

Для получения всех продуктов:

Global.CurrentSession.CreateCriteria(typeof(Product)).List<Product>()

Для создания продукта:

var product = new Product
{
Description = txtDesctiption.Text,
Name = txtName.Text,
Price = double.Parse(txtPrice.Text);
};
Global.CurrentSession.SaveOrUpdate(product);

Для обновления:

var product = Global.CurrentSession.Get<Product>(productId);
product.Description = "edited";
Global.CurrentSession.SaveOrUpdate(product);

Для удаления

var product = Global.CurrentSession.Get<Product>(productId);
Global.CurrentSession.Delete(product);