Bom dia amigos, hoje para fechar o assunto de usar HQL para operações em massa, vou mostrar como criar um Extension Method para adicionar funções do Banco de Dados dinamicamente para serem usadas em uma HQL.

No último post sobre o assunto, eu falei um pouco das vantagens de se usar o HQL para atualizações em massa (Bulk-Insert, Bulk-Update, Bulk-Delete). Quem adotar esta prática, pode se deparar algumas vezes com a necessidades de acessar uma função do sistema de banco de dados, seja esta nativa ou criada pela usuário.

Como da última vez, não vou entrar nos méritos sobre se é bom ou não manter regras no banco de dados, ou o fato de que passar a usar uma função do banco de dados no seu Comando pode lhe custar os benefícios da “fácil portabilidade” para outras plataformas de banco de dados. Em principal porque na real a grande maioria das empresas que eu já vi usando um ORM nunca mudou de banco de dados (nem para testar) e acredito fortemente que jamais o farão, mas isso é outra história. Afinal, um ORM tem suas qualidade muito além da simples possibilidade de portabilidade.

Imaginemos o cenário

Eu possuo um sistema de atendimento de uma rede de lanchonetes, onde são inseridos cerca de 150 pedidos por minuto na base de dados. Para tornar o sistema mais rápido para consulta e fechamentos, eu separo a tabela de pedidos entre “produção” (pedidos dos últimos 3 dias) e “histórico” (os pedidos dos últimos 30 dias, exceto os que estão em produção) – vamos assumir que os mais antigos ainda vai para um DW depois, nosso foco são as tabelas de produção e de histórico.

Então, para poder mover os dados de forma mais eficiente, nós usamos o HQL, pois é bem mais rápido para isto. Porém a tabela de histórico possui um campo novo, que é o campo “MovedDate”, que guarda a data em que o dado foi movido para a tabela de histórico.

(peço novamente que abstraiam se esta é ou não a melhor maneira de fazê-lo, vamos apenas focar em demonstrar a ideia).

Assim temos um comando similar a:

            const string hql = @" insert into OrdersHistory "
                     + " (OrderId, Decription, Amount, Date, MovedDate) "
                     + " Select Id, Description, Amount, Date, ?????, "
                     + " from Orders where Date <= :date ";

            _session.CreateQuery(hql)
                    .SetDateTime("date", DateTime.Now)
                    .ExecuteUpdate();

O que devemos informar para o campo MovedDate? Se você já tentou reproduzir a situação acima já deve ter percebido que o não funciona enviar os dados como parâmetro – pois ele não será substituído, nem concatenar um ‘2012-01-01 12:00:00’ na instrução pois o HNibernate irá reclamar que os tipo String e DateTime não são compatíveis.

Então para isto, vamos usar a própria função do SQLServer GETDATE():

            const string hql = @" insert into OrdersHistory "
                     + " (OrderId, Decription, Amount, Date, MovedDate) "
                     + " Select Id, Description, Amount, Date, GetDate(), "
                     + " from Orders where Date <= :date ";

            _session.CreateQuery(hql)
                    .SetDateTime("date", DateTime.Now)
                    .ExecuteUpdate();

Porém ao fazer isto, você irá receber uma mensagem similar a:

 "NHibernate.QueryException : No data type for node: MethodNode (SUA_FUNCAO_SQL) [ select SUA_FUNCAO_SQL(ABC) from XXXXX ]  at NHibernate.Hql.Ast.ANTLR.Tree.SelectClause.InitializeExplicitSelectClause"

Isto acontece pois o NHibernate não tem conhecimento da função enviada, tenta tratá-la internamente, e acaba estourando uma exceção. Para que isto não ocorra, basta registrar no NHibernate a função que queremos usar. Há várias formas de fazê-lo de forma estática, porém aqui vamos abordar como fazê-lo dinamicamente.

Para registrarmos nossa função dinamicamente ao NHibernate, vamos criar um Extension Method para o classe Dialect, que terá o seguinte código:

    public static class DialectExtensions
    {
        private static readonly MethodInfo registerFunctionMethod = typeof(Dialect).GetMethod("RegisterFunction", BindingFlags.Instance | BindingFlags.NonPublic);

        public static void RegisterFunction(this Dialect dialect, String name, ISQLFunction function)
        {
            registerFunctionMethod.Invoke(dialect, new Object[] { name, function });
        }

       public static void RegisterFunction(this ISessionFactory factory, String name, ISQLFunction function)
       {
           registerFunctionMethod.Invoke(GetDialect(factory), new Object[] { name, function });
       }

       public static Dialect GetDialect(this ISessionFactory factory)
       {
           return((factory as SessionFactoryImpl).Dialect);
       }
   }

Agora basta passar à nossa SessionFactory a informação necessária para resolver nosso método:

Session.SessionFactory.RegisterFunction("GETDATE", new NoArgSQLFunction("GETDATE", NHibernateUtil.DateTime, true));

Agora nosso insert irá funcionar perfeitamente e sem problemas. Este recurso pode ser usado para diversas outras situações e para criar também “atalhos” próprios para diversas situações, de uma lida sobre Dialect Functions na documentação do Hibernate para ter uma visão mais ampla do que você poderá construir.

Espero ter podido contribuir um pouco mais para facilitar o uso de operações em massa com HQL nos projetos de vocês.

Enjoy!