A criação de aplicações que fazem acesso a dados é uma realidade no mercado já há algum tempo. Essas aplicações trazem um problema, criado pela forma diferente como os bancos de dados e as linguagens orientadas a objetos tratam os elementos: enquanto os bancos de dados são chamados relacionais, baseando-se nas relações entre as entidades, as linguagens orientadas a objetos possuem os objetos. Para que a conexão entre os dois funcionem, é preciso que seja feita uma ponte entre as entidades relacionais e os objetos, ou classes. É aí que entra o NHibernate e outros projetos com o mesmo fim, como veremos ao longo desse artigo.

Conhecendo o NHibernate e o Fluent NHibernate

O NHibernate é um dos mais antigos mapeadores objeto-relacional disponíveis para o desenvolvimento .NET. Muitas pessoas acabam tendo dificuldades na transição entre a programação para bancos de dados relacionais e a programação orientada a objetos. Isso faz com que os ORMs, como o NHibernate, sejam excelentes opções para retirar essas dificuldades e permitir o foco naquilo que é realmente importante e fácil para o desenvolvedor, que é o código Orientado a Objetos.

Entretanto, atualmente os ORMs são um tópico bastante quente no mundo de desenvolvimento. Então, porque escolher o NHibernate, quando existem tantas outras opções, algumas inclusive mais simples de utilização que ele? Primeiramente, o NHibernate traz alguns recursos que não são encontrados comumente em outros ORMs, o que faz dele uma das opções mais completas do mercado. Ao mesmo tempo em que é completo, entretanto, ele não traz tudo que é necessário, obedecendo a máxima do “faça menos bem do que mais mal”. Por isso, existem alguns pontos que permitem extensões interessantes ao projeto original, como o próprio Fluent NHibernate, que veremos mais adiante.

Um dos principais pontos a favor do NHibernate é a quantidade de bancos de dados suportados com pouco ou nenhum problema, englobando os principais, como SQL Server, Oracle e MySQL, além de todos os demais que aceitam ODBC (Open Database Connectivity) ou OLE DB (Object Linking and Embedding Database). Outro ponto que merece destaque é o número de formas de mapeamento possíveis, que não são poucas, estando entre elas a utilização do mapeamento “fluente”, utilizando o Fluent NHibernate.

O NHibernate traz outros pontos interessantes, como a facilidade de instalação. Enquanto o LINQ to SQL (outro ORM), por exemplo, vem embutido em aplicações .NET, o NHibernate não possui essa facilidade. Porém, ele permite a instalação através de várias instâncias, como o NuGet, a mais simples delas, sem contar os pacotes no GitHub e no SourceForge, que são os recomendados para quem deseja estender ou mesmo modificar o NHibernate.

Nesse contexto, o que é o Fluent NHibernate? Conforme comentamos, o NHibernate possui várias formas possíveis de realizarmos o mapeamento objeto-relacional, e o Fluent NHibernate é apenas uma dessas formas. Ele implica na utilização do chamado mapeamento fluente, devido ao seu nome, e é muito simples de ser utilizado. Se formos realizar uma comparação entre o mapeamento via XML, por exemplo, e o Fluent, veremos que a simplicidades do último é o que há de melhor em mapeamento objeto-relacional no NHibernate. Devido à sua simplicidade, entretanto, o Fluent NHibernate pode trazer alguns problemas no mapeamento de relações “N para N”, mais complexas, embora não traga nenhum problema nas demais.

Como podemos ver, o NHibernate unido à extensão Fluent forma uma ferramenta extremamente interessante para o mapeamento objeto-relacional. É importante que saibamos o funcionamento delas na prática, e por isso veremos como realizar uma simples aplicação que realiza a inserção de dados em uma base de dados utilizando o mapeamento objeto-relacional com esses dois projetos. Veremos ainda alguns outros conceitos simples, que serão explicados oportunamente.

Preparando o projeto em camadas

O desenvolvimento de software em camadas é um dos padrões de projeto mais utilizados no mundo atualmente. Esse tipo de desenvolvimento consiste na separação dos elementos em camadas, de acordo com a sua ação básica. Uma aplicação qualquer normalmente irá consistir de pelo menos 3 camadas:

  • Aplicação: a camada onde se encontra a aplicação, o que o usuário estará enxergando. Toda a lógica relacionada ao que o usuário enxerga deve estar nessa camada;
  • Dados: a camada responsável pelo acesso aos dados, onde quer que eles estejam. Normalmente faz uso de algum padrão, como o Repository, para facilitar esse acesso;
  • Domínio: a camada de domínio é responsável pela modelagem do software. Por exemplo, se temos um software que cadastrará cliente, é nessa camada que essa classe Cliente irá estar, juntamente com outras classes da regra de negócios da aplicação.

A forma como fazemos uso desse padrão de projeto é totalmente definida pelo desenvolvedor. Normalmente, utilizamos mais de um projeto dentro de uma mesma solução, fazendo referências internas quando necessário. Em outros casos, para evitar esse tipo de referenciação, utilizamos diretórios dentro de um mesmo projeto. O resultado final é praticamente o mesmo: a separação entre as partes de um software, mantendo-o mais organizadamente, o que irá facilitar alterações futuras. Podemos notar na Figura 1 que o projeto “Aplicação” é o projeto inicial, e é do tipo Windows Forms Application. Os demais são bibliotecas de classes (Class Library).

<

Estrutura
do projeto em camadas

Figura 1. Estrutura do projeto em camadas

Vale ressaltar que poderíamos ter várias outras camadas em um projeto. Inclusive, a tendência é que o número de camadas aumente conforme a complexidade do software aumenta.

Agora que temos os projetos criados, precisamos prepará-los para execução com o NHibernate e o Fluent NHibernate. Para isso, iremos importar suas dlls para os projetos que as necessitam. Para fins de simplificação, estaremos utilizando o NuGet, e não os repositórios online dos projetos, que demandariam muito mais tempo. O NuGet irá, automaticamente (a não ser que informemos outra coisa), instalar o NHibernate e o Fluent no projeto padrão da solução. Precisamos alterar esse comportamento para instalá-los nos projetos. Os comandos para instalação estão a seguir:

Install-Package NHibernate –ProjectName <nome_do_projeto>
  Install-Package FluentNHibernate –ProjectName <nome_do_projeto>

Uma vez que temos as ferramentas instaladas, normalmente precisamos configurá-las. Para fins de utilização nesse artigo, estaremos utilizando as configurações padrão do NHibernate, mas é interessante notarmos que há outras opções. O NHibernate traz uma série de drivers para bancos de dados, além de dialetos e strings de conexão específicas desses drivers. Drivers para o IBM DB2, SQLite, Firebird, PostgreSQL, MySQL podem ser destacados. Cada driver possui um ou mais dialetos que podem ser utilizados, como diferentes meios de atingir os mesmos resultados.

Agora, vamos criar nossa classe de domínio. Como estamos lidando com uma aplicação extremamente simples, vamos criar apenas uma classe Cliente, que irá conter alguns poucos dados a respeito de um cliente de uma empresa qualquer. A ideia é apenas que entendamos o funcionamento básico dos elementos, mas não entraremos em muitos detalhes. O código dessa classe, pertencente à camada de Domínio, está mostrado na Listagem 1.

Listagem 1. Classe Cliente

namespace Domínio
  {
      public class Cliente
      {
          public virtual int Id { get; protected set; }
          public virtual string Nome { get; set; }
          public virtual string CPF { get; set; }
      }
  }

Aqui, podemos ressaltar dois fatores interessantes:

  • A propriedade Id possui um setter protegido (protected). Essa ação é porque o NHibernate irá criar o identificar para nós, e não é seguro realizar qualquer alteração nesse valor, pois pode criar problemas. Esse tipo de ação também seria utilizado caso tivéssemos coleções de dados, já que as operações delas não alterarão a referência da coleção, apenas os dados presentes na mesma.
  • As propriedades são definidas como virtual. Essa ação, também uma prática recomendada, é realizada devido a uma característica, possível com NHibernate, conhecida como lazy loading, ou “carregamento preguiçoso”, em uma tradução literal. Esse tipo de carregamento dos dados aumenta a performance da aplicação, uma vez que os dados só serão buscados para a aplicação quando forem necessários.

Precisamos de mais uma classe importante em nossa camada de domínio. Agora que possuímos um modelo (classe de representação de dados da base), vamos criar uma interface básica para o acesso a esses dados. Esse tipo de prática é comum no padrão Repository, que estaremos utilizando. Vamos criar uma base de repositório de dados para cada uma das classes do domínio da aplicação. Essa interface pode ser vista na Listagem 2. Nesse caso, estamos definindo apenas o método de inserção, mas em uma aplicação completa também veríamos métodos para todo o CRUD.

Listagem 2. Interface IClienteRepository

namespace Domínio
  {
      public interface IClienteRepository
      {
          void Salvar(Cliente cliente);
      }
  }

Assim, temos nossa aplicação preparada para o NHibernate. Note que temos as classes para representação das tabelas da base de dados (tabela Cliente), mas ainda não há uma correspondência entre as duas. Essa correspondência estará realizada quando tivermos o mapeamento feito, o que será feito em um passo posterior. Entretanto, as ações que tivemos e explicamos (propriedades virtuais e setter protegido para o identificador) vão nos auxiliar nesse mapeamento e também auxiliar o NHibernate internamente.

A camada de acesso a dados

Com isso, temos nossa aplicação montada. Nesse momento vamos partir para o desenvolvimento propriamente dito. A camada de acesso a dados é onde a mágica do NHibernate ocorre, e veremos como isso funciona. É, portanto, nessa camada que as DLLs do NHibernate e do Fluent NHibernate são realmente necessárias.

Como podemos lembrar, na camada de domínio criamos uma interface chamada IClienteRepository. Essa interface é parte essencial do padrão Repository, muito utilizado para acesso e armazenamento de dados em aplicações. Esse padrão cria repositórios de dados para cada uma das classes do domínio da aplicação – Cliente, nesse caso. Entretanto, trata-se de apenas uma interface, e necessitamos realizar a implementação dessa interface. Essa implementação será bastante simples, como veremos a seguir.

No acesso a dados, é muito comum trabalharmos com sessões e transações. Quando utilizamos o NHibernate, não é diferente. Iremos criar uma instância da interface ISession, que será utilizada para viabilizar as transações dentro da base de dados, além das Injeções de Dependência. Podemos verificar essa criação na Listagem 3. Repare que estamos criando a variável internamente na classe e a estamos instanciando dentro do construtor da classe de repositório. Note que o atributo é privado e somente-leitura (readonly). A questão de ser privado é bastante clara: não queremos que a sessão saia dessa classe. Já o caso do somente-leitura acontece quando queremos evitar que a sessão seja alterada durante o uso, o que poderia trazer diversos problemas para o funcionamento da aplicação.

Listagem 3. Classe ClienteRepository

public class ClienteRepository : IClienteRepository
  {
       private readonly ISession sessao;
   
       public ClienteRepository(ISession sessao)
       {
            this.sessao = sessao;
       }
  ...

Com a sessão definida, vamos realizar a utilização da mesma dentro dos métodos do CRUD. Como estamos criando uma aplicação simplificada, temos apenas o método Salvar() para ser implementado. Para isso, vamos fazer uso do conceito de transações dentro da base de dados, através da instância de sessão. Isso pode ser visualizado na Listagem 4. Repare que estamos fazendo uso da palavra-chave using para criarmos um escopo dentro do qual a transação estará ativa. Essa ativação foi realizada através do método BeginTransaction() da sessão instanciada anteriormente. A partir daí, basta salvarmos o cliente passado como parâmetro dentro da base de dados e darmos o commit, desta vez através da transação criada.

Listagem 4. Método Salvar(Cliente)

public void Salvar(Cliente cliente)
  {
      using (var transacao = sessao.BeginTransaction()) {
           sessao.SaveOrUpdate(cliente);
           transacao.Commit();
      }
  }

Note que a criação do método Salvar() é apenas um exemplo de como podem ser criados os métodos de inserção, atualização, exclusão, busca, etc. Um método de exclusão seguiria a mesma estrutura mostrada na Listagem 5, com a alteração do método SaveOrUpdate() pelo método Delete(). Os demais métodos também fariam uso da seção para realizar a operação proposta.

A criação da seção possibilita também a utilização da injeção de dependências, conforme comentamos. Esse tipo de ação busca aumentar o desacoplamento dos objetos da aplicação, melhorando a testabilidade e aumentando a facilidade das futuras manutenções ou adições de funcionalidades.

Realizando o mapeamento com Fluent NHibernate

Como sabemos, o NHibernate é uma ferramenta de ORM, ou Mapeamento Objeto-Relacional. Isso faz com que uma das partes mais importantes do processo de utilização dele seja o mapeamento entre as tabelas da base de dados e as classes de domínio da aplicação. A diferença entre os paradigmas das duas formas de programação é que faz com que o mapeamento seja necessário, e veremos que esse mapeamento pode ser feito de forma fluente, utilizando o projeto Fluent NHibernate. Essa não é a única opção de mapeamento que o NHibernate fornece, mas é a mais simples delas.

Vamos começar o mapeamento propriamente dito. Como vimos anteriormente, temos a classe de domínio Cliente, que será mapeada para uma tabela de base de dados. Vamos verificar esse mapeamento e veremos a simplicidade da criação do mesmo. O FluentNHibernate traz uma série de métodos, como veremos, que são responsáveis por realizar esse mapeamento.

Para começarmos o mapeamento, precisamos entender uma classe importante. A classe ClassMap<T> é uma classe genérica que é utilizada para trazer os métodos de mapeamento para a classe filha. Nesse caso, estaremos criando uma classe ClienteMap derivando dessa classe ClassMap<Cliente>, sendo que o tipo genérico T será substituído pelo tipo Cliente. É interessante notarmos que a classe possui uma versão não genérica que também pode ser utilizada. Vamos entender a importância da classe genérica em alguns momentos.

Agora que a classe está criada, vamos começar a codificação de mapeamento. Note que estamos utilizando o método construtor da classe ClienteMap para realizar essa codificação. Precisamos utilizar o namespace FluentNHibernate.Mappings para utilizar os métodos. Repare que na Listagem 5 estamos utilizando o método Id(), inicialmente, para realizar o mapeamento do identificador da classe. Como parâmetro, uma expressão Lambda que nos permite buscar o Id da classe Cliente (e daí uma parte da importância da utilização do tipo genérico T como Cliente). Ainda a respeito do mapeamento do Id estamos informando que ele será gerado (.GeneratedBy) pelo NHibernate, através de um método específico. Existem outros métodos de geração, e a escolha pelo GuidComb() é pela simplicidade. Para os demais campos, estamos utilizando o método Map(), novamente com uma expressão Lambda como parâmetro. Esse método, como podemos ver no mapeamento da propriedade Nome, pode receber informações, e nesse caso estamos informando que o Nome não pode ser nulo (Not.Nullable()).

Listagem 5. Classe ClienteMap

namespace Dados
  {
      public class ClienteMap : ClassMap<Cliente>
      {
          public ClienteMap()
          {
              Id(c => c.Id).GeneratedBy.GuidComb();
              Map(c => c.Nome).Not.Nullable();
              Map(c => c.CPF);
          }
      }
  }

Aqui, podemos ressaltar um método de mapeamento que não foi utilizado. Trata-se do método NaturalId(). Esse método fará o mapeamento de um campo, normalmente o Nome, como um identificador natural. O identificador natural não se sobrepõe ao método Id(), apenas informa que aquele campo será único e também funcionará como um identificador mais adequado para o usuário e nem tão adequado para o próprio sistema.

Mas qual é o ganho real que esse tipo de mapeamento traz para o desenvolvedor? Com a utilização de métodos, ele faz exatamente o que faríamos em um arquivo XML. Aqui, o ganho é que a utilização de métodos é mais intuitiva para muitos desenvolvedores. Entretanto, esse não é o grande ponto do Fluent NHibernate. Ele traz uma característica muito utilizada, que é o mapeamento automático. Esse tipo de mapeamento está mostrado na Listagem 6. Mas, é claro, precisamos ter cuidado porque o mapeamento automático nem sempre faz aquilo tudo que desejamos, o que faz com que seja interessante sabermos como mapear utilizando os métodos ou mesmo um arquivo XML para utilização do FluentNHibernate.

Listagem 6. Mapeamento automático com Fluent NHibernate

using FluentNHibernate.Automapping;
  ...
   
  namespace Dados
  {
      public class ClienteMap : ClassMap<Cliente>
      {
          public ClienteMap()
          {
              AutoMap.AssemblyOf<Cliente>();
          }
      }
  }

Ao longo desse artigo trouxemos um dos mapeadores objeto/relacionais mais utilizados no mercado, o NHibernate. Vimos algumas das possibilidades que essa ferramenta nos traz, entendendo porque devemos utilizá-lo. A ideia é que, mesmo com a presença de outros ORM’s no mercado, como LINQ to SQL e Entity Framework, possamos enxergar que o NHibernate é extremamente completo, especialmente nas formas de mapeamento que traz. Vimos o mapeamento “fluente”, através do projeto Fluent NHibernate, mas há várias outras possibilidades, que poderiam render vários outros artigos.

As possibilidades de mapeamento no NHibernate são muitas, e entre elas podemos destacar o mapeamento por XML e o ConfORM. O mapeamento por XML é o mais complexo, uma vez que envolve a utilização de código XML puro, ainda que com o auxílio do IntelliSense (se estivermos utilizando o Visual Studio). Através da tag <hibernate-mapping> é possível realizar esse mapeamento, com algumas configurações. Já o ConfORM é um projeto similar ao Fluent NHibernate, que busca facilitar o mapeamento de ao trazer algumas convenções ao NHibernate. Ele é também muito bom, mas um pouco mais complexo que o Fluent NHibernate para mapeamentos simples.

Links

NHibernate
http://nhibernate.info/

Fluent NHibernate
http://www.fluentnhibernate.org/