Skip to content

1. Introdução ao ORM NHibernate

O PDF deste documento está disponível |AQUI|.

Os exemplos do documento estão disponíveis |AQUI|.

Este documento é uma breve introdução ao NHibernate, o equivalente em .NET do framework Java Hibernate. Para uma introdução completa, consulte:


Título: NHibernate in Action, Autor: Pierre-Henri Kuaté, Editora: Manning, ISBN-13: 978-1932394924


Um ORM (Mapeador Objeto-Relacional) é um conjunto de bibliotecas que permite que um programa que utiliza um banco de dados o acesse sem emitir comandos SQL explícitos e sem conhecer as particularidades do SGBD utilizado.


Pré-requisitos


Numa estrutura [débutant-intermédiaire-avancé], este documento está na seção [intermédiaire]. Sua compreensão requer diversos pré-requisitos que podem ser encontrados em alguns dos documentos que escrevi:

  1. Linguagem C# 2008: [Aprendizado da linguagem C# versão 3.0 com o Framework .NET 3.5 (2008)]
  1. [Spring IoC], disponível no URL [Spring IoC para .NET (2005)]. Apresenta os fundamentos da inversão de controle (Inversion of Control) ou injeção de dependências (Dependency Injection) do framework Spring.Net [Spring.NET | Homepage ].

Às vezes, são fornecidas sugestões de leitura no início dos parágrafos deste documento. Elas fazem referência aos documentos anteriores.


Ferramentas


As ferramentas utilizadas neste estudo de caso estão disponíveis gratuitamente na web. São as seguintes (dezembro de 2011):

  • Nhibernate 3.2, disponível no URL [http://nhforge.org/Default.aspx]
  • Spring.net 1.3.2 disponível no URL [http://www.springframework.net]. O framework Spring.net é muito abrangente. Utilizaremos aqui apenas a biblioteca que ele fornece para facilitar o uso do framework Nhibernate.
  • O Log4net 1.2.10 está disponível no URL [http://logging.apache.org/log4net]. Esse framework de logs é utilizado pelo Nhibernate.
  • O Nunit 2.5 está disponível no URL [http://www.nunit.org/]. Esse framework de testes unitários é o equivalente, para .NET, ao framework JUnit para a plataforma Java.
  • O driver ADO.NET 6.4.4 do SGBD MySQL 5 está disponível no URL [http://dev.mysql.com/downloads/connector/net]

Todos os arquivos DLL necessários para os projetos do Visual Studio 2010 foram reunidos em uma pasta [libnet4]:

 

1.1. O papel do NHIBERNATE em uma arquitetura em camadas .NET

Um aplicativo .NET que utiliza um banco de dados pode ser arquitetado em camadas da seguinte maneira:

A camada [dao] se comunica com a SGBD por meio da API e da ADO.NET. Vamos relembrar os principais métodos dessa API.

No modo conectado, o aplicativo:

  1. abre uma conexão com a fonte de dados
  2. trabalha com a fonte de dados em modo de leitura/gravação
  3. encerra a conexão

Três interfaces ADO.NET são as principais envolvidas nessas operações:

  • IDbConnection, que encapsula as propriedades e os métodos da conexão.
  • IDbCommand, que encapsula as propriedades e métodos do comando SQL executado.
  • IDataReader, que encapsula as propriedades e métodos do resultado de uma ordem SQL Select.

A interface IDbConnection

serve para gerenciar a conexão com o banco de dados. Entre os métodos M e propriedades P dessa interface, encontram-se os seguintes:

Nome
Tipo
Função
ConnectionString
P
cadeia de conexão com o banco de dados. Ela especifica todos os parâmetros necessários para estabelecer a conexão com um banco de dados específico.
Open
M
abre a conexão com o banco de dados definido por ConnectionString
Close
M
encerra a conexão
BeginTransaction
M
inicia uma transação.
State
P
estado da conexão: ConnectionState.Closed, ConnectionState.Open, ConnectionState.Connecting, ConnectionState.Executing, ConnectionState.Fetching, ConnectionState.Broken

Se Connection for uma classe que implementa a interface IDbConnection, a conexão pode ser estabelecida da seguinte forma:

1
2
3
IDbConnection connexion=new Connection();
connexion.ConnectionString=...;
connexion.Open();

A interface IDbCommand

Serve para executar uma ordem SQL ou um procedimento armazenado. Entre os métodos M e propriedades P dessa interface, encontram-se os seguintes:

Nome
Tipo
Função
CommandType
P
indica o que deve ser executado — obtém seus valores de uma enumeração:
- CommandType.Text: executa a ordem SQL definida na propriedade CommandText. Esse é o valor padrão.
- CommandType.StoredProcedure: executa um procedimento armazenado no banco de dados
CommandText
P
- o texto do comando SQL a ser executado se CommandType = CommandType.Text
- o nome da procedimento armazenado a ser executado se CommandType = CommandType.StoredProcedure
Connection
P
a conexão IDbConnection a ser utilizada para executar a ordem SQL
Transaction
P
a transação IDbTransaction na qual executar a ordem SQL
Parameters
P
a lista de parâmetros de uma ordem SQL configurada. A ordem update articles set price=price*1.1 where id=@id possui o parâmetro @id.
ExecuteReader
M
para executar uma ordem SQL Select. Obtém-se um objeto IDataReader que representa o resultado da ordem Select.
ExecuteNonQuery
M
para executar uma ordem SQL: Atualizar, Inserir, Excluir. É exibido o número de linhas afetadas pela operação (atualizadas, inseridas, excluídas).
ExecuteScalar
M
para executar uma ordem SQL Select, que retorna apenas um único resultado, como em: select count(*) from articles.
CreateParameter
M
para criar os parâmetros IDbParameter de uma ordem SQL configurada.
Prepare
M
permite otimizar a execução de uma consulta parametrizada quando ela é executada várias vezes com parâmetros diferentes.

Se Command for uma classe que implementa a interface IDbCommand, a execução de uma ordem SQL sem transação terá a seguinte forma:

// início da conexão 
IDbConnection connexion=...
connexion.Open();
// preparação do comando
IDbCommand commande=new Command();
commande.Connection=connexion;
// execução da ordem select
commande.CommandText="select ...";
IDbDataReader reader=commande.ExecuteReader();
...
// execução da ordem update, insert, delete
commande.CommandText="insert ...";
int nbLignesInsérées=commande.ExecuteNonQuery();
...
// encerramento da conexão
connexion.Close();

A interface IDataReader

serve para encapsular os resultados de uma ordem SQL Select. Um objeto IDataReader representa uma tabela com linhas e colunas, que são processadas sequencialmente: primeiro a primeira linha, depois a segunda, ... Entre os métodos M e propriedades P dessa interface, encontram-se os seguintes:

Nome
Tipo
Função
FieldCount
P
o número de colunas da tabela IDataReader
GetName
M
GetName(i) retorna o nome da coluna nº i da tabela IDataReader.
Item
P
Item[i] representa a coluna nº i da linha atual da tabela IDataReader.
Read
M
avança para a próxima linha da tabela IDataReader. Retorna o valor booleano True se a leitura foi bem-sucedida; caso contrário, retorna False.
Close
M
fecha a tabela IDataReader.
GetBoolean
M
GetBoolean(i): retorna o valor booleano da coluna nº i da linha atual da tabela IDataReader. Os outros métodos análogos são os seguintes: GetDateTime, GetDecimal, GetDouble, GetFloat, GetInt16, GetInt32, GetInt64, GetString.
Getvalue
M
Getvalue(i): retorna o valor da coluna nº i da linha atual da tabela IDataReader como tipo object.
IsDBNull
M
IsDBNull(i) retorna True se a coluna nº i da linha atual da tabela IDataReader não tiver valor, o que é simbolizado pelo valor SQL NULL.

A análise de um objeto IDataReader costuma ser semelhante ao seguinte:

// abertura da conexão 
IDbConnection connexion=...
connexion.Open();
// preparação do comando
IDbCommand commande=new Command();
commande.Connection=connexion;
// execução da ordem select
commande.CommandText="select ...";
IDataReader reader=commande.ExecuteReader();
// análise dos resultados
while(reader.Read()){
     // analisar linha atual
        ...
}
// encerramento do leitor
reader.Close();
// encerramento da conexão
connexion.Close();

Na arquitetura anterior,

o conector [ADO.NET] está vinculado ao SGBD. Assim, a classe que implementa a interface [IDbConnection] é:

  • a classe [MySQLConnection] para o SGBD MySQL
  • a classe [SQLConnection] para o SGBD e o SQLServer

A camada [dao] depende, portanto, do SGBD utilizado. Alguns frameworks (Linq, Ibatis.net, NHibernate) eliminam essa restrição ao adicionar uma camada adicional entre a camada [dao] e o conector [ADO.NET] do SGBD utilizado. Utilizaremos aqui o framework [NHibernate].

No exemplo acima, a camada [dao] não se comunica mais com o conector [ADO.NET], mas sim com o framework NHibernate, que lhe apresentará uma interface independente do conector [ADO.NET] utilizado. Essa arquitetura permite trocar o SGBD sem alterar a camada [dao]. Nesse caso, apenas o conector [ADO.NET] precisa ser trocado.

1.2. O banco de dados de exemplo

Para demonstrar como trabalhar com o NHibernate, utilizaremos o seguinte banco de dados MySQL [dbpam_nhibernate]:

  • No [1], o banco de dados possui três tabelas:
    • [employes]: uma tabela que registra as funcionárias de uma creche
    • [cotisations]: uma tabela que registra as alíquotas das contribuições sociais
    • [indemnites]: uma tabela que registra informações que permitem calcular a folha de pagamento das funcionárias

Tabela [employes]

  • em [2], a tabela dos funcionários, e em [3], o significado de seus campos

O conteúdo da tabela poderia ser o seguinte:

 

Tabela [cotisations]

  • em [4], a tabela de contribuições e em [5], o significado de seus campos

O conteúdo da tabela poderia ser o seguinte:

 

Tabela [indemnites]

  • em [6], a tabela de indenizações e em [7], o significado de seus campos

O conteúdo da tabela poderia ser o seguinte:

 

A exportação da estrutura do banco de dados para um arquivo SQL resulta no seguinte:

#
# Estrutura da tabela `cotisations`: 
#

CREATE TABLE `cotisations` (
  `ID` bigint(20) NOT NULL auto_increment,
  `SECU` double NOT NULL,
  `RETRAITE` double NOT NULL,
  `CSGD` double NOT NULL,
  `CSGRDS` double NOT NULL,
  `VERSION` int(11) NOT NULL,
  PRIMARY KEY  (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;

#
# Estrutura da tabela `indemnites`: 
#

CREATE TABLE `indemnites` (
  `ID` bigint(20) NOT NULL auto_increment,
  `ENTRETIEN_JOUR` double NOT NULL,
  `REPAS_JOUR` double NOT NULL,
  `INDICE` int(11) NOT NULL,
  `INDEMNITES_CP` double NOT NULL,
  `BASE_HEURE` double NOT NULL,
  `VERSION` int(11) NOT NULL,
  PRIMARY KEY  (`ID`),
  UNIQUE KEY `INDICE` (`INDICE`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1;

#
# Estrutura da tabela `employes`: 
#

CREATE TABLE `employes` (
  `ID` bigint(20) NOT NULL auto_increment,
  `PRENOM` varchar(20) NOT NULL,
  `SS` varchar(15) NOT NULL,
  `ADRESSE` varchar(50) NOT NULL,
  `CP` varchar(5) NOT NULL,
  `VILLE` varchar(30) NOT NULL,
  `NOM` varchar(30) NOT NULL,
  `VERSION` int(11) NOT NULL,
  `INDEMNITE_ID` bigint(20) NOT NULL,
  PRIMARY KEY  (`ID`),
  UNIQUE KEY `SS` (`SS`),
  KEY `FK_EMPLOYES_INDEMNITE_ID` (`INDEMNITE_ID`),
  CONSTRAINT `FK_EMPLOYES_INDEMNITE_ID` FOREIGN KEY (`INDEMNITE_ID`) REFERENCES `indemnites` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;

Observe, nas linhas 6, 20 e 36, que as chaves primárias ID possuem o atributo autoincrement. Isso significa que MySQL gerará automaticamente os valores das chaves primárias sempre que um registro for adicionado. O desenvolvedor não precisa se preocupar com isso.

1.3. O projeto de demonstração em C#

Para apresentar a configuração e o uso do NHibernate, utilizaremos a seguinte arquitetura:

Um programa de console [1] processará os dados do banco de dados anterior [2] por meio do framework [NHibernate] [3]. Isso nos levará a apresentar:

  • os arquivos de configuração do NHibernate
  • o API do NHibernate

O projeto em C# será o seguinte:

Os elementos necessários para o projeto são os seguintes:

  • no [1], os DLL de que o projeto necessita:
    • [NHibernate]: o DLL do framework NHibernate
    • [MySql.Data]: o DLL do conector ADO.NET do SGBD MySQL
    • [log4net]: o DLL do framework Log4net, que permite gerar logs
  • em [2], as classes de imagens das tabelas do banco de dados
  • em [3], o arquivo [App.config] que configura toda a aplicação, incluindo o framework [NHibernate]
  • em [4], e os aplicativos de console de teste

1.3.1. Configuração da conexão com o banco de dados

Voltemos à arquitetura de teste:

Conforme mostrado acima, o [NHibernate] precisa ter acesso ao banco de dados. Para isso, ele precisa de algumas informações:

  • o SGBD que gerencia o banco de dados (MySQL, SQLServer, Postgres, Oracle, ...). A maioria dos SGBD adicionou à linguagem SQL extensões próprias. Conhecendo o SGBD, o NHibernate pode adaptar os comandos SQL que emite para esse SGBD. O NHibernate utiliza o conceito de dialeto do SQL.
  • os parâmetros de conexão ao banco de dados (nome do banco, nome do usuário proprietário da conexão, sua senha)

Essas informações podem ser inseridas no arquivo de configuração [App.config]. Aqui está o arquivo que será utilizado com um banco de dados MySQL 5:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <!-- seções de configuração -->
    <configSections>
        <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
        <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
    </configSections>


    <!-- configuração NHibernate -->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <!--
            <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
            -->
            <property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
            <property name="connection.connection_string">
                Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
            </property>
            <property name="show_sql">false</property>
            <mapping assembly="pam-nhibernate-demos"/>
        </session-factory>
    </hibernate-configuration>

    <!-- Esta seção contém as configurações do log4net -->
    <!-- NOTE IMPORTANTE: os logs não estão ativos por padrão. É necessário ativá-los programaticamente com a instrução log4net.Config.XmlConfigurator.Configure();
    ! -->
    <log4net>
        <!-- Defina um appender de saída (para onde os logs serão direcionados) -->
        <appender name="LogFileAppender" type="log4net.Appender.FileAppender, log4net">
            <param name="File" value="log.txt" />
            <param name="AppendToFile" value="false" />
            <layout type="log4net.Layout.PatternLayout, log4net">
                <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] &lt;%X{auth}&gt; - %m%n" />
            </layout>
        </appender>
        <appender name="LogDebugAppender" type="log4net.Appender.DebugAppender, log4net">
            <layout type="log4net.Layout.PatternLayout, log4net">
                <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] &lt;%X{auth}&gt; - %m%n"/>
            </layout>
        </appender>
        <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender, log4net">
            <layout type="log4net.Layout.PatternLayout, log4net">
                <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] &lt;%X{auth}&gt; - %m%n"/>
            </layout>
        </appender>

        <!-- Configurar a categoria raiz, definir o nível de prioridade padrão e adicionar o(s) appender(s) (para onde os logs serão direcionados) -->
        <root>
            <priority value="INFO" />
            <!--
            <appender-ref ref="LogFileAppender" />
            <appender-ref ref="LogDebugAppender"/>
            -->
            <appender-ref ref="ConsoleAppender"/>
        </root>

        <!-- Especifique o nível para alguns namespaces específicos -->
        <!-- O nível pode ser: ALL, DEBUG, INFO, WARN, ERROR, FATAL, OFF -->
        <logger name="NHibernate">
            <level value="INFO" />
        </logger>
    </log4net>
</configuration>
  • linhas 4-7: definem seções de configuração no arquivo [App.config]. Consideremos a linha 6:

<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />

Essa linha define a seção de configuração de NHibernate no arquivo [App.config]. Ela possui dois atributos: name e type.

  • O atributo [name] identifica a seção de configuração. Essa seção deve ser delimitada pelas tags <name>...</name>, neste caso <hibernate-configuration>...</hibernate-configuration>, nas linhas 11 a 24.
  • O atributo [type=classe,DLL] indica o nome da classe responsável por processar a seção definida pelo atributo [name], bem como a DLL que contém essa classe. Aqui, a classe se chama [NHibernate.Cfg.ConfigurationSectionHandler] e está localizada na DLL [NHibernate.dll]. Vale lembrar que essa DLL faz parte das referências do projeto em análise.

Consideremos agora a seção de configuração de NHibernate:


    <!-- configuração NHibernate -->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <!--
            <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
            -->
            <property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
            <property name="connection.connection_string">
                Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
            </property>
            <property name="show_sql">false</property>
            <mapping assembly="pam-nhibernate-demos"/>
        </session-factory>
</hibernate-configuration>
  • linha 2: a configuração de NHibernate está dentro de uma tag <hibernate-configuration>. O atributo xmlns (Xml NameSpace) define a versão utilizada para configurar NHibernate. De fato, com o passar do tempo, a forma de configurar o NHibernate evoluiu. Aqui, é utilizada a versão 2.2.
  • linha 3: a configuração do NHibernate está aqui inteiramente contida na tag <session-factory> (linhas 3 e 14). Uma sessão NHibernate é a ferramenta utilizada para trabalhar com um banco de dados de acordo com o esquema:
    • abertura da sessão
    • trabalho com o banco de dados por meio dos métodos do API e NHibernate
    • fechar sessão

A sessão é criada por um factory, um termo genérico que designa uma classe capaz de criar objetos. As linhas 3 a 14 configuram esse factory.

  • linhas 4, 6, 8, 9: configuram a conexão com o banco de dados de destino. As principais informações são o nome do SGBD utilizado, o nome do banco de dados, a identidade do usuário e sua senha.
  • linha 4: define o provedor da conexão, aquele ao qual se solicita uma conexão com o banco de dados. O valor da propriedade [connection.provider] é o nome de uma classe NHibernate. Essa propriedade não depende do SGBD utilizado.
  • linha 6: o driver ADO.NET a ser utilizado. Trata-se do nome de uma classe NHibernate especializada para um determinado SGBD, neste caso, MySQL. A linha 6 foi colocada entre comentários, pois não é indispensável.
  • linha 8: a propriedade [dialect] define o dialeto SQL a ser utilizado com o SGBD. Aqui, trata-se do dialeto do SGBD MySQL.

Se mudarmos para o SGBD, como encontramos o dialeto NHibernate correspondente a ele? Voltemos ao projeto C# anterior e cliquemos duas vezes em DLL [NHibernate] na aba [References]:

  • em [1], a aba [Explorateur d'objets] exibe várias entradas DLL, incluindo aquelas referenciadas pelo projeto.
  • em [2], o DLL e o [NHibernate]
  • em [3], o DLL e o [NHibernate] desenvolvidos. Nele encontram-se os diferentes namespaces definidos.
  • em [4], o namespace [NHibernate.Dialect], onde se encontram as classes que definem os diferentes dialetos SQL utilizáveis.
  • em [5], a classe do dialeto do SGBD MySQL 5.
  • em [6], o espaço de nomes da classe [MySqlDataDriver] utilizada na linha 6 abaixo:

    <!-- configuração NHibernate -->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <!--
            <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
            -->
            <property name="dialect">NHibernate.Dialect.MySQLDialect</property>
            <property name="connection.connection_string">
                Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
            </property>
            <property name="show_sql">false</property>
            <mapping assembly="pam-nhibernate-demos"/>
        </session-factory>
</hibernate-configuration>
  • linhas 9-11: a string de conexão com o banco de dados. Essa string tem o formato “param1=val1;param2=val2; ...”. O conjunto de parâmetros assim definidos permite que o driver do SGBD estabeleça uma conexão. O formato dessa string de conexão depende do SGBD utilizado. As strings de conexão para os principais SGBD podem ser encontradas no site [http://www.connectionstrings.com/]. Aqui, a string “Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;” é uma string de conexão para o SGBD MySQL. Ela indica que:
    • Server=localhost;: o SGBD está na mesma máquina que o cliente que está tentando estabelecer a conexão
    • Database=dbpam_nhibernate; : o banco de dados MySQL de destino
    • Uid=root;: o usuário que está abrindo a conexão é o usuário root
    • Pwd=;: esse usuário não possui senha (caso específico deste exemplo)
  • linha 12: a propriedade [show_sql] indica se NHibernate deve exibir em seus logs os comandos SQL que ele emite para o banco de dados. Na fase de desenvolvimento, é útil definir essa propriedade como [true] para saber exatamente o que o NHibernate está fazendo.
  • linha 13: para entender a tag <mapping>, voltemos à arquitetura do aplicativo:

Se o programa de console fosse um cliente direto do conector ADO.NET e quisesse a lista de funcionários, ele faria com que o conector executasse uma ordem SQL Select, e receberia em resposta um objeto do tipo IDataReader, que teria de processar para obter a lista de funcionários inicialmente desejada.

No exemplo acima, o programa de console é o cliente de NHibernate e NHibernate é o cliente do conector ADO.NET. Veremos posteriormente que o API, proveniente do NHibernate, permitirá que o programa de console solicite a lista de funcionários. O NHibernate converterá essa solicitação em uma ordem SQL Select, que será executada pelo conector ADO.NET. Este, por sua vez, retornará um objeto do tipo IDataReader. A partir desse objeto, o NHibernate deve ser capaz de construir a lista de funcionários que lhe foi solicitada. Isso é possibilitado por meio da configuração. A cada tabela do banco de dados está associada uma classe C#. Assim, a partir das linhas da tabela [employes] retornadas pelo IDataReader, o NHibernate será capaz de construir uma lista de objetos representando funcionários e retorná-la ao programa de console. Essas relações entre tabelas e classes são criadas em arquivos de configuração. O NHibernate utiliza o termo “mapping” para definir essas relações.

Voltemos à linha 13 abaixo:


    <!-- configuração NHibernate -->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <!--
            <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
            -->
            <property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
            <property name="connection.connection_string">
                Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
            </property>
            <property name="show_sql">false</property>
            <mapping assembly="pam-nhibernate-demos"/>
        </session-factory>
</hibernate-configuration>

A linha 13 indica que os arquivos de configuração tabelas <--> classes serão encontrados no assembly [pam-nhibernate-demos]. Um assembly é o executável ou o DLL produzido pela compilação de um projeto. Aqui, os arquivos de mapeamento serão colocados no assembly do projeto de exemplo. Para saber o nome desse assembly, é preciso verificar as propriedades do projeto:

  • em [1], nas propriedades do projeto
  • na aba [Application] [2], o nome do assembly [3] que será gerado.
  • Como o tipo de saída é [Application console] [4], o arquivo gerado na compilação do projeto se chamará [pam-nhibernate-demos.exe]. Se o tipo de saída fosse [Bibliothèque de classes] [5], o arquivo gerado na compilação do projeto se chamaria [pam-nhibernate-demos.dll]
  • O assembly é gerado na pasta [bin/Release] do projeto [6].

Conforme explicado anteriormente, os arquivos de tabelas de mapeamento <--> classes deverão estar no arquivo [pam-nhibernate-demos.exe] [6].

1.3.2. Configuração do de mapeamento de tabelas <--> classes

Voltemos à arquitetura do projeto em análise:

  • no [1], o programa de console utiliza os métodos do API do framework NHibernate. Esses dois blocos trocam objetos.
  • No [2], o NHibernate utiliza o API de um conector .NET. Ele envia comandos SQL para o SGBD de destino.

O programa de console irá manipular objetos que refletem as tabelas do banco de dados. Neste projeto, esses objetos e as ligações que os unem às tabelas do banco de dados foram colocados na pasta [Entites] abaixo:

 
  • cada tabela do banco de dados corresponde a uma classe e a um arquivo de mapeamento entre as duas
Tabela
Classe
Mapeamento
contribuições
Cotisations.cs
Cotisations.hbm.xml
funcionários
Employe.cs
Employe.hbm.xml
indenizações
Indemnites.cs
Indemnites.hbm.xml

1.3.2.1. Mapeamento da tabela [cotisations]

Consideremos a tabela [cotisations]:

ID
chave primária do tipo autoincremento
VERSION
número de versão do registro
SECU
taxa (porcentagem) de contribuição para a previdência social
RETRAITE
taxa de contribuição para a aposentadoria
CSGD
taxa de contribuição para a contribuição social generalizada dedutível
CSGRDS
taxa de contribuição para a contribuição social generalizada e a contribuição para o pagamento da dívida social

Uma linha desta tabela pode ser encapsulada em um objeto do tipo [Cotisations.cs] da seguinte forma:


namespace PamNHibernateDemos {
    public class Cotisations {
        // propriedades automáticas
        public virtual int Id { get; set; }
        public virtual int Version { get; set; }
        public virtual double CsgRds { get; set; }
        public virtual double Csgd { get; set; }
        public virtual double Secu { get; set; }
        public virtual double Retraite { get; set; }

        // fabricantes
        public Cotisations() {
        }
        // ToString
        public override string ToString() {
            return string.Format("[{0}|{1}|{2}|{3}]", CsgRds, Csgd, Secu, Retraite);
        }
    }

}

Foi criada uma propriedade automática para cada uma das colunas da tabela [cotisations]. Cada uma dessas propriedades deve ser declarada como virtual (virtual), pois NHibernate irá herdar da classe e redefinir (override) suas propriedades. Portanto, elas devem ser virtuais.

Observe-se, na linha 1, que a classe pertence ao espaço de nomes [PamNHibernateDemos].

O arquivo de mapeamento [Cotisations.hbm.xml] entre a tabela [cotisations] e a classe [Cotisations] é o seguinte:


<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PamNHibernateDemos" assembly="pam-nhibernate-demos">
    <class name="Cotisations" table="COTISATIONS">
        <id name="Id" column="ID" unsaved-value="0">
            <generator class="native" />
        </id>
        <version name="Version" column="VERSION"/>
        <property name="CsgRds" column="CSGRDS"/>
        <property name="Csgd" column="CSGD"/>
        <property name="Retraite" column="RETRAITE"/>
        <property name="Secu" column="SECU"/>
    </class>
</hibernate-mapping>
  • o arquivo de mapeamento é um arquivo XML definido dentro da tag <hibernate-mapping> (linhas 2 e 14)
  • linha 4: a tag <class> estabelece a ligação entre uma tabela do banco de dados e uma classe. Aqui, a tabela [COTISATIONS] (atributo table) e a classe [Cotisations] (atributo name). No .NET, uma classe deve ser definida por seu nome completo (incluindo o namespace) e pelo assembly que a contém. Essas duas informações são fornecidas pela linha 3. A primeira (namespace) pode ser encontrada na definição da classe. A segunda (assembly) é o nome do assembly do projeto. Já indicamos como encontrar esse nome.
  • linhas 5-7: a tag <id> serve para definir o mapeamento da chave primária da tabela [cotisations].
    • linha 5: o atributo name designa o campo da classe [Cotisations] que receberá a chave primária da tabela [cotisations]. O atributo column indica a coluna da tabela [cotisations] que serve como chave primária. O atributo unsaved-value serve para definir uma chave primária ainda não gerada. Esse valor permite que NHibernate saiba como gravar um objeto [Cotisations] na tabela [cotisations]. Se esse objeto tiver um campo Id=0, será executada uma operação SQL INSERT; caso contrário, será executada uma operação SQL UPDATE. O valor de unsaved-value depende do tipo do campo Id da classe [Cotisations]. Nesse caso, ele é do tipo int, e o valor padrão de um tipo int é 0. Um objeto [Cotisations] ainda não salvo (portanto, sem chave primária) terá, portanto, seu campo Id=0. Se o campo Id fosse do tipo Object ou derivado, teríamos escrito unsaved-value=null.
    • linha 6: quando o NHibernate precisa salvar um objeto [Cotisations] com um campo Id=0, ele deve realizar no banco de dados uma operação INSERT, durante a qual deve obter um valor para a chave primária do registro. A maioria dos SGBD possui um método próprio para gerar automaticamente esse valor. A tag <generator> serve para definir o mecanismo a ser utilizado na geração da chave primária. A tag <generator class="native"> indica que deve-se utilizar o mecanismo padrão do SGBD em uso. Vimos no parágrafo 1.2 que as chaves primárias das nossas três tabelas MySQL possuíam o atributo autoincrement. Durante suas operações INSERT, NHibernate não fornecerá um valor para a coluna ID do registro adicionado, deixando que MySQL gere esse valor.
  • linha 8: a tag <version> serve para definir a coluna da tabela (bem como o campo da classe correspondente) que permite “versionar” os registros. Inicialmente, a versão é igual a 1. Ela é incrementada a cada operação UPDATE. Por outro lado, toda operação UPDATE ou DELETE é realizada com um filtro WHERE ID= id AND VERSION=v1. Portanto, um usuário só pode modificar ou excluir um objeto se tiver a versão correta do mesmo. Caso contrário, uma exceção é gerada pelo NHibernate.
  • linha 9: a tag <property> serve para definir um mapeamento de coluna normal (nem chave primária, nem coluna de versão). Assim, a linha 9 indica que a coluna CSGRDS da tabela [COTISATIONS] está associada à propriedade CsgRds da classe [Cotisations].

1.3.2.2. Mapeamento da tabela [indemnites]

Consideremos a tabela [indemnites]:

ID
chave primária do tipo autoincremento
VERSION
número da versão do registro
BASE_HEURE
custo em euros de uma hora de plantão
ENTRETIEN_JOUR
Indenização em euros por dia de plantão
REPAS_JOUR
Subsídio de refeição em euros por dia de plantão
INDEMNITES_CP
Subsídios de férias remuneradas. Trata-se de uma porcentagem a ser aplicada ao salário-base.

Uma linha desta tabela pode ser encapsulada em um objeto do tipo [Indemnites] da seguinte forma:


namespace PamNHibernateDemos {
    public class Indemnites {

        // propriedades automáticas
        public virtual int Id { get; set; }
        public virtual int Version { get; set; }
        public virtual int Indice { get; set; }
        public virtual double BaseHeure { get; set; }
        public virtual double EntretienJour { get; set; }
        public virtual double RepasJour { get; set; }
        public virtual double IndemnitesCp { get; set; }

        // construtores
        public Indemnites() {
        }

        // identidade
        public override string ToString() {
            return string.Format("[{0}|{1}|{2}|{3}|{4}]", Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCp);
        }

    }
}

O arquivo de mapeamento da tabela [indemnites] <--> classe [Indemnites] poderia ser o seguinte (Indemnites.hbm.xml):


<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PamNHibernateDemos" assembly="pam-nhibernate-demos">
    <class name="Indemnites" table="INDEMNITES">
        <id name="Id" column="ID" unsaved-value="0">
            <generator class="native" />
        </id>
        <version name="Version" column="VERSION"/>
        <property name="Indice" column="INDICE" unique="true"/>
        <property name="BaseHeure" column="BASE_HEURE" />
        <property name="EntretienJour" column="ENTRETIEN_JOUR" />
        <property name="RepasJour" column="REPAS_JOUR" />
        <property name="IndemnitesCp" column="INDEMNITES_CP" />
    </class>
</hibernate-mapping>

Não há nada de novo aqui em relação ao arquivo de mapeamento explicado anteriormente. A única diferença está na linha 9. O atributo unique="true" indica que, na tabela [indemnites], há uma restrição de exclusividade na coluna [INDICE]: não pode haver duas linhas com o mesmo valor na coluna [INDICE].

1.3.2.3. Mapeamento da tabela [employes]

Consideremos a tabela [employes]:

ID
chave primária do tipo autoincremento
VERSION
número de versão do registro
PRENOM
nome do funcionário
NOM
sobrenome
ADRESSE
seu endereço
CP
seu CEP
VILLE
sua cidade
INDEMNITE_ID
chave estrangeira em INDEMNITES (ID)

A novidade em relação às tabelas anteriores é a presença de uma chave estrangeira: a coluna [INDEMNITE_ID] é uma chave estrangeira na coluna [ID] da tabela [INDEMNITES]. Esse campo faz referência à linha da tabela [INDEMNITES] a ser utilizada para calcular os subsídios do funcionário.

A classe [Employe] a a imagem da tabela [employes] poderia ser a seguinte:


namespace PamNHibernateDemos {
    public class Employe {
        // propriedades automáticas
        public virtual int Id { get; set; }
        public virtual int Version { get; set; }
        public virtual string SS { get; set; }
        public virtual string Nom { get; set; }
        public virtual string Prenom { get; set; }
        public virtual string Adresse { get; set; }
        public virtual string Ville { get; set; }
        public virtual string CodePostal { get; set; }
        public virtual Indemnites Indemnites { get; set; }

        // construtores
        public Employe() {
        }

        // ToString
        public override string ToString() {
            return string.Format("[{0}|{1}|{2}|{3}|{4}|{5}|{6}]", SS, Nom, Prenom, Adresse, Ville, CodePostal, Indemnites);
        }
    }
}

O arquivo de mapeamento [Employe.hbm.xml] poderia ser o seguinte:


<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PamNHibernateDemos" assembly="pam-nhibernate-demos">
    <class name="Employe" table="EMPLOYES">
        <id name="Id" column="ID" unsaved-value="0">
            <generator class="native" />
        </id>
        <version name="Version" column="VERSION"/>
        <property name="SS" column="SS"/>
        <property name="Nom" column="NOM"/>
        <property name="Prenom" column="PRENOM"/>
        <property name="Adresse" column="ADRESSE"/>
        <property name="Ville" column="VILLE"/>
        <property name="CodePostal" column="CP"/>
        <many-to-one name="Indemnites" column="INDEMNITE_ID" cascade="save-update" lazy="false"/>
    </class>
</hibernate-mapping>

A novidade está na linha 15, com o surgimento de uma nova tag: <many-to-one>. Essa tag serve para mapear uma coluna de chave estrangeira [INDEMNITE_ID] da tabela [EMPLOYES] para a propriedade [Indemnites] da classe [Employe]:


namespace PamNHibernateDemos {
    public class Employe {
        // propriedades automáticas
..
        public virtual Indemnites Indemnites { get; set; }

...
    }
}

A tabela [EMPLOYES] possui uma chave estrangeira [INDEMNITE_ID] que faz referência à coluna [ID] da tabela [INDEMNITES]. Várias (many) linhas da tabela [EMPLOYES] podem referenciar uma mesma linha (one) da tabela [INDEMNITES]. Daí o nome da tag <many-to-one>. Essa tag possui, neste caso, os seguintes atributos:

  • column: indica o nome da coluna da tabela [EMPLOYES] que é a chave estrangeira na tabela [INDEMNITES]
  • name: indica a propriedade da classe [Employe] associada a essa coluna. O tipo dessa propriedade é necessariamente a classe associada à tabela de destino da chave estrangeira, neste caso, a tabela [INDEMNITES]. Sabe-se que essa classe é a classe [Indemnites] já descrita. É isso que a linha 5 acima reflete. Isso significa que, quando NHibernate recuperar da base de dados um objeto [Employe], ele também recuperará o objeto [Indemnites] que o acompanha.
  • cascata: esse atributo pode assumir diversos valores:
    • save-update: uma operação de inserção (save) ou atualização (update) no objeto [Employe] deve ser propagada para o objeto [Indemnites] que ele contém.
    • delete: a exclusão de um objeto [Employe] deve ser propagada para o objeto [Indemnites] que ele contém.
    • all: propaga as operações de inserção (save), atualização (update) e exclusão (delete).
    • none: não propaga nada

Para concluir, relembremos a configuração de NHibernate no arquivo [App.config]:


    <!-- configuração NHibernate -->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <!--
            <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
            -->
            <property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
            <property name="connection.connection_string">
                Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
            </property>
            <property name="show_sql">false</property>
            <mapping assembly="pam-nhibernate-demos"/>
        </session-factory>
</hibernate-configuration>

A linha 13 indica que os arquivos de mapeamento *.hbm.xml serão encontrados no assembly [pam-nhibernate-demos]. Isso não ocorre por padrão. É necessário configurá-lo no projeto C#:

  • no [1], seleciona-se as propriedades de um arquivo de mapeamento
  • em [2], a ação de geração deve ser [Ressource incorporée] [3]. Isso significa que, na geração do projeto, o arquivo de mapeamento deve ser incorporado ao assembly gerado.

1.4. do API para o NHibernate

Voltemos à arquitetura do nosso projeto de exemplo:

Nos parágrafos anteriores, configuramos o NHibernate de duas maneiras:

  • no [App.config], configuramos a conexão com o banco de dados
  • definimos, para cada tabela do banco de dados, a classe que representa essa tabela e o arquivo de mapeamento que permite converter a classe na tabela e vice-versa.

Resta-nos agora descobrir os métodos oferecidos pelo NHibernate para manipular os dados do banco de dados: inserção, atualização, exclusão e listagem.

1.4.1. O objeto SessionFactory

Toda operação NHibernate é realizada dentro de uma sessão. Uma sequência típica de operações NHibernate é a seguinte:

  • abrir uma sessão NHibernate
  • iniciar uma transação na sessão
  • realizar operações de persistência com a sessão (Load, Get, Find, CreateQuery, Save, SaveOrUpdate, Delete)
  • confirmar (commit) ou reverter (rollback) a transação
  • fechar a sessão NHibernate

Uma sessão é obtida por meio de uma fábrica do tipo [SessionFactory]. Essa fábrica é a configurada pela tag <session-factory> no arquivo de configuração [App.config]:


    <!-- configuração NHibernate -->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <!--
            <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
            -->
            <property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
            <property name="connection.connection_string">
                Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
            </property>
            <property name="show_sql">false</property>
            <mapping assembly="pam-nhibernate-demos"/>
        </session-factory>
</hibernate-configuration>

Em um código C#, a SessionFactory pode ser obtida da seguinte maneira:


ISessionFactory sessionFactory = new Configuration().Configure().BuildSessionFactory();

A classe Configuration é uma classe do framework NHibernate. A instrução anterior utiliza a seção de configuração de NHibernate em [App.config]. O objeto [ISessionFactory] obtido contém, então, as seguintes:

  • informações para criar uma conexão com o banco de dados de destino
  • arquivos de mapeamento entre as tabelas do banco de dados e as classes persistentes manipuladas pelo NHibernate.

1.4.2. A sessão NHibernate

Uma vez criada a SessionFactory (isso é feito uma única vez), é possível obter as sessões que permitem realizar operações de persistência NHibernate. Um código comum é o seguinte:


try{
      // início de sessão 
      using (ISession session = sessionFactory.OpenSession())
      {
        // início da transação
        using (ITransaction transaction = session.BeginTransaction())
        {
........................ opérations de persistance
          // validação da transação
          transaction.Commit();
        }
      }
}catch (Exception ex){
....
}
  • linha 3: uma sessão é criada a partir do SessionFactory dentro de uma cláusula using. Ao sair da cláusula using, a sessão será fechada automaticamente. Sem a cláusula using, seria necessário fechar a sessão explicitamente (session.Close()).
  • linha 6: as operações de persistência serão realizadas dentro de uma transação. Ou todas são bem-sucedidas, ou nenhuma é bem-sucedida. Dentro da cláusula using,, a transação é validada por um Commit (linha 10). Se, durante a transação, uma operação de persistência lançar uma exceção, a transação será automaticamente invalidada por um Rollback ao sair do using.
  • O try/catch das linhas 1 e 13 permite interceptar uma eventual exceção lançada pelo código dentro do try (sessão, transação, persistência).

1.4.3. A interface ISession

Apresentamos agora alguns dos métodos da interface ISession implementada por uma sessão NHibernate:

ITransaction BeginTransaction()
inicia uma transação na sessão
ITransaction tx=session.BeginTransaction();
void Clear()
esvazia a sessão. Os objetos que ela continha ficam desassociados.
session.Clear();
void Close()
fecha a sessão. Os objetos que ela continha são sincronizados com o banco de dados. Essa operação de sincronização também é realizada ao final de uma transação. Este último caso é o mais comum.
session.Close();
IQuery CreateQuery(string queryString)
cria uma consulta HQL (Hibernate Query Language) para execução posterior.
IQuery query=session.createQuery("select e from Empregado e);
void Delete(object obj)
exclui um objeto. Esse objeto pode pertencer à sessão (vinculado) ou não (desvinculado). Durante a sincronização da sessão com o banco de dados, será realizada uma operação SQL DELETE nesse objeto.
// carregamos um funcionário do BD
Empregado e = session.Get<Empregado>(143);
// excluímos o funcionário
session.Delete(e);
void Flush()
força a sincronização da sessão com o banco de dados. O conteúdo da sessão não é alterado.
session.Flush();
T Get<T>(object id)
busca na base de dados o objeto T com a chave primária id. Se esse objeto não existir, retorna o ponteiro como nulo.
// carregamos um funcionário do BD
Empregado e = session.Get<Empregado>(143);
object Save(object obj)
insere o objeto obj na sessão. Esse objeto não possui chave primária antes do Save. Após o Save, ele passa a ter uma. Durante a sincronização da sessão, será realizada uma operação SQL INSERT no banco de dados.
// criamos um funcionário
Funcionário e = new Funcionário(){...};
// salvamos
e = session.Save(e);
SaveOrUpdate(object obj)
executa a operação Save se o objeto não tiver uma chave primária ou a operação Update se ele já tiver uma.
void Update(object obj)
atualiza o objeto obj no banco de dados. Uma operação SQL UPDATE é então executada no banco de dados.
// carregamos um funcionário da BD
Empregado e = session.Get<Empregado>(143);
// altera-se o nome dele
e.Nom = ...;
// atualiza-se na base de dados
session.Update(e);

1.4.4. A interface IQuery

A interface IQuery permite consultar o banco de dados para extrair dados. Vimos como criar uma instância dela:

IQuery query=session.createQuery("select e from Employe e);

O parâmetro do método createQuery é uma consulta HQL (Hibernate Query Language), uma linguagem análoga à linguagem SQL, mas que consulta classes em vez de tabelas. A consulta acima solicita a lista de todos os funcionários. Aqui estão alguns exemplos de consultas HQL:

select e from Employe e where e.Nom like 'A%'
select e from Employe order by e.Nom asc
select e from Employe e where e.Indemnites.Indice=2

Apresentamos agora alguns dos métodos da interface IQuery:

IList<T> List<T>()
retorna o resultado da consulta na forma de uma lista de objetos T
IList<Empregado> empregados=session.createQuery("select e from Empregado e order by e.Nom asc").List<Empregado>();
IList List()
retorna o resultado da consulta na forma de uma lista, em que cada elemento da lista representa uma linha de resultado da consulta Select na forma de uma matriz de objetos.
IList linhas = session.createQuery("select e.Nom, e.Prenom, e.SS from Empregado").List();
linhas[i][j] representa a coluna j da linha i em um tipo object. Assim, linhas[10][1] é um tipo object que representa o nome de uma pessoa. Geralmente, são necessárias conversões de tipo para recuperar os dados em seu tipo exato.
T UniqueResult<T>()
retorna o primeiro objeto do resultado da consulta
Empregado e=session.createQuery("select e from Empregado e where e.Nom='MARTIN'").UniqueResult<Empregado>();

Uma consulta HQL pode ser configurada:

1
2
3
string numSecu;
...
Employe e=session.createQuery("select e from Employe e where e.SS=:num").SetString("num",numSecu).UniqueResult<Employe>();

Na consulta HQL da linha 3, :num é um parâmetro que deve receber um valor antes que a consulta seja executada. No exemplo acima, o método SetString é utilizado para isso. A interface IQuery dispõe de vários métodos Set para atribuir um valor a um parâmetro:

  • - SetBoolean(string name, bool value)
  • - SetSingle(nome como string, valor único)
  • - SetDouble(nome como string, valor como double)
  • - SetInt32(nome como string, valor como int32)
  • ..

1.5. Alguns exemplos de código

Os exemplos a seguir baseiam-se na arquitetura estudada anteriormente e recapitulada abaixo. O banco de dados é o MySQL [dbpam_nhibernate], também apresentado. Os exemplos são programas de console [1] que utilizam o framework NHibernate [3] para manipular o banco de dados [2].

O projeto em C# no qual se inserem os exemplos a seguir é o já apresentado:

  • em [1], os DLL necessários ao projeto:
    • [NHibernate]: o DLL do framework NHibernate
    • [MySql.Data]: o DLL do conector ADO.NET do SGBD MySQL 5
    • [log4net]: o DLL de uma ferramenta que permite gerar logs
  • no [2], as classes de imagens das tabelas do banco de dados
  • em [3], o arquivo [App.config] que configura toda a aplicação, incluindo o framework [NHibernate]
  • em [4], e aplicativos de console para testes. São esses que apresentaremos parcialmente.

1.5.1. Obter o conteúdo do banco de dados

O programa [ShowDataBase.cs] permite exibir o conteúdo do banco de dados:


using System;
using System.Collections;
using System.Collections.Generic;
using NHibernate;
using NHibernate.Cfg;


namespace PamNHibernateDemos
{
  public class ShowDataBase
  {

    private static ISessionFactory sessionFactory = null;

    // programa principal
    static void Main(string[] args)
    {
      // inicialização da fábrica NHibernate
      sessionFactory = new Configuration().Configure().BuildSessionFactory();
      try
      {
        // exibição do conteúdo do banco de dados
        Console.WriteLine("Affichage base -------------------------------------");
        ShowDataBase1();
      }
      catch (Exception ex)
      {
        // exibição da exceção 
        Console.WriteLine(string.Format("L'erreur suivante s'est produite : [{0}]", ex.ToString()));
      }
      finally
      {
        if (sessionFactory != null)
        {
          sessionFactory.Close();
        }
      }
      // aguardando entrada do teclado
      Console.ReadLine();
    }

    // teste1
    static void ShowDataBase1()
    {
      // início de sessão 
      using (ISession session = sessionFactory.OpenSession())
      {
        // início da transação
        using (ITransaction transaction = session.BeginTransaction())
        {
          // recupera-se a lista de funcionários
          IList<Employe> employes = session.CreateQuery(@"select e from Employe e order by e.Nom asc").List<Employe>();
          // exibindo a lista
          Console.WriteLine("--------------- liste des employés");
          foreach (Employe e in employes)
          {
            Console.WriteLine(e);
          }
          // recuperação da lista de indenizações
          IList<Indemnites> indemnites = session.CreateQuery(@"select i from Indemnites i order by i.Indice asc").List<Indemnites>();
          // exibição
          Console.WriteLine("--------------- liste des indemnités");
          foreach (Indemnites i in indemnites)
          {
            Console.WriteLine(i);
          }
          // recupera-se a lista de contribuições
          Cotisations cotisations = session.CreateQuery(@"select c from Cotisations c").UniqueResult<Cotisations>();
          Console.WriteLine("--------------- tableau des taux de cotisations");
          Console.WriteLine(cotisations);
          // confirmar transação
          transaction.Commit();
        }
      }
    }
  }
}

Explicações:

  • linha 19: o objeto SessionFactory é criado. É ele que nos permitirá obter os objetos Session.
  • linha 24: exibe-se o conteúdo do banco de dados
  • linhas 31-37: o SessionFactory é fechado na cláusula finally do try.
  • linha 43: o método que exibe o conteúdo do banco de dados
  • linha 46: obtém-se uma Session a partir da SessionFactory.
  • linha 49: inicia-se uma transação
  • linha 52: consulta HQL para recuperar a lista de funcionários. Devido à chave estrangeira que vincula a entidade Employe à entidade Indemnite, para cada funcionário, teremos sua indenização.
  • linha 60: consulta HQL para obter a lista de indenizações.
  • linha 68: consulta HQL para obter a única linha da tabela de contribuições.
  • linha 72: fim da transação
  • linha 73: fim do `using Itransaction` da linha 49 – a transação é fechada automaticamente
  • linha 74: fim do `using Isession` da linha 46 – a sessão é fechada automaticamente.

Exibição na tela obtida:

Affichage base -------------------------------------
--------------- liste des employés
[254104940426058|Jouveinal|Marie|5 rue des oiseaux|St Corentin|49203|[1|1,93|2|3|12]]
[260124402111742|Laverti|Justine|La Brûlerie|St Marcel|49014|[2|2,1|2,1|3,1|15]]

--------------- liste des indemnités
[1|1,93|2|3|12]
[2|2,1|2,1|3,1|15]
--------------- tableau des taux de cotisations
[3,49|6,15|9,39|7,88]

Observe-se nas linhas 3 e 4 que, ao consultar um funcionário, também foi obtida sua indenização.

1.5.2. Inserir dados no banco de dados

O programa [FillDataBase.cs] permite inserir dados no banco de dados:


using System;
using System.Collections;
using System.Collections.Generic;
using NHibernate;
using NHibernate.Cfg;


namespace PamNHibernateDemos
{
  public class FillDataBase
  {

    private static ISessionFactory sessionFactory = null;

    // programa principal
    static void Main(string[] args)
    {
      // inicialização da fábrica NHibernate
      sessionFactory = new Configuration().Configure().BuildSessionFactory();
      try
      {
        // exclusão do conteúdo do banco de dados
        Console.WriteLine("Effacement base -------------------------------------");
        ClearDataBase1();
        Console.WriteLine("Affichage base -------------------------------------");
        ShowDataBase();
        Console.WriteLine("Remplissage base -------------------------------------");
        FillDataBase1();
        Console.WriteLine("Affichage base -------------------------------------");
        ShowDataBase();
      }
      catch (Exception ex)
      {
        // exibição da exceção 
        Console.WriteLine(string.Format("L'erreur suivante s'est produite : [{0}]", ex.ToString()));
      }
      finally
      {
        if (sessionFactory != null)
        {
          sessionFactory.Close();
        }
      }
      // aguardando entrada do teclado
      Console.ReadLine();
    }

    // teste1
    static void ShowDataBase()
    {
             // veja o exemplo anterior
    }

    // ClearDataBase1
    static void ClearDataBase1()
    {
      // início de sessão 
      using (ISession session = sessionFactory.OpenSession())
      {
        // início da transação
        using (ITransaction transaction = session.BeginTransaction())
        {
          // recuperar a lista de funcionários
          IList<Employe> employes = session.CreateQuery(@"select e from Employe e").List<Employe>();
          // exclui todos os funcionários
          Console.WriteLine("--------------- suppression des employés associés");
          foreach (Employe e in employes)
          {
            session.Delete(e);
          }
          // recuperação da lista de indenizações
          IList<Indemnites> indemnites = session.CreateQuery(@"select i from Indemnites i").List<Indemnites>();
          // excluir as indenizações
          Console.WriteLine("--------------- suppression des indemnités");
          foreach (Indemnites i in indemnites)
          {
            session.Delete(i);
          }
          // recupera-se a lista de contribuições
          Cotisations cotisations = session.CreateQuery(@"select c from Cotisations c").UniqueResult<Cotisations>();
          Console.WriteLine("--------------- suppression des taux de cotisations");
          if (cotisations != null)
          {
            session.Delete(cotisations);
          }
          // confirmar transação
          transaction.Commit();
        }
      }
    }

    // FillDataBase
    static void FillDataBase1()
    {
      // iniciar sessão 
      using (ISession session = sessionFactory.OpenSession())
      {
        // início da transação
        using (ITransaction transaction = session.BeginTransaction())
        {
          // criam-se duas indenizações
          Indemnites i1 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
          Indemnites i2 = new Indemnites() { Id = 0, Indice = 2, BaseHeure = 2.1, EntretienJour = 2.1, RepasJour = 3.1, IndemnitesCp = 15 };
          // são criados dois funcionários
          Employe e1 = new Employe() { Id = 0, SS = "254104940426058", Nom = "Jouveinal", Prenom = "Marie", Adresse = "5 rue des oiseaux", Ville = "St Corentin", CodePostal = "49203", Indemnites = i1 };
          Employe e2 = new Employe() { Id = 0, SS = "260124402111742", Nom = "Laverti", Prenom = "Justine", Adresse = "La Brûlerie", Ville = "St Marcel", CodePostal = "49014", Indemnites = i2 };
          // criam-se as alíquotas de contribuição
          Cotisations cotisations = new Cotisations() { Id = 0, CsgRds = 3.49, Csgd = 6.15, Secu = 9.39, Retraite = 7.88 };
          // salva-se tudo
          session.Save(e1);
          session.Save(e2);
          session.Save(cotisations);
          // confirmar transação
          transaction.Commit();
        }
      }
    }

  }
}

Explicações

  • linha 19: o SessionFactory é criado
  • linhas 37-43: ela é encerrada na cláusula finally do try
  • linha 55: o método ClearDataBase1, que esvazia o banco de dados. O princípio é o seguinte:
    • recuperam-se todos os funcionários (linha 64) em uma lista
    • eles são excluídos um por um (linhas 67-70)
  • linha 93: o método FillDataBase1 insere alguns dados no banco de dados
  • criam-se duas entidades Indemnites (linhas 102, 103)
  • criam-se dois funcionários com essas indenizações (linhas 105, 106)
  • cria-se um objeto Cotisations na linha 108.
  • linhas 110, 111: as duas entidades “Empregado” são persistidas no banco de dados
  • linha 112: a entidade “Contribuições” é, por sua vez, gravada
  • Pode parecer surpreendente que as entidades Indemnités das linhas 102 e 103 não tenham sido salvas. Na verdade, elas foram salvas ao mesmo tempo que as entidades Employe. Para entender isso, é preciso voltar ao mapeamento da entidade Employe:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PamNHibernateDemos" assembly="pam-nhibernate-demos">
    <class name="Employe" table="EMPLOYES">
        <id name="Id" column="ID" unsaved-value="0">
            <generator class="native" />
        </id>
        <version name="Version" column="VERSION"/>
        <property name="SS" column="SS"/>
        <property name="Nom" column="NOM"/>
        <property name="Prenom" column="PRENOM"/>
        <property name="Adresse" column="ADRESSE"/>
        <property name="Ville" column="VILLE"/>
        <property name="CodePostal" column="CP"/>
        <many-to-one name="Indemnites" column="INDEMNITE_ID" cascade="save-update" lazy="false"/>
    </class>
</hibernate-mapping>

A linha 15, que mapeia a relação de chave estrangeira entre a entidade Employe e a entidade Indemnites, possui o atributo cascade= "save-update", o que faz com que as operações "save " e "update" da entidade Employe sejam propagadas para a entidade interna Indemnites.

Tela exibida:

Effacement base -------------------------------------
--------------- suppression des employés et des indemnités associées
--------------- suppression des indemnités restantes
--------------- suppression des taux de cotisations
Affichage base -------------------------------------
--------------- liste des employés
--------------- liste des indemnités
--------------- tableau des taux de cotisations

Remplissage base -------------------------------------
Affichage base -------------------------------------
--------------- liste des employés
[254104940426058|Jouveinal|Marie|5 rue des oiseaux|St Corentin|49203|[2|2,1|2,1|3,1|15]]
[260124402111742|Laverti|Justine|La Brûlerie|St Marcel|49014|[1|1,93|2|3|12]]
--------------- liste des indemnités
[1|1,93|2|3|12]
[2|2,1|2,1|3,1|15]
--------------- tableau des taux de cotisations
[3,49|6,15|9,39|7,88]

1.5.3. Pesquisa de um funcionário

O programa [Program.cs] possui diversos métodos que ilustram o acesso e o manuseio dos dados do banco de dados. Apresentamos alguns deles.

O método [FindEmployee] permite localizar um funcionário com base em seu número de previdência social:


// FindEmployee
    static void FindEmployee() {
      try {
        // início da sessão 
        using (ISession session = sessionFactory.OpenSession()) {
          // início da transação
          using (ITransaction transaction = session.BeginTransaction()) {
            // buscando um funcionário pelo número SS
            String numSecu = "254104940426058";
            IQuery query = session.CreateQuery(@"select e from Employe e where e.SS=:numSecu");
            Employe employe = query.SetString("numSecu", numSecu).UniqueResult<Employe>();
            if (employe != null) {
              Console.WriteLine("Employe[" + numSecu + "]=" + employe);
            } else {
              Console.WriteLine("Employe[" + numSecu + "] non trouvé...");
            }

            numSecu = "xx";
            employe = query.SetString("numSecu", numSecu).UniqueResult<Employe>();
            if (employe != null) {
              Console.WriteLine("Employe[" + numSecu + "]=" + employe);
            } else {
              Console.WriteLine("Employe[" + numSecu + "] non trouvé...");
            }

            // confirmar transação
            transaction.Commit();
          }
        }
      } catch (Exception e) {
        Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
      }
    }

Explicações

  • linha 10: a consulta Select configurada por numSecu a ser executada
  • linha 11: a atribuição de um valor ao parâmetro numSecu e a execução do método UniqueResult para obter um único resultado.

Exibição na tela obtida:

Recherche d'un employé -------------------------------------
Employe[254104940426058]=[254104940426058|Jouveinal|Marie|5 rue des oiseaux|St Corentin|49203|[2|2,1|2,1|3,1|15]]
Employe[xx] non trouvé...

1.5.4. Inserção de entidades inválidas

O método a seguir tenta gravar uma entidade [Employe] não inicializada.


// SaveEmptyEmployee
    static void SaveEmptyEmployee() {
      try {
        // início de sessão 
        using (ISession session = sessionFactory.OpenSession()) {
          // início da transação
          using (ITransaction transaction = session.BeginTransaction()) {
            // cria-se um funcionário sem dados
            Employe e = new Employe();
            // cria-se um valor de indenização inexistente
            Indemnites i = new Indemnites() { Id = 0, Indice = 3, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
            // que é associada ao funcionário
            e.Indemnites = i;
            // salva-se o funcionário, deixando os demais campos em branco
            session.Save(e);
            // confirma a transação
            transaction.Commit();
          }
        }
      } catch (Exception e) {
        Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
      }
    }

Explicações

Recordemos o código da classe [Employe]:


namespace PamNHibernateDemos {
    public class Employe {
        // propriedades automáticas
        public virtual int Id { get; set; }
        public virtual int Version { get; set; }
        public virtual string SS { get; set; }
        public virtual string Nom { get; set; }
        public virtual string Prenom { get; set; }
        public virtual string Adresse { get; set; }
        public virtual string Ville { get; set; }
        public virtual string CodePostal { get; set; }
        public virtual Indemnites Indemnites { get; set; }

        // construtores
        public Employe() {
        }

        // ToString
        public override string ToString() {
            return string.Format("[{0}|{1}|{2}|{3}|{4}|{5}|{6}]", SS, Nom, Prenom, Adresse, Ville, CodePostal, Indemnites);
        }
    }
}

Um objeto [Employe] não inicializado terá o valor null em todos os seus campos do tipo string. Ao inserir o registro na tabela [employes], o NHibernate deixará vazias as colunas correspondentes a esses campos. No entanto, na tabela [employes], todas as colunas possuem o atributo not null, o que impede que haja colunas sem valor. O driver ADO.NET lançará, então, uma exceção:

sauvegarde d'un employé vide -------------------------------------
L'exception suivante s'est produite : could not insert: [PamNHibernateDemos.Employe][SQL: INSERT INTO EMPLOYES (VERSION, SS, NOM, PRENOM, ADRESSE, VILLE, CP, INDEMNITE_ID) VALUES (?, ?, ?, ?, ?, ?, ?, ?)]

1.5.5. Criação de dois subsídios com o mesmo índice dentro de uma transação

Na tabela [indemnites], a coluna [indice] foi declarada com o atributo “unique”, o que impede a existência de duas linhas com o mesmo índice. O método a seguir cria duas indenizações com o mesmo índice dentro de uma transação:


// CreateIndemnites1
    static void CreateIndemnites1() {
      try {
        // login 
        using (ISession session = sessionFactory.OpenSession()) {
          // início da transação
          using (ITransaction transaction = session.BeginTransaction()) {
            // criam-se duas indenizações com o mesmo índice
            Indemnites i1 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
            Indemnites i2 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
            // elas são salvas
            session.Save(i1);
            session.Save(i2);
            // confirmação da transação
            transaction.Commit();
          }
        }
      } catch (Exception e) {
        Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
      }
    }

Explicações

  • nas linhas 9 e 10, criam-se duas entidades Indemnites com o mesmo índice. No entanto, no banco de dados, a coluna INDICE possui o atributo UNIQUE.
  • As linhas 12 e 13 colocam as duas entidades Indemnites no contexto de persistência. Esse contexto é sincronizado com o banco de dados durante a validação da transação na linha 15. Essa sincronização resultará em duas entidades INSERT. O segundo provocará uma exceção devido à exclusividade da coluna INDICE. Como estamos dentro de uma transação, o primeiro INSERT será revertido.

O resultado obtido é o seguinte:

Effacement base -------------------------------------
--------------- exclusão de funcionários
--------------- exclusão das indenizações
--------------- exclusão das alíquotas de contribuição
Création de deux indemnités de même indice dans une transaction --------------
L'exception suivante s'est produite : could not insert: [PamNHibernateDemos.Indemnites][SQL: INSERT INTO INDEMNITES (VERSION, INDICE, BASE_HEURE, ENTRETIEN_JOUR, REPAS_JOUR, INDEMNITES_CP) VALUES (?, ?, ?, ?, ?, ?)]
Affichage base -------------------------------------
--------------- lista de funcionários
--------------- lista de indenizações
--------------- tabela de alíquotas de contribuição

Na linha 9, pode-se observar que a tabela [indemnites] está vazia. Nenhuma inserção ocorreu.

1.5.6. Criação de duas indenizações com o mesmo índice fora da transação

O método a seguir cria duas indenizações com o mesmo índice sem utilizar uma transação:


// CreateIndemnites2
    static void CreateIndemnites2() {
      try {
        // início de sessão 
        using (ISession session = sessionFactory.OpenSession()) {

          // criam-se dois subsídios com o mesmo índice
          Indemnites i1 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
          Indemnites i2 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.94, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
          // elas são salvas
          session.Save(i1);
          session.Save(i2);
        }
      } catch (Exception e) {
        Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
      }
    }

Explicações

  • Temos o mesmo código de antes, mas sem transação.
  • A sincronização do contexto de persistência com o banco de dados será feita ao fechar esse contexto, na linha 13 (fechamento do Session). A sincronização irá gerar dois INSERT. O segundo irá falhar devido à exclusividade da coluna INDICE. Mas, como não estamos em uma transação, o primeiro INSERT não será revertido.

O resultado obtido é o seguinte:

1
2
3
4
5
6
7
Création de deux indemnités de même indice sans transaction --------------
L'exception suivante s'est produite : could not insert: [PamNHibernateDemos.Indemnites][SQL: INSERT INTO INDEMNITES (VERSION, INDICE, BASE_HEURE, ENTRETIEN_JOUR, REPAS_JOUR, INDEMNITES_CP) VALUES (?, ?, ?, ?, ?, ?)]
Affichage base -------------------------------------
--------------- lista de funcionários
--------------- lista de indenizações
[1|1,93|2|3|12]
--------------- tabela de alíquotas de contribuição

O banco de dados estava vazio antes da execução do método. Na linha 6, pode-se observar que a tabela [indemnites] possui uma linha.