Oct 29, 2010

Что нового в NHibernate 3

9 октября был выпущен NHibernate 3 beta 1. Решил посмотреть что в нем нового. Для этого быстренько набросал модель:

2010-10-29_1826

Для маппинга я буду использовать ConfOrm. Его настройки:

public class DomainMapper
{
    public HbmMapping GenerateMappigs()
    {
        IEnumerable<Type> domainEntities = this.GetDomainEntities();

        var relationalMapper = new ObjectRelationalMapper();
        relationalMapper.TablePerConcreteClass(domainEntities);
        relationalMapper.Patterns.PoidStrategies.Add(new NativePoidPattern());
        relationalMapper.Cascade<Category, Product>(Cascade.Persist);
        relationalMapper.ManyToMany<Category, Product>();
        relationalMapper.Cascade<Order, Product>(Cascade.Persist);

        var mapper = new Mapper(relationalMapper);
        mapper.PatternsAppliers.RootClass.Add(new TableNamesApplier());
        mapper.Class<User>(x => x.Property(y => y.VeryLongProperty, map => map.Lazy(true)));
        HbmMapping mapping = mapper.CompileMappingFor(domainEntities);
        return mapping;
    }

    /// <summary>
    /// Gets all objects that are inherited from <see cref="BaseEntity"/>.
    /// </summary>
    private IEnumerable<Type> GetDomainEntities()
    {
       Assembly domainAssembly = typeof(BaseEntity).Assembly;
       IEnumerable<Type> domainEntities = from t in domainAssembly.GetTypes()
                                          where t.BaseType == typeof(BaseEntity) && !t.IsGenericType
                                          select t;
            return domainEntities;
    }
}

В предыдущих постах говорили что стоит добавлять и схему базы данных. Скажу честно, я её не создавал, а попросил это сделать NHibernate. Поэтому я не буду её тут приводить Smile

Чтобы сгенерировать базу данных можно использовать SchemaExport:

new SchemaExport(cfg).Execute(true, true, false);

Дальше по порядку:

1. И конечно самое ожидаемое – Linq Provider

Чтобы попробовать его я решил попробовать запросы, которе прошлый провайдер (основанный на ICriteria) не мог выполнить.

  1. (from u in session.Query<User>()
    where u.Orders.Count > 5
    select u).Count()
    Выполняет следующий sql:
    select
        cast(count(*) as INT) as col_0_0_
    from
        Users user0_
    where
        (
            select
                cast(count(*) as INT)
            from
                Orders orders1_
            where
                user0_.Id=orders1_.OrderUser
        )>@p0;
    @p0 = 5 
  2. Интересно было так же посмотреть как выбираются анонимные объекты, запрос:
    var firstName = (from u in session.Query<User>()
                     select new { u.FirstName }).FirstOrDefault();
    Выполняет
    select
        TOP (@p0) user0_.FirstName as col_0_0_
    from
        Users user0_;
    @p0 = 1 [Type: Int32 (0)]
    Я думаю комментарии тут не нужны, тем более если еще вспомнить с каким количеством баз данных умеет работать NHibernate и насколько это круто Smile
  3. Но вот такой запрос уже выполнить не получилось:
     var rows = (from u in session.Query<User>()
                 let order = u.Orders.FirstOrDefault()
                 where u.Orders.Count > 5 && order != null && order.Id == 4
                 select u).ToList();

2. Fluent синтаксис для конфигурации SessionFactory

Для этого примера мне потребовалась следующая кофигурация:

DomainMapper mapper = new DomainMapper();
HbmMapping generatedMappigs = mapper.GenerateMappigs();

var cfg = new Configuration();
cfg.SessionFactory()
        .Proxy.Through<ProxyFactoryFactory>()             
        .Integrate
            .Using<MsSql2008Dialect>()
            .AutoQuoteKeywords()
            .Connected
                .By<SqlClientDriver>()
                .ByAppConfing("connectionString")
            .CreateCommands
                .ConvertingExceptionsThrough<SQLStateConverter>();
cfg.SetProperty("show_sql", "true"); // I haven't found how to configure them
cfg.SetProperty("format_sql", "true");
cfg.AddDeserializedMapping(generatedMappigs, "WhatsNew");

чтобы использовать такой синтаксис необходимо подключить неймспейс NHibernate.Cfg.Loquacious. Конечно название они выбрали... Но дословно гугл переводит как "словоохотливый". Может оно и правильно, но я раньше нигде не встречал. Наиболее полный пример всех настроек я смог найти у Fabio Maulo.

Так же конфигурировать можно с помощью лямбда выражений. Но в примере я их не использовал. Подробнее можно почитать тут.

3. QueryOver синтаксис

Это API позволяет упросить написание ICriteria запросов. Во первых больше нет необходимости использовать строки (раньше это решалось с помощью библиотеки NHLambdaExtensions), во вторых теперь возможны сложные условия внутри одного вызова через &&, ||, в третьих упрощена работа с алиасами (можете посмотреть насколько это было не просто в предыдущей версии), в четвертых работа с проекциями теперь гораздо более читабельна, и в пятых мне наверно стоило сделать из этого абзаца n-тых список c номерами Smile.

Приведу простой примерчик как теперь можно делать запросы:

var query = session.QueryOver<User>()
                    .Select(x => x.FirstName, x => x.LastName)
                    .WhereRestrictionOn(x => x.FirstName).IsLike("first", MatchMode.Anywhere)
                    .WhereRestrictionOn(x => x.LastName).IsLike("last")
                    .List<object[]>()
                    .Select(properties => new
                                                {
                                                    FirstName = properties[0],
                                                    LastName = properties[1]
                                                });

Это выполнит запрос:

SELECT
    this_.FirstName as y0_,
    this_.LastName as y1_
FROM
    Users this_
WHERE
    this_.FirstName like @p0
    and this_.LastName like @p1;
@p0 = '%first%' [Type: String (4000)], 
@p1 = 'last' [Type: String (4000)]

Ну и обращаться к результатам можно как и к обычным анонимным объектам:

foreach (var user in query)
{
    Console.WriteLine(user.FirstName);
}

Хорошая документация о том как использовать этот синтаксис можно найти тут.

4. Lazy Property

Не знаю почему эта фича была реализована только сейчас, но теперь она работает, так если у сущности есть какое то поле, которое не хотелось бы загружать каждый раз, можно пометить его как Lazy. Но с этой штукой надо быть осторожно, потому как если из базы данных достается список таких объектов, то вероятнее всего весь этот список будет перебираться (иначе зачем его было доставать) это может стать причиной Select N+1.  Ну демонстрация очень простая Smile :

var user = session.Get<User>(1);
string veryLongProperty = user.VeryLongProperty;

И запросы:

/* 1 */
    SELECT
        user0_.Id as Id4_0_,
        user0_.FirstName as FirstName4_0_,
        user0_.LastName as LastName4_0_
    FROM
        Users user0_
    WHERE
        user0_.Id=@p0;
    @p0 = 1 [Type: Int32 (0)]
/* 2 */
    SELECT
        user_.VeryLongProperty as VeryLong4_4_
    FROM
        Users user_
    WHERE
        user_.Id=@p0;
    @p0 = 1 [Type: Int32 (0)]

Примечательно то, что благодаря этой фиче смогли реализовать Lazy Load one-to-one связи которой раньше не было. 

5. Нет зависимости от log4net

Последняя версия log4net вышла в 2003 году… Видимо поэтому многие сейчас отказываются от него. В общем теперь вам нет необходимости таскать log4net за собой, его даже нет в архиве со всеми файлами NHibernate. Вот пример как настраивать для работы с другими логгерами.

6. Остальное

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

  • [NH-2309] - Add support for Future() with the new Linq provider
  • [NH-626] - Adding XmlDoc to NH types
  • [NH-2135] - Compatible with Mono
  • [NH-2256] - Add support for user-provided extensions to the Linq provider

В общем прогресс проекта виден. Я боялся что EF задавит хибер за счет майкросовской рекламы, но видимо нет, слишком уж он хорош Smile.