Artigo do tipo Tutorial
Recursos especiais neste artigo:
Conteúdo sobre boas práticas.
NHibernate – Mapeamento de Herança e Gerenciamento de Sessões na Web
Na última edição falamos sobre a arquitetura, conceitos e recursos do NHibernate, explicando como o NHibernate trabalha e como funcionam seus principais recursos. Neste artigo veremos a aplicação prática destes conceitos em uma aplicação simulando um cenário que seja o mais próximo possível da realidade. Construiremos um exemplo onde aplicaremos os conceitos de mapeamento de herança, gerenciamento de sessões, mapeamento de campos calculados, mapeando listas com Bags, Lists e Sets e uso de proxies.


Em que situação o tema é útil

Este tema é útil a qualquer equipe que trabalhe com NHibernate ou que tenha a intenção de utilizá-lo em seus projetos. Com este artigo o leitor poderá tirar melhor proveito do NHibernate em seus projetos, aumentando a qualidade de seu código, sua produtividade e performance.

Frameworks ORM vêm sendo utilizados largamente no mercado de software, sendo NHibernate e o Entity Framework os mais utilizados. Por outro lado, é muito comum vermos a subutilização dos recursos dos mesmos, onde os desenvolvedores utilizam apenas os recursos básicos destes frameworks, abrindo mão de benefícios como produtividade e manutenibilidade.

Neste artigo vamos abordar de forma prática alguns recursos pouco falados normalmente como mapeamento de herança, proxies e gerenciamento de sessão.

Para isso, vamos criar um exemplo baseado no cenário de uma loja de roupas que precisa registrar vendas de seus produtos. Diante disso, vamos considerar os seguintes requisitos:

1. Cada venda é registrada sob um pedido.

2. Cada pedido pode conter um ou mais itens.

3. Cada item possui uma quantidade vendida e um valor unitário de venda e está relacionado a um único produto.

4. Cada produto por sua vez possui um nome e um valor de compra.

5. Cada pedido possui um único comprador, sendo que atualmente este pode ser um cliente comum, um revendedor ou um funcionário.

6. Clientes comuns não possuem desconto.

7. Funcionários e revendedores possuem desconto, a ser informado no momento de seu respectivo cadastro.

8. Apenas pessoas jurídicas podem ser revendedores

9. Os clientes comuns precisam ter registrado no sistema seu nome, endereço e CPF.

10. Os funcionários precisam ter registrado seu nome, endereço, cpf, cargo, salário e respectivo percentual de desconto.

11. Os revendedores precisam ter registrado no sistema seu nome, endereço, CNPJ e respectivo percentual de desconto.

12. O sistema precisa ser WEB

13. Como o desconto de Revendedores e Funcionarios podem ser alterados ao longo do tempo, torna-se necessária a gravação do percentual de desconto aplicado a cada pedido.

Diante disso, teríamos que ter no nosso sistema a possibilidade de cadastramento das seguintes entidades:

· Pedido com um ou mais Itens

· Produtos

· Clientes

· Funcionários

· Revendedores

Analisando estes requisitos, teríamos o diagrama da Figura 1 ilustrando o domínio de nossa aplicação.

abrir imagem em nova janela

Figura 1. Modelo de domínio da aplicação

Observando o diagrama da Figura 1 nos deparamos com dois cenários de herança, onde no primeiro temos o nosso BaseModel, contendo apenas o seu respectivo ID. Desta forma padronizamos que nossa aplicação não terá identificados naturais ou compostos, sendo que todo objeto terá obrigatoriamente que ter um ID.

No segundo cenário de herança temos a classe Pessoa sendo herdada por Cliente, Funcionario e Revendedor. Esta herança visa atender ao requisito número 5, onde o pedido possui apenas um comprador, que pode ser um cliente, funcionário ou revendedor. Como estes três elementos possuem atributos em comum, abstraímos tais atributos em uma classe base chamada Pessoa, restando às classes especialistas apenas os atributos específicos de cada uma delas.

Desta forma conseguimos fazer com que o Pedido esteja vinculado à uma Pessoa qualquer, não importando para o mesmo se trata-se de um cliente, de um funcionário ou de um revendedor. Além disso, podemos observar também que apenas funcionário e revendedor implementam a interface IDescontable. Esta interface visa identificar as entidades que são passíveis de recebimento de desconto na nossa aplicação.

Na relação entre Pedido e ItemPedido temos um relacionamento one-to-many, onde cada pedido pode ter 1 ou muitos itens enquanto que cada item está vinculado a apenas um pedido.

Esta é a arquitetura de nosso domínio. Para viabilizar esta aplicação vamos utilizar o NHibernate com uma base de dados MySQL. Para o mapeamento de nosso modelo vamos utilizar o FluentNHibernate, por ser mais transparente e limpo.

Devido ao fato de que o objeto Session do NHibernate não é thread-safe, ou seja, um único objeto não é preparado para lidar com requisições concorrentes, e que o mesmo foi feito para ter um ciclo de vida o mais curto possível, precisamos definir a estratégia de gerenciamento deste objeto na nossa aplicação. O NHibernate possui a classe de gerenciamento de contexto para web chamada WebSessionContext, que iremos utilizar. Esta classe nos ajuda a manter uma instância da session (do NH) atrelada ao ciclo de vida da requisição WEB, porém ela não faz tudo automaticamente, sendo que nós somos responsáveis por abrir e fechar a session, assim como anexar e desanexar a mesma do nosso WebSessionContext.

É muito comum vermos a implementação disto em um HttpModule, fazendo com que ao iniciar a requisição a session seja aberta e anexada ao contexto e ao final da requisição a mesma seja fechada e desanexada do mesmo. O único porém desta abordagem é que, caso seja feita uma requisição para uma página que não faz acesso á base de dados, vamos abrir e fechar uma sessão sem precisarmos utilizá-la.

Por este motivo, nossa estratégia será um pouco diferente. Vamos abrir nossa session e anexar a mesma ao contexto apenas quando for necessário e fechar a mesma no final de cada requisição (caso tenha sido necessário abri-la).

Outro ponto de decisão antes de iniciarmos a implementação é com relação ao mapeamento dos objetos. O NHibernate possui três tipos de mapeamento:

· Via XML – Arquivos XML externos à aplicação

· Via Custom Attributes – Atributos anotados diretamente no modelo

· Via Fluent API – Classes C# de mapeamento usando o pattern Fluent Interface

No nosso caso vamos optar pelo mapeamento via Fluent pelo fato do mesmo ser mais limpo, intuitivo e fácil de manter.

Uma vez tomadas as decisões iniciais, vamos à arquitetura da aplicação. Nossa aplicação precisa ser web, então faremos utilizando ASP.NET MVC. Visando separar as responsabilidades teremos inicialmente três bibliotecas, sendo uma para o projeto MVC, uma para nosso modelo de domínio e outra para nossa camada de persistência.

Para nosso exemplo utilizaremos o NHibernate 3.3.1, o FluentNH, o JQuery (versão mais nova) e o MySQL como base de dados.

Baseado na arquitetura discutida no tópico anterior, nossa solution terá três projetos:

· ASP.NET MVC – Projeto de interface com usuário, contendo os controllers e views de nossa aplicação. Este projeto depende do NHibernate, do Model e do Persistence.

· Class Library Model – Projeto contendo nossas classes de negócio e mapeamento das mesmas para a base de dados. Este projeto depende do NHibernate e do FluentNH.

· Class Library Persistence – Projeto contendo as classes que farão interface com a base de dados fazendo uso da infra estrutura do NHibernate. Este projeto depende do NHibernate e do FluentNH.

Criando a camada de persistência

A primeira coisa que vamos fazer é criar nossa camada de persistência, para isso crie um novo projeto ASP.NET MVC chamado NHibernatePratica e adicione uma class library no mesmo, chamada NHibernatePratica.Persistence.

O objetivo desta camada é criar a interface de comunicação da nossa aplicação com a base de dados, utilizando para isso o NHibernate. Esta camada terá três classes:

· NHSessionManager – Classe responsável por realizar o gerenciamento das sessões do NHIbernate.

· NHSessionFactoryManager – Classe responsável por realizar o gerenciamento da session factory do NHibernate.

· GenericRepository – Classe base de repositório que servirá para realização dos serviços básicos de CRUD.

Crie duas pastas neste projeto, chamadas NHibernate e Repository. Dentro da pasta NHibernate crie uma nova classe chamada NHSessionFactoryManager e altere o código da mesma para que fique como na Listagem 1.

Listagem 1. Classe Gerenciamento de session factory


  01     using System;
  02     using System.Collections.Generic;
  03     using NHibernate;
  04     using FluentNHibernate.Cfg;
  05     using FluentNHibernate.Cfg.Db;
  06     using NHibernate.Context;
  07     using System.Reflection;
  08     
  09     namespace NHibernatePratica.Persistence.NHibernate
  10     {
  11            public class NHSessionFactoryManager
  12            {
  13                   private static ISessionFactory _sessionFactory = null;
  14                   private static Object createLock = new Object();
  15             
  16                   public static ISessionFactory GetSessionFactory() { 
  17                       lock(createLock){
  18                           if(_sessionFactory == null){
  19                               FluentConfiguration config = 
                                   BuildConfiguration();
  20                               _sessionFactory =
                                   config.BuildSessionFactory();
  21                           }
  22                         }            
  23     
  24                         return _sessionFactory;
  25                    }
  26     
  27                  private static FluentConfiguration BuildConfiguration() {
  28                         FluentConfiguration config = Fluently.Configure()
  29                           .Database(MySQLConfiguration.Standard
  30                           .ConnectionString(c => 
                               c.FromAppSetting("connectionNH"))
  31                           .Driver("NHibernate.Driver.MySqlDataDriver")
  32                           .Provider
                              ("NHibernate.Connection.DriverConnectionProvider")
  33                           .Dialect("NHibernate.Dialect.MySQL5Dialect"))
  34                         .CurrentSessionContext<WebSessionContext>()
  35                         .Mappings(x => 
                              x.FluentMappings.AddFromAssembly
                             (Assembly.Load("NHibernatePratica.Model")));
  36     
  37                         //config.ExposeConfiguration(x =>
                             x.Properties.Add
                            (new KeyValuePair<string, string>
                            ("hbm2ddl.auto", "create")));            
  38     
  39                         return config;
  40                  }
  41           }
  42     }

Na Listagem 1 temos a nossa classe de gerenciamento de SessionFactory. A SessionFactory é o objeto mais caro do NHibernate, sendo sua inicialização um processo consideravelmente custoso para a aplicação. Por este motivo é importantíssimo garantirmos que teremos apenas uma instância de SessionFactory na nossa aplicação e é exatamente isso que esta classe faz. Na linha 13 temos um atributo do tipo ISessionFactory, privado e estático. Será neste atributo que teremos armazenada nossa única instância. Na linha 14 temos um object chamado lock, este objeto é utilizado na linha 17 apenas para garantir que, diante de um cenário de concorrência de usuários, não corramos o risco de termos dois acessos simultâneos solicitando a criação do session factory. Desta forma, travamos o bloco de criação do session factory para que ele seja executado apenas por um usuário de cada vez, garantindo que jamais acontecerá de dois usuários criarem uma session factory ao mesmo tempo. Na linha 18 nós verificamos se o sessionfactory não está criado e então carregamos nossa configuração através do método BuildConfiguration e em seguida criamos nosso session factory.

Na linha 27 temos a implementação do buildConfiguration, onde carregamos o database (linha 29), o connection string (linha 30), o driver (linha 31), o provider (linha 32) e o dialeto (linha 33) utilizados. Na linha 30 é interessante observar que poderíamos definir o connection string diretamente no parâmetro do método, mas o próprio FluentNH nos fornece um método para carregar esta configuração diretamente do AppSettings, através do FromAppSetting onde precisamos apenas informar o nome do parâmetro onde está armazenado o connection string.

Na linha 34 temos uma configuração importantíssima, onde determinamos o contexto de gerenciamento de conexões, indicando a classe WebSessionContext. Com isso, durante o ciclo de vida das requisições, o NHibernate irá armazenar a nossa session atual diretamente no objeto HttpContext via reflection.

Na linha 35 temos a definição de onde estarão nossos modelos mapeados, onde neste caso definimos que os mesmos virão do assembly NHibernatePratica.Model. Apenas para fins de exemplo o mesmo foi definido de forma fixa, mas nada nos impede de poder configurar isso no XML da aplicação ou mesmo passar como parâmetro para o método de configuração.

Na linha 37 temos um método que altera a propriedade do NHiberante responsável por definir a estratégia de geração do DDL. Com esta configuração estamos definindo que queremos que o NHibernate crie toda a estrutura da nossa base de dados, baseado no nosso mapeamento. Este código está comentado, pois ele só será utilizado uma vez, apenas para que não tenhamos que criar todos os tables manualmente.

...

Quer ler esse conteúdo completo? Tenha acesso completo