Сейчас работаю на проекте, где активно юзается NHibernate и библиотека Fluent NHibernate для реализации data access layer. До этого никогда с Hibernate не работал, незначительное количество времени применял ADO.NET, и начиная с 2008 все проекты шли на LINQ-to-SQL и немного Entity Framework. Было очень интересно пощупать альтернативу вездесущим ORM от Microsoft, узнать ее плюсы, минусы, наиболее подходящие области применения.
Как описать data object?
Тут же можно настроить и валидацию, например для поля PostalCode.
Что если есть связи с другими объектами? Как выглядят mappings?
Пример:
Класс Retailer имеет поля Store и Type, являющиеся ссылками на объекты из другой таблицы (связь один-ко-многим), а также коллекцию Alerts (много-к-одному)
Как вытащить объекты из базы данных?
Я использовал библиотеку SharpArch, позволяющая организовать доступ к данным посредством паттерна Repository: например, для того чтобы поработать с объектами типа Address, нужно использовать AddressRepository, для других объектов соответственно будут другие репозитории: StoreRepository, EmployeeRepository… Repository предоставляет базовые операции для получения и сохранения объектов: GetAll(), GetById(), Update() и так далее.
Ок, с единичными объектами всё понятно - работаем через Repository, но как быть если нам необходимо сделать выборку по каким-нибудь критериям?
Вот здесь NHibernate мне понравился множеством вариантов реализации данной задачи, разной степени простоты, гибкости и скорости :-) Отсортировано от более высокоуровневых вещей к более низкоуровневым:
Если что-то хотите узнать подробнее, пишите в комментариях в блоге!
По порядку.
Что такое NHibernate? Это ORM-система, позволяющая создавать абстрагированный от конкретной базы данных объектно-ориентированный data access layer. Связи между data objects и таблицами базы данных определяются через mappings, прописываемые программистом в XML-файле.
Fluent NHibernate - надстройка над NHibernate, позволяющая прописывать mappings вместо XML через .NET-классы посредством лямбда-выражений, т.е. более удобным и защищенным от ошибок способом.
Далее везде буду описывать именно NHibernate в сочетании с Fluent NHibernate.
Как описать data object?
Первое что бросается в глаза после LINQ-to-SQL и EF - отсутствие “из коробки” автоматической генерации классов, каждую сущность программисту необходимо создавать вручную. С другой стороны, объем кода, приходящийся на каждый из классов, где-то на порядок меньше кода, сгенерированного майкрософтовскими генераторами. Если к примеру в data object поля называются так же, как столбцы в таблице в базе данных и нет внешних ключей, то mappings вообще не нужны, достаточно описать класс вот так:
public class Address
{
public virtual int Id { get; set; }
[NotNull, NotEmpty]
public virtual string AddressType { get; set; }
[NotNull, NotEmpty]
public virtual string Address1 { get; set; }
public virtual string Address2 { get; set; }
[NotNull, NotEmpty]
public virtual string City { get; set; }
[NotNull, NotEmpty]
public virtual string State { get; set; }
[NotNull, NotEmpty, Pattern(@"^((\d{5}-\d{4})|(\d{5})|([A-Z]\d[A-Z]\s\d[A-Z]\d))$")]
public virtual string PostalCode { get; set; }
[NotNull, NotEmpty, Pattern(@"USA|Canada")]
public virtual string Country { get; set; }
public virtual float Latitude { get; set; }
public virtual float Longitude { get; set; }
public virtual double Distance { get; set; }
}
Тут же можно настроить и валидацию, например для поля PostalCode.
Что если есть связи с другими объектами? Как выглядят mappings?
Пример:
public class RetailerMaps : IAutoMappingOverride<Retailer>
{
public void Override(AutoMapping<Retailer> mapping)
{
mapping.Table("Retailers");
mapping.References(x => x.Store, "StoreId");
mapping.References(x => x.Type, "RetailerTypeId");
mapping.HasMany(x => x.Alerts).Table("RetailerAlerts").KeyColumn("RetailerId").Cascade.All();
}
}
Класс Retailer имеет поля Store и Type, являющиеся ссылками на объекты из другой таблицы (связь один-ко-многим), а также коллекцию Alerts (много-к-одному)
Как вытащить объекты из базы данных?
Я использовал библиотеку SharpArch, позволяющая организовать доступ к данным посредством паттерна Repository: например, для того чтобы поработать с объектами типа Address, нужно использовать AddressRepository, для других объектов соответственно будут другие репозитории: StoreRepository, EmployeeRepository… Repository предоставляет базовые операции для получения и сохранения объектов: GetAll(), GetById(), Update() и так далее.
Ок, с единичными объектами всё понятно - работаем через Repository, но как быть если нам необходимо сделать выборку по каким-нибудь критериям?
Вот здесь NHibernate мне понравился множеством вариантов реализации данной задачи, разной степени простоты, гибкости и скорости :-) Отсортировано от более высокоуровневых вещей к более низкоуровневым:
- LINQ to NHibernate
- HQL
- ICriteria API
- Native SQL
var result = from x in Session.Linq<Product>()Теоретически самый простой и удобный способ, но возникают проблемы: в версии NHibernate 2.x транслятор с LINQ реализован не полностью, многие операции тупо не работают, в версии 3.0, вышедшей буквально месяц назад, вроде доделали, но у меня не было возможности потестировать, на проекте используется версия 2.1.2; И та же бодяга, что и с LINQ-to-SQL - при сложных запросах генерится большой и медленно работающий SQL, для критичных к скорости частей приложения LINQ to NHibernate не подходит.
where x.ShouldDisplay == visibility && x.Alignments.Any(z => alignmentIds.Contains(z.Id))
orderby x.SortOrder
select x
Hibernate Query Language - универсальный независимый от базы данных язык запросов
var result = sess.CreateQuery("select custОчень похож на SQL, но удобнее, запросы получаются короче, плюс независимость от базы данных, плюс генерит неплохой SQL-код. Недостатки: ошибки в запросах обнаруживаются только во время выполнения
from Product prod,
Store store
inner join store.Customers cust
where prod.Name = 'widget'
and store.Location.Name in ( 'Melbourne', 'Sydney' )
and prod = all elements(cust.CurrentOrder.LineItems)");
IList cats = sess.CreateCriteria(typeof(Cat))Более низкоуровневая вещь, позволяет поэтапное построение выражений, менее подвержен ошибкам, чем HQL, независимость от базы данных. Недостатки: Сложнее HQL и менее гибкий, есть непонятные ограничения. К примеру, мне не удалось сделать банальную проекцию так, как я хотел: выбрать определенные поля с одной таблицы и соединить с коллекцией элементов из другой.
.Add( Expression.In( "Name", new String[] { "Fritz", "Izi", "Pk" } ) )
.Add( Expression.Disjunction()
.Add( Expression.IsNull("Age") )
.Add( Expression.Eq("Age", 0 ) )
.Add( Expression.Eq("Age", 1 ) )
.Add( Expression.Eq("Age", 2 ) )
) )
.List();
Самый низкий уровень, но всё равно выше чем чистый SQL.
var result = sess.CreateSQLQuery("SELECT c.ID, NAME, BIRTHDATE, DOG_ID, D_ID, D_NAME FROM CATS c, DOGS d WHERE c.DOG_ID = d.D_ID")Преимущества: максимальный уровень контроля над получающимся SQL кодом, максимально развязаны руки для оптимизации, возможность преобразования полученных строк из базы в data objects, в том числе сложные преобразования с коллекциями и связями (всё как у более высокоуровневых методов). Недостатки: сложно, отлов ошибок только во время выполнения, здесь уже имеем зависимость от конкретной базы данных. Я наиболее плотно работал именно с Native SQL, там много интересных возможностей, в ближайшее время планирую написать отдельную статью про него.
.AddEntity("cat", typeof(Cat))
.AddJoin("cat.Dog");
Если что-то хотите узнать подробнее, пишите в комментариях в блоге!
1) Репозиторий не пишется, он генерится
2) Он-то как раз к сожалению не все команды LINQ поддерживает, поэтому часто приходится переходить на более низкий уровень
3) Да, проблем нет, просто такая особенность. Собственно в LINQ-to-SQL довольно сложно менять/писать свой mapping, если возникла необходимость отойти от автоматически сгенерированного кода, а в NHibernate код классов аккурантый и понятный, это понравилось
Repository<\T> where T :new()
{
public IList<\T> GetAll()
public T GetById();
public Update(T entity);
etc.
}
что избавляет от написания репозитория на каждую сущность
2) Для организации запросов есть еще Linq2Nhibernate, зависший в первом релизе и кажется больше не развивающийся, но в принципе он со своими обязанностями справляется
3) Отсутствие генератора data objects конечно неприятно, особенно когда таблиц в БД дохрена, но в принципе можно сгенерить в студии классы для EF и выкинуть из них ненужные фиксапы и т.п.