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, и все прелести проверки время компиляции.

15 comments:

  1. Немного сумбурно, новичкам могут быть непонятны многие термины, например "доменные объекты", "refactor". Больше похоже на введение в NHibernate для опытного разработчика =)

    ReplyDelete
  2. Надо будет поработать над этим...

    ReplyDelete
  3. А мне понравилось. Продолжай Sly. Интересно понятно актуально.

    ReplyDelete
  4. Я мало что понял. Винигрет какойто.

    ReplyDelete
  5. А пытался понять? Очень ценный комментарий :)

    ReplyDelete
  6. отлично. Наконец то появилось хоть немного нормальной инфы на русском по хибернейту ))

    ReplyDelete
  7. Поясните новичку где взять:
    "Path". В этой строке среда ругается что переменная
    "Path" не объявлена:
    "...Configure(Path.Combine(AppDomain.Cur..."

    ReplyDelete
  8. насчет типов в маппинге аттрибутами.. можно определить расширяющий метод, который вернет имя типа в строке, тоесть GetType() разбить по разделителю '.' и вернуть последний элемент массива.

    ReplyDelete
  9. Почему-то пример с атрибутами не заработал(( The element 'class' in namespace 'urn:nhibernate-mapping-2.2' has invalid child element 'property' in namespace 'urn:nhibernate-mapping-2.2'

    ReplyDelete
  10. Правильно ли я понял - Refactor-Rename не будет работать ни в одном из трех способов, включая Fluent маппинг?

    ReplyDelete
  11. Не могу найти причину.

    Реализую следующее:

    return Fluently.Configure()
    .Database(MySQLConfiguration.Standard.ConnectionString(
    cs => cs.Server("78....")
    .Database("s...")
    .Username("co...")
    .Password("Nu...")))
    .Mappings(m => m.FluentMappings.AddFromAssemblyOf())
    .BuildSessionFactory();

    без строки " .Mappings(m => m.FluentMappings.AddFromAssemblyOf())" все работает
    но с мапингом выдает следующую ошибку:

    "An invalid or incomplete configuration was used while creating a SessionFactory. Check PotentialReasons collection, and InnerException for more detail."

    подскажите пожалуйста

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. Of() = Of{Product}() - скрылись при публикации "острые" скобки

      Delete