MvcExtensions: Quick Start Project Template

Очень часто, когда кто-то пытается начать использовать MvcExtensions у него появляются вопросы: с чего начать и как это использовать. Чтобы этих вопрос стало меньше создал проект ProjectTemplate.

Что внутри?

Все это чудо настроено и просто работает

Как использовать?

  1. Скачать (для MVC 4, MVC 3)
  2. Распаковать*
  3. Открыть проект и добавить вашу логику
  4. ???
  5. RROFIT!

*После распаковки я рекомендую создать git репозиторий и закоммитить туда первоначальную структуру

 > git init
 > git add .
 > git commit -m 'Initial project strucure'

PS

Жду ваших предложений по развитию проекта.

Как расширять и актуализировать знания

Очень часто меня спрашивают (как знакомые, так и на собеседованиях), как я актуализирую свои знания. Рецепт очень прост.

  1. Читайте блоги. В блогах всегда много новой информации, оттуда можно почерпнуть много новых идей и информации, которые еще не успели стать меинстримом. В моем списке подписок больше 100 блогов.

  2. Ведите блог. Так вы сможете найти единомышленников, а вместе легче развиваться.

  3. Участвуйте в конференциях. Не обязательно быть докладчиком, можно быть внимательным слушателем и не стесняйтесь участвовать в кулуарных обсуждениях.

  4. Станьте активным участником тематических групп, к примеру DotNetConf, и сервисов ВиО (вопросы и ответы), таких как StackOverflow.com. Отвечая на интересные вопросы вы можете узнать для себя что-то новое.

  5. Ищите новые интересные компоненты, библиотеки и фреймворки. Я регулярно просматриваю что нового в NuGet или что происходит на GitHub

  6. Участвуйте в Open Source проектах. Не обязательно быть коммитером, достаточно просто быть в сообществе. Изучайте код, сообщайте об ошибках, пишите в листы рассылки и читайте их

  7. Читайте книги.

Чего вам не хватает в MvcExtensions?

Скорее всего вы знаете, что кроме NHibernate я еще занимаюсь проектом MvcExtensions, недавно была выпущена версия 2.5

Вот примерный план на версию 3.0

  • Build with psake
  • Integration with WebActivator - this means that we will remove Bootstrapper tasks
  • Integration with native MVC IoC adapters
  • Conventional internationalization and localization
  • Generic model binders

План лежит тут и постепенно будет пополняться и со временем выполнятся. Список уже реализованных изменений тут.

Я хочу узнать у вас, как у пользователей MvcExtensions, чего не хватает именно ВАМ. Пишите в комментариях.

NHibernate: маленькая хитрость при работе с Oracle или PostgreSQL

В ADO.NET провайдерах для Oracle, PostgreSQL и, возможно, других есть одна неприятная особенность, которая может сказаться на производительности вашего приложения, если вы запрашиваете у сервера большие объемы данных: они не кэшируют вызовы метода IDataReader.GetOrdinal. Как оказалось это очень критично для NHibernate, но, к счастью, разработчики NHibernate (а точнее Hibernate) эту проблему заметили и уже решили.

Но эта фича осталась незамеченной и почти не задокументированной.

Для того, чтобы в NHibernate включить кэширование вызовов IDataReader.GetOrdinal необходимо в hibernate.cfg выставить опцию “adonet.wrap_result_sets” в значение “true”:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
    <!-- other options -->
    <session-factory name="MySessionFactory">
        <!-- other session factory options -->
        <property name="adonet.wrap_result_sets">true</property>
    </session-factory>
</hibernate-configuration>

C помощью FluentNHibernate это делается так:

var config = Fluently.Configure()
    .ExposeConfiguration(c => c.SetProperty(NHibernate.Cfg.Environment.WrapResultSets, "true"))
    .Database(db)
    /* other configuration */
    .BuildConfiguration();

Метод ExposeConfiguration добавляет действия, которые будут вызваны над объектом NHibernate.Cfg.Configuration при вызове метода BuildConfiguration. Таким образом код выше будет аналогичен следующему:

var config = Fluently.Configure()
    .Database(db)
    /* other configuration */
    .BuildConfiguration();

config.SetProperty(NHibernate.Cfg.Environment.WrapResultSets, "true");
Ссылки по теме

NHibernate: как хранить иерархические сущности (деревья) в базе

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

    class Tree {
        int Id;
        Tree Parent;
    }

Но, это только на первый взгляд.

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

C такой задачей ни одна ORM уже не справится: в лучшем случае вы получите SELECT N+1. Для решения этой проблемы вам придется написать кастомный зависящий от конкретной базы запрос: рекурсивные запрос с WITH в Microsoft Sql Server; запрос с CONNECT BY PRIOR в Oracle; либо специальную хранимую процедуру.

В статье “How to map a tree in NHibernate" Gabriel Schenker предлагает альтернативный вариант: необходимо добавить таблицу, в которой для каждой сущности мы будем хранить ссылки на всех ее предков и всех ее потомков. Потомки будут отображаться на коллекцию Descendants, а предки на коллекцию Ancestors. Обе коллекции many-to-many:

    class Tree {
        int Id;
        Tree Parent;
        IEnumerable<Tree> Children;
        IEnumerable<Tree> Ancestors;
        IEnumerable<Tree> Descendant;
    }

С такой структурой очень легко обращаться.

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

Brandy.Grapes

Brandy.Grapes - это небольшой (всего 3) набор библиотек, который позволяет легко и непринужденно работать с сохраняемыми иерархическими сущностями в NHibernate.

  • Необходимо установить библиотеку через nuget (поддерживается NHibernate By Code и FluentNHibernate):

    > install-package Brandy.Grapes.NHibernate
    

    или

    > install-pacakge Brandy.Grapes.FluentNhibernate
    
  • Унаследовать вашу сущность от TreeEntry`1

    public class MySuperTree : TreeEntry<MySuperTree> {
        public virtual int Id { get; set; }
    
        public virtual string Name { get; set; }
    }
    
  • Наконец, написать маппинг, к примеру, для FluentNHibernate:

    using Brandy.Grapes.FluentNHibernate;
    public class MySuperTreeMap : ClassMap {
        public MySuperTreeMap() {
            Id(x => x.Id);
            Map(x => x.Name);
    
            this.MapTree("MySuperTreeHierarchy"); // вся магия происходит здесь
        }
    }
    
  • Наслаждаться: теперь Brandy.Grapes будет отслеживать изменения в иерархии и корректно сохранять их в базу.

Как справедливо заметил Денис Боровнев, при изменении иерархии необходимо из базы подгрузить всю иерархию для данного элемента, чтобы правильно обновить связи. Если у вас в проекте иерархические сущности изменяются достаточно часто, то можно отключить изменение иерархии из кода и обновлять связи через базу. Существует несколько способов:

  • Вызывать хранимую процедуру (по триггеру, или из кода), для обновления иерархических связей:

    -- пример для Microsoft Sql Server
    CREATE PROCEDURE [dbo].[FillHierarchy] (@table_name nvarchar(MAX), @hierarchy_name nvarchar(MAX))
    AS
    BEGIN
        DECLARE @sql nvarchar(MAX), @id_column_name nvarchar(MAX)
        SET @id_column_name = '[' + @table_name + '_ID]'
        SET @table_name = '[' + @table_name + ']'
        SET @hierarchy_name = '[' + @hierarchy_name + ']'
    
        SET @sql = ''
        SET @sql = @sql + 'WITH Hierachy(CHILD_ID, PARENT_ID) AS ( '
        SET @sql = @sql + 'SELECT ' + @id_column_name + ', [PARENT_ID] FROM ' + @table_name + ' e '
        SET @sql = @sql + 'UNION ALL '
        SET @sql = @sql + 'SELECT e.' + @id_column_name + ', e.[PARENT_ID] FROM ' + @table_name + ' e '
        SET @sql = @sql + 'INNER JOIN Hierachy eh ON e.' + @id_column_name + ' = eh.[PARENT_ID]) '
        SET @sql = @sql + 'INSERT INTO ' + @hierarchy_name + ' ([CHILD_ID], [PARENT_ID]) ( '
        SET @sql = @sql + 'SELECT [CHILD_ID], [PARENT_ID] FROM Hierachy WHERE [PARENT_ID] IS NOT NULL '
        SET @sql = @sql + ') '
    
        EXECUTE (@sql)
    END
    GO
    
  • Для каждой иерархии создать View (тот же запрос, что и в хранимой процедуре) и отобразить связи Ancestors и Descendants на эту View:

    -- Пример для Microsoft Sql Server
    CREATE VIEW [MySuperTreeHierarchy]
    AS
        WITH Hierachy (CHILD_ID, PARENT_ID) 
        AS 
        (
            SELECT [MySuperTree_ID], [PARENT_ID] FROM [MySuperTree] AS e
            UNION ALL
            SELECT e.[MySuperTree_ID], e.[PARENT_ID] FROM [MySuperTree] AS e 
                INNER JOIN Hierachy AS eh ON e.[MySuperTree_ID] = eh.[PARENT_ID]
        )
    
        SELECT [CHILD_ID], [PARENT_ID] FROM Hierachy WHERE [PARENT_ID] IS NOT NULL
    GO
    

Оба этих подхода обладают большей гибкостью и надежностью, чем иерархические запросы на чистом SQL из кода.

PS: интерфейс абстрактного класса TreeEntry`1:

    public abstract class TreeEntry<T> where T : TreeEntry {
        public virtual T Parent { get; set; }

        public virtual IEnumerable<T> Children { get; }

        public virtual IEnumerable<T> Ancestors { get; }

        public virtual IEnumerable<T> Descendants { get; }

        public virtual void AddChild(T child);

        public virtual void RemoveChild(T child);
    }

PPS: для EF такое сделать также возможно, но т.к. EF не поддерживает скрытие коллекций за интерфейсом IEnumerable`1, я не стал выкладывать реализацию для EF в открытый доступ.