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 .NET do framework Java Hibernate. Para uma introdução abrangente, consulte:
Título: NHibernate in Action, Autor: Pierre-Henri Kuaté, Editora: Manning, ISBN-13: 978-1932394924
Um ORM (Object Relational Mapper) é um conjunto de bibliotecas que permite que um programa que utiliza uma base de dados interaja com ela sem emitir comandos SQL explícitos e sem conhecer os detalhes do SGBD que está a ser utilizado.
Pré-requisitos
Numa escala de [iniciante-intermédio-avançado], este documento enquadra-se na categoria [intermédio]. A sua compreensão requer vários pré-requisitos que podem ser encontrados em alguns dos documentos que escrevi:
- [Spring IoC], disponível no URL [Spring IoC para .NET]. Apresenta os conceitos básicos de Inversão de Controlo (IoC) ou Injeção de Dependências (DI) na estrutura Spring.NET [Spring.NET | Página inicial].
Recomendações de leitura são, por vezes, fornecidas no início dos parágrafos deste documento. Estas remetem para os documentos anteriores.
Ferramentas
As ferramentas utilizadas neste estudo de caso estão disponíveis gratuitamente na Internet. São as seguintes (dezembro de 2011):
- Nhibernate 3.2 disponível em [http://nhforge.org/Default.aspx]
- Spring.net 1.3.2 disponível no URL [http://www.springframework.net]. O framework Spring.net é muito abrangente. Aqui, utilizaremos apenas a biblioteca que ele fornece para facilitar a utilização do framework Nhibernate.
- Log4net 1.2.10 disponível em [http://logging.apache.org/log4net]. Esta estrutura de registo é utilizada pelo Nhibernate.
- NUnit 2.5, disponível em [http://www.nunit.org/]. Esta estrutura de testes unitários é o equivalente .NET da estrutura JUnit para a plataforma Java.
- O controlador ADO.NET 6.4.4 para o SGBD MySQL 5 está disponível em [http://dev.mysql.com/downloads/connector/net]
Todas as DLLs necessárias para projetos do Visual Studio 2010 foram compiladas numa pasta [libnet4]:
![]() |
1.1. O papel do NHIBERNATE numa arquitetura .NET em camadas
Uma aplicação .NET que utilize uma base de dados pode ser arquitetada em camadas da seguinte forma:
![]() |
A camada [DAO] comunica com o SGBD através da API ADO.NET. Vamos rever os principais métodos desta API.
No modo conectado, a aplicação:
- abre uma ligação à fonte de dados
- trabalha com a fonte de dados no modo de leitura/gravação
- encerra a ligação
Três interfaces ADO.NET estão principalmente envolvidas nessas operações:
- IDbConnection, que encapsula as propriedades e métodos da ligaçã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 instrução SQL SELECT.
A interface IDbConnection
é utilizada para gerir a ligação à base de dados. Entre os métodos M e propriedades P desta interface encontram-se os seguintes:
Nome | Tipo | Função |
P | Cadeia de ligação à base de dados. Especifica todos os parâmetros necessários para estabelecer uma ligação com uma base de dados específica. | |
M | Abre a ligação à base de dados definida por ConnectionString | |
M | Fecha a ligação | |
M | inicia uma transação. | |
P | Estado da ligação: ConnectionState.Closed, ConnectionState.Open, ConnectionState.Connecting, ConnectionState.Executing, ConnectionState.Fetching, ConnectionState.Broken |
Se Connection for uma classe que implementa a interface IDbConnection, a ligação pode ser aberta da seguinte forma:
A interface IDbCommand
é utilizada para executar uma instrução SQL ou um procedimento armazenado. Entre os métodos M e propriedades P desta interface encontram-se os seguintes:
Nome | Tipo | Função |
P | especifica o que executar - obtém os seus valores de uma enumeração: - CommandType.Text: executa a instrução SQL definida na propriedade CommandText. Este é o valor predefinido. - CommandType.StoredProcedure: executa um procedimento armazenado na base de dados | |
P | - o texto da instrução SQL a executar se CommandType= CommandType.Text - o nome do procedimento armazenado a executar se CommandType= CommandType.StoredProcedure | |
P | a ligação IDbConnection a utilizar para executar a instrução SQL | |
P | a transação IDbTransaction na qual a instrução SQL deve ser executada | |
P | A lista de parâmetros para uma instrução SQL parametrizada. A instrução `update articles set price=price*1.1 where id=@id` tem o parâmetro `@id`. | |
M | Para executar uma instrução SQL SELECT. Isto devolve um objeto IDataReader que representa o resultado da instrução SELECT. | |
M | para executar uma instrução SQL Update, Insert ou Delete. Retorna o número de linhas afetadas pela operação (atualizadas, inseridas ou eliminadas). | |
M | para executar uma instrução SQL Select que retorna um único resultado, como: select count(*) from articles. | |
M | para criar os parâmetros IDbParameter de uma instrução SQL parametrizada. | |
M | permite-lhe otimizar a execução de uma consulta parametrizada quando esta é executada várias vezes com parâmetros diferentes. |
Se Command for uma classe que implementa a interface IDbCommand, a execução de uma instrução SQL sem uma transação assumirá a seguinte forma:
A interface IDataReader
Utilizada para encapsular os resultados de uma instrução SQL Select. Um objeto IDataReader representa uma tabela com linhas e colunas, que são processadas sequencialmente: primeiro a primeira linha, depois a segunda e assim por diante. Entre os métodos (M) e propriedades (P) desta interface encontram-se os seguintes:
Nome | Tipo | Função |
P | O número de colunas na tabela do IDataReader | |
M | GetName(i) devolve o nome da coluna i na tabela IDataReader. | |
P | Item[i] representa a coluna número i da linha atual na tabela IDataReader. | |
M | avança para a linha seguinte da tabela IDataReader. Devolve True se a operação de leitura for bem-sucedida, False caso contrário. | |
M | Fecha a tabela IDataReader. | |
M | GetBoolean(i): devolve o valor booleano da coluna i na linha atual da tabela IDataReader. Outros métodos semelhantes incluem: GetDateTime, GetDecimal, GetDouble, GetFloat, GetInt16, GetInt32, GetInt64, GetString. | |
M | Getvalue(i): devolve o valor da coluna i na linha atual da tabela IDataReader como um tipo de objeto. | |
M | IsDBNull(i) retorna True se a coluna i da linha atual na tabela IDataReader não tiver valor, o que é representado pelo valor SQL NULL. |
A utilização de um objeto IDataReader é frequentemente semelhante ao seguinte:
Na arquitetura anterior,
![]() |
o conector [ADO.NET] está ligado ao SGBD. Assim, a classe que implementa a interface [IDbConnection] é:
- a classe [MySQLConnection] para o SGBD MySQL
- a classe [SQLConnection] para o SGBD SQLServer
A camada [DAO] depende, portanto, do SGBD utilizado. Algumas estruturas (Linq, Ibatis.net, NHibernate) eliminam esta restrição adicionando uma camada adicional entre a camada [DAO] e o conector [ADO.NET] do SGBD utilizado. Aqui, utilizaremos a estrutura [NHibernate].
![]() |
No diagrama acima, a camada [DAO] já não comunica com o conector [ADO.NET], mas sim com a estrutura NHibernate, que lhe fornece uma interface independente do conector [ADO.NET] que está a ser utilizado. Esta arquitetura permite-lhe mudar de SGBD sem alterar a camada [DAO]. Apenas o conector [ADO.NET] precisa de ser alterado.
1.2. A base de dados de exemplo
Para demonstrar como trabalhar com o NHibernate, utilizaremos a seguinte base de dados MySQL [dbpam_nhibernate]:
![]() |
- Em [1], a base de dados tem três tabelas:
- [employees]: uma tabela que regista os funcionários de uma creche
- [contribuições]: uma tabela que armazena as taxas de contribuição para a segurança social
- [folha de pagamento]: uma tabela que armazena informações utilizadas para calcular o salário dos funcionários
tabela [employees]
![]() |
- Em [2], a tabela de funcionários, e em [3], o significado dos seus campos
O conteúdo da tabela poderia ser o seguinte:
Tabela [contribuições]
![]() |
- Em [4], a tabela de contribuições, e em [5], o significado dos seus campos
O conteúdo da tabela poderia ser o seguinte:
Tabela [indemnizações]
![]() |
- em [6], a tabela de subsídios, e em [7], o significado dos seus campos
O conteúdo da tabela poderia ser o seguinte:
A exportação da estrutura da base de dados para um ficheiro SQL produz o seguinte resultado:
Note que nas linhas 6, 20 e 36, as chaves primárias ID têm o atributo *<a id="autoincrement"></a> * definido como *autoincrement*. Isto significa que o MySQL irá gerar automaticamente os valores da chave primária sempre que for adicionado um novo registo. O programador não precisa de se preocupar com isto.
1.3. O projeto de demonstração em C#
Para apresentar a configuração e a utilização do NHibernate, utilizaremos a seguinte arquitetura:
![]() |
Um programa de consola [1] irá manipular dados da base de dados [2] através da estrutura [NHibernate] [3]. Isto irá levar-nos a apresentar:
- os ficheiros de configuração do NHibernate
- a API do NHibernate
O projeto em C# será o seguinte:
![]() |
Os elementos necessários para o projeto são os seguintes:
- em [1], as DLLs necessárias para o projeto:
- [NHibernate]: a DLL do framework NHibernate
- [MySql.Data]: a DLL para o conector ADO.NET do SGBD MySQL
- [log4net]: a DLL da estrutura Log4net para a geração de registos
- em [2], as classes que representam as tabelas da base de dados
- em [3], o ficheiro [App.config] que configura toda a aplicação, incluindo a estrutura [NHibernate]
- em [4], aplicações de consola de teste
1.3.1. Configurar a ligação à base de dados
Voltemos à arquitetura de teste:
![]() |
Como mostrado acima, o [NHibernate] deve poder aceder à base de dados. Para tal, necessita de determinadas informações:
- o SGBD que gere a base de dados (MySQL, SQL Server, Postgres, Oracle, etc.). A maioria dos SGBDs adicionou as suas próprias extensões à linguagem SQL. Ao conhecer o SGBD, o NHibernate pode adaptar as instruções SQL que emite a esse SGBD específico. O NHibernate utiliza o conceito de dialetos SQL.
- os parâmetros de ligação à base de dados (nome da base de dados, nome de utilizador do proprietário da ligação e palavra-passe)
Estas informações podem ser colocadas no ficheiro de configuração [App.config]. Aqui está o que será utilizado com uma base de dados MySQL 5:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!---->
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
</configSections>
<!---->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<!-- configuration sections
<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>
<!-- configuration NHibernate -->
<!-- This section contains the log4net configuration settings
! -->
<log4net>
<!-- NOTE IMPORTANTE: logs are not active by default. They must be activated programmatically with the log4net.Config.XmlConfigurator.Configure() instruction;-->
<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] <%X{auth}> - %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] <%X{auth}> - %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] <%X{auth}> - %m%n"/>
</layout>
</appender>
<!-- Define an output appender (where the logs can go) -->
<root>
<priority value="INFO" />
<!-- Setup the root category, set the default priority level and add the appender(s) (where the logs will go)
<appender-ref ref="LogFileAppender" />
<appender-ref ref="LogDebugAppender"/>
-->
<appender-ref ref="ConsoleAppender"/>
</root>
<!-- Specify the level for some specific namespaces -->
<!-- Level can be : ALL, DEBUG, INFO, WARN, ERROR, FATAL, OFF -->
<logger name="NHibernate">
<level value="INFO" />
</logger>
</log4net>
</configuration>
- Linhas 4–7: definem secções de configuração no ficheiro [App.config]. Considere a linha 6:
<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
Esta linha define a secção de configuração do NHibernate no ficheiro [App.config]. Possui dois atributos: name e type.
- O atributo [name] nomeia a secção de configuração. Esta secção deve ser delimitada aqui pelas tags <name>...</name>, neste caso <hibernate-configuration>...</hibernate-configuration> nas linhas 11–24.
- O atributo [type=class,DLL] especifica o nome da classe responsável por gerir a secção definida pelo atributo [name], bem como a DLL que contém essa classe. Aqui, a classe chama-se [NHibernate.Cfg.ConfigurationSectionHandler] e está localizada na DLL [NHibernate.dll]. Recorde-se que esta DLL é uma das referências do projeto em questão.
Agora, vamos analisar a secção de configuração do NHibernate:
<!-- configuration 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 do NHibernate está contida na tag <hibernate-configuration>. O atributo xmlns (XML Namespace) especifica a versão utilizada para configurar o NHibernate. Ao longo do tempo, a forma como o NHibernate é configurado evoluiu. Aqui, é utilizada a versão 2.2.
- Linha 3: Toda a configuração do NHibernate está contida na tag <session-factory> (linhas 3 e 14). Uma sessão do NHibernate é a ferramenta utilizada para trabalhar com uma base de dados de acordo com o esquema:
- abrir sessão
- trabalhar com a base de dados utilizando métodos da API do NHibernate
- fechar sessão
A sessão é criada por uma fábrica, um termo genérico que se refere a uma classe capaz de criar objetos. As linhas 3–14 configuram esta fábrica.
- Linhas 4, 6, 8, 9: configuram a ligação à base de dados de destino. As principais informações incluem o nome do SGBD utilizado, o nome da base de dados, o ID do utilizador e a palavra-passe.
- Linha 4: define o fornecedor de ligação, a entidade à qual é solicitada uma ligação à base de dados. O valor da propriedade [connection.provider] é o nome de uma classe NHibernate. Esta propriedade é independente do SGBD utilizado.
- Linha 6: O controlador ADO.NET a utilizar. Este é o nome de uma classe NHibernate especializada para um determinado SGBD, neste caso o MySQL. A linha 6 foi comentada porque não é essencial.
- Linha 8: A propriedade [dialect] define o dialeto SQL a ser utilizado com o SGBD. Aqui, trata-se do dialeto do SGBD MySQL.
Se mudar de SGBD, como encontrar o dialeto NHibernate correspondente? Volte ao projeto C# anterior e clique duas vezes na DLL [NHibernate] no separador [Referências]:
![]() |
- Em [1], o separador [Object Explorer] apresenta várias DLLs, incluindo as referenciadas pelo projeto.
- Em [2], a DLL [NHibernate]
- Em [3], a DLL [NHibernate]. Aqui pode ver os vários namespaces definidos nessa DLL.
- em [4], o namespace [NHibernate.Dialect], onde pode encontrar as classes que definem os vários dialetos SQL que podem ser utilizados.
- em [5], a classe de dialeto para o SGBD MySQL 5.
![]() |
- em [6], o namespace da classe [MySqlDataDriver] utilizada na linha 6 abaixo:
<!-- configuration 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 cadeia de ligação à base de dados. Esta cadeia tem o formato "param1=val1;param2=val2; ...". O conjunto de parâmetros definido desta forma permite que o controlador do SGBD estabeleça uma ligação. O formato desta cadeia de ligação depende do SGBD utilizado. As cadeias de ligação para os principais SGBDs podem ser encontradas no site [http://www.connectionstrings.com/]. Aqui, a cadeia «Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;» é uma cadeia de ligação para o SGBD MySQL. Indica que:
- Server=localhost; : o SGBD encontra-se na mesma máquina que o cliente que tenta estabelecer a ligação
- Database=dbpam_nhibernate; : a base de dados MySQL em questão
- Uid=root; : o utilizador que abre a ligação é o utilizador root
- Pwd=; : este utilizador não tem palavra-passe (um caso especial neste exemplo)
- Linha 12: A propriedade [show_sql] especifica se o NHibernate deve exibir as instruções SQL que envia para a base de dados nos seus registos. Durante o desenvolvimento, é útil definir esta propriedade como [true] para ver exatamente o que o NHibernate está a fazer.
- Linha 13: Para compreender a tag <mapping>, vamos rever a arquitetura da aplicação:
![]() |
Se o programa de consola fosse um cliente direto do conector ADO.NET e quisesse a lista de funcionários, faria com que o conector executasse uma instrução SQL SELECT e receberia em troca um objeto IDataReader que teria de processar para obter a lista de funcionários que inicialmente pretendia.
No exemplo acima, o programa de consola é o cliente do NHibernate, e o NHibernate é o cliente do conector ADO.NET. Veremos mais tarde que a API do NHibernate permitirá que o programa de consola solicite a lista de funcionários. O NHibernate traduzirá esta solicitação numa instrução SQL SELECT que fará com que o conector ADO.NET a execute. O conector irá devolver um objeto do tipo IDataReader. A partir deste objeto, o NHibernate deve ser capaz de construir a lista de funcionários que foi solicitada. Isto é possível através da configuração. Cada tabela na base de dados está associada a uma classe C#. Assim, com base nas linhas da tabela [employees] devolvidas pelo IDataReader, o NHibernate será capaz de construir uma lista de objetos que representam os funcionários e devolvê-la ao programa de consola. Estes mapeamentos de tabela para classe são definidos em ficheiros de configuração. O NHibernate utiliza o termo «mapeamento» para descrever estas relações.
Voltemos à linha 13 abaixo:
<!-- configuration 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 especifica que os ficheiros de configuração de mapeamento de tabela para classe se encontram no assembly [pam-nhibernate-demos]. Um assembly é o executável ou DLL produzido pela compilação de um projeto. Aqui, os ficheiros de mapeamento serão colocados no assembly do projeto de exemplo. Para encontrar o nome deste assembly, verifique as propriedades do projeto:
![]() |
- em [1], as propriedades do projeto
- no separador [Aplicação] [2], o nome do assembly [3] que será gerado.
- Como o tipo de saída é [Aplicação de Consola] [4], o ficheiro gerado quando o projeto for compilado terá o nome [pam-nhibernate-demos.exe]. Se o tipo de saída fosse [Biblioteca de Classes] [5], o ficheiro gerado quando o projeto fosse compilado teria o nome [pam-nhibernate-demos.dll]
- O assembly é gerado na pasta [bin/Release] do projeto [6].
A partir da explicação anterior, note que a tabela de mapeamento <--> ficheiros de classe deve ser incluída no ficheiro [pam-nhibernate-demos.exe] [6].
1.3.2. Configurar a tabela de mapeamento <-->classe
Voltemos à arquitetura do projeto em questão:
![]() |
- Em [1], o programa de consola utiliza os métodos da API do framework NHibernate. Estes dois blocos trocam objetos.
- Em [2], o NHibernate utiliza a API de um conector .NET. Envia comandos SQL para o SGBD de destino.
O programa de consola irá manipular objetos que refletem as tabelas da base de dados. Neste projeto, esses objetos e as ligações que os conectam às tabelas da base de dados foram colocados na pasta [Entities] abaixo:
![]() |
- Cada tabela da base de dados corresponde a uma classe e a um ficheiro de mapeamento entre as duas
Tabela | Classe | Mapeamento |
contribuições | MembershipFees.cs | Contributions.hbm.xml |
funcionários | Employee.cs | Employee.hbm.xml |
subsídios | Subsídios.cs | Subsídios.hbm.xml |
1.3.2.1. Mapeamento da tabela [contributions]
Considere a tabela [contributions]:
![]() |
|
Uma linha desta tabela pode ser encapsulada num objeto do tipo [ Cotisations.cs] da seguinte forma:
namespace PamNHibernateDemos {
public class Cotisations {
// automatic properties
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; }
// manufacturers
public Cotisations() {
}
// ToString
public override string ToString() {
return string.Format("[{0}|{1}|{2}|{3}]", CsgRds, Csgd, Secu, Retraite);
}
}
}
Criámos uma propriedade automática para cada coluna da tabela [contributions]. Cada uma destas propriedades deve ser declarada como virtual, porque o NHibernate irá derivar a classe e substituir as suas propriedades. Por conseguinte, estas propriedades devem ser virtuais.
Note, na linha 1, que a classe pertence ao namespace [PamNHibernateDemos].
O ficheiro 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 ficheiro de mapeamento é um ficheiro XML definido dentro da tag <hibernate-mapping> (linhas 2 e 14)
- Linha 4: A tag <class> associa uma tabela da base de dados a uma classe. Aqui, a tabela [COTISATIONS] (atributo table) e a classe [Cotisations] (atributo name). No .NET, uma classe deve ser definida pelo seu nome completo (incluindo o namespace) e pelo assembly que a contém. Estas 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á explicámos como encontrar este nome.
- Linhas 5–7: A tag <id> é utilizada para definir o mapeamento da chave primária da tabela [contributions].
- Linha 5: O atributo name especifica o campo na classe [Cotisations] que irá conter a chave primária da tabela [cotisations]. O atributo column especifica a coluna na tabela [cotisations] que serve como chave primária. O atributo unsaved-value é utilizado para definir uma chave primária que ainda não foi gerada. Este valor permite ao NHibernate saber como guardar um objeto [Cotisations] na tabela [cotisations]. Se este objeto tiver um campo Id definido como 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 na classe [Cotisations]. Aqui, é do tipo int, e o valor por defeito para um tipo int é 0. Um objeto [Cotisations] que ainda não tenha sido guardado (e, portanto, não tenha chave primária) terá, assim, o seu campo Id definido como 0. Se o campo Id fosse do tipo Object ou de um tipo derivado, teríamos escrito unsaved-value=null.
- Linha 6: Quando o NHibernate precisa de guardar um objeto [Cotisations] com o campo Id definido como 0, deve realizar uma operação INSERT na base de dados, durante a qual deve obter um valor para a chave primária do registo. A maioria dos SGBDs possui um método proprietário para gerar automaticamente este valor. A tag <generator> é utilizada para definir o mecanismo a ser utilizado para gerar a chave primária. A tag <generator class="native"> indica que o mecanismo padrão do SGBD em uso deve ser empregado. Vimos na Secção 1.2 que as chaves primárias das nossas três tabelas MySQL tinham o atributo autoincrement. Durante as suas operações INSERT, o NHibernate não fornecerá um valor para a coluna ID do registo adicionado, permitindo que o MySQL gere esse valor.
- Linha 8: A tag <version> é utilizada para definir a coluna da tabela (bem como o campo de classe correspondente) que permite que os registos sejam «versionados». Inicialmente, a versão é definida como 1. É incrementada a cada operação UPDATE. Além disso, todas as operações UPDATE ou DELETE são realizadas com uma cláusula WHERE: ID=id AND VERSION=v1. Um utilizador só pode, portanto, modificar ou eliminar um objeto se tiver a versão correta do mesmo. Se não for esse o caso, o NHibernate lança uma exceção.
- Linha 9: A tag <property> é utilizada para definir um mapeamento de coluna normal (nem uma chave primária nem uma 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]
Considere a tabela [indemnites]:
![]() |
|
Uma linha desta tabela pode ser encapsulada num objeto do tipo [ Indemnites] da seguinte forma:
namespace PamNHibernateDemos {
public class Indemnites {
// automatic properties
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; }
// manufacturers
public Indemnites() {
}
// identity
public override string ToString() {
return string.Format("[{0}|{1}|{2}|{3}|{4}]", Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCp);
}
}
}
O ficheiro de mapeamento para a 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 comparação com o ficheiro de mapeamento explicado anteriormente. A única diferença está na linha 9. O atributo unique="true" indica que existe uma restrição de unicidade na coluna [INDICE] da tabela [indemnites]: não podem existir duas linhas com o mesmo valor na coluna [INDICE].
1.3.2.3. Mapeamento da tabela [employes]
Vamos considerar a tabela [employes]:
![]() |
|
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]. Este campo faz referência à linha da tabela [INDEMNITES] a ser utilizada para calcular os subsídios do funcionário.
A classe [ Employe] que representa a tabela [employes] poderia ser a seguinte:
namespace PamNHibernateDemos {
public class Employe {
// automatic properties
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; }
// manufacturers
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 ficheiro de mapeamento [Employee.hbm.xml] poderia ter o seguinte aspeto:
<?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 nova funcionalidade encontra-se na linha 15, com a introdução de uma nova tag: <many-to-one>. Esta tag é utilizada para mapear uma coluna de chave estrangeira [INDEMNITE_ID] da tabela [EMPLOYEES] para a propriedade [Benefits] da classe [Employee]:
namespace PamNHibernateDemos {
public class Employe {
// automatic properties
..
public virtual Indemnites Indemnites { get; set; }
...
}
}
A tabela [EMPLOYEES] possui uma chave estrangeira [INDEMNITY_ID] que faz referência à coluna [ID] na tabela [INDEMNITIES]. Várias (muitas) linhas na tabela [EMPLOYEES] podem referenciar uma única (uma) linha na tabela [BENEFITS]. Daí o nome da tag <many-to-one>. Esta tag tem os seguintes atributos aqui:
- column: especifica o nome da coluna na tabela [EMPLOYEES] que serve como chave estrangeira na tabela [BENEFITS]
- nome: especifica a propriedade da classe [Employee] associada a esta coluna. O tipo desta propriedade deve ser a classe associada à tabela de destino da chave estrangeira, neste caso a tabela [Compensation]. Sabemos que esta classe é a classe [Indemnites] já descrita. Isto reflete-se na linha 5 acima. Isto significa que quando o NHibernate recupera um objeto [Employee] da base de dados, também recuperará o objeto [Indemnites] correspondente.
- cascade: este atributo pode ter vários valores:
- save-update: uma operação de inserção (save) ou atualização no objeto [Employee] deve ser propagada para o objeto [Benefits] que este contém.
- delete: a eliminação de um objeto [Employee] deve propagar-se para o objeto [Benefits] que este contém.
- all: propaga operações de inserção (salvar), atualização e eliminação.
- none: não propaga nada
Por fim, vamos rever a configuração do NHibernate no ficheiro [App.config]:
<!-- configuration 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 especifica que os ficheiros de mapeamento *.hbm.xml serão encontrados no assembly [pam-nhibernate-demos]. Este não é o comportamento padrão. Deve configurá-lo no projeto C#:
![]() |
- Em [1], selecione as propriedades de um ficheiro de mapeamento
- em [2], a ação de geração deve ser [Recurso Incorporado] [3]. Isto significa que, quando o projeto for gerado, o ficheiro de mapeamento deve ser incorporado no assembly gerado.
1.4. A API do NHibernate
Voltemos à arquitetura do nosso projeto de exemplo:
![]() |
Nas secções anteriores, configurámos o NHibernate de duas formas:
- No [App.config], configurámos a ligação à base de dados
- para cada tabela na base de dados, escrevemos a classe que representa essa tabela e o ficheiro de mapeamento que nos permite converter entre a classe e a tabela e vice-versa.
Ainda precisamos de explorar os métodos oferecidos pelo NHibernate para manipular dados da base de dados: inserir, atualizar, eliminar e listar.
1.4.1. O objeto SessionFactory
Todas as operações do NHibernate ocorrem dentro de uma sessão. Uma sequência típica de operações do NHibernate é a seguinte:
- abrir uma sessão do NHibernate
- iniciar uma transação dentro da sessão
- executar operações de persistência com a sessão (Load, Get, Find, CreateQuery, Save, SaveOrUpdate, Delete)
- confirmar ou reverter a transação
- fechar a sessão do NHibernate
Uma sessão é obtida a partir de uma fábrica [SessionFactory]. Esta fábrica é aquela configurada pela tag <session-factory> no ficheiro de configuração [App.config]:
<!-- configuration 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>
No código C#, o SessionFactory pode ser obtido da seguinte forma:
ISessionFactory sessionFactory = new Configuration().Configure().BuildSessionFactory();
A classe Configuration é uma classe do framework NHibernate. A instrução anterior utiliza a secção de configuração do NHibernate no ficheiro [App.config]. O objeto [ISessionFactory] resultante possui então as:
- informações necessárias para criar uma ligação à base de dados de destino
- ficheiros de mapeamento entre tabelas de base de dados e classes persistentes geridas pelo NHibernate.
1.4.2. A sessão do NHibernate
Depois de criada a SessionFactory (isto é feito apenas uma vez), pode obter as sessões necessárias para realizar operações de persistência do NHibernate. Um trecho de código comum é o seguinte:
try{
// session opening
using (ISession session = sessionFactory.OpenSession())
{
// start of transaction
using (ITransaction transaction = session.BeginTransaction())
{
........................ opérations de persistance
// transaction validation
transaction.Commit();
}
}
}catch (Exception ex){
....
}
- Linha 3: É criada uma sessão a partir da SessionFactory dentro de um bloco using. Quando o bloco using terminar, a sessão será automaticamente encerrada. Sem o bloco using, a sessão teria de ser encerrada explicitamente (session.Close()).
- Linha 6: as operações de persistência serão realizadas dentro de uma transação. Ou todas elas são bem-sucedidas, ou nenhuma delas é bem-sucedida. Dentro do bloco using, a transação é confirmada com um Commit (linha 10). Se uma operação de persistência dentro da transação lançar uma exceção, a transação será automaticamente revertida ao sair do bloco using.
- Os blocos try/catch nas linhas 1 e 13 permitem capturar quaisquer exceções lançadas pelo código dentro do bloco try (sessão, transação, persistência).
1.4.3. A Interface ISession
Apresentaremos agora alguns dos métodos da interface ISession implementados por uma sessão NHibernate:
inicia uma transação na sessão ITransaction tx = session.BeginTransaction(); | |
limpa a sessão. Os objetos que ela continha são desanexados. session.Clear(); | |
Encerra a sessão. Os objetos nela contidos são sincronizados com a base de dados. Esta operação de sincronização também é realizada no final de uma transação. Este último caso é o mais comum. session.Close(); | |
cria uma consulta HQL (Hibernate Query Language) para execução posterior. IQuery query = session.createQuery("select e from Employee e"); | |
elimina um objeto. Este objeto pode pertencer à sessão (associado) ou não (não associado). Quando a sessão estiver sincronizada com a base de dados, será executada uma operação SQL DELETE neste objeto. // carrega um funcionário da base de dados Employee e = session.Get<Employee>(143); // eliminá-lo session.Delete(e); | |
Força a sessão a sincronizar-se com a base de dados. O conteúdo da sessão não é alterado. session.Flush(); | |
recupera o objeto T com a chave primária id da base de dados. Se este objeto não existir, devolve um ponteiro nulo. // Carregar um funcionário da base de dados Employee e = session.Get<Employee>(143); | |
adiciona o objeto obj à sessão. Este objeto não tem chave primária antes do Save. Após o Save, passa a ter uma. Quando a sessão é sincronizada, será executada uma operação SQL INSERT na base de dados. // Criar um funcionário Employee e = new Employee(){...}; // guardá-lo e = session.Save(e); | |
Executa uma operação de gravação se obj não tiver uma chave primária, ou uma operação de atualização se já tiver uma. | |
atualiza o objeto obj na base de dados. Em seguida, é executada uma operação SQL UPDATE na base de dados. // Carregar um funcionário da base de dados Employee e = session.Get<Employee>(143); // alterar o nome e.Name = ...; // Atualizar o funcionário na base de dados session.Update(e); |
1.4.4. A interface IQuery
A interface IQuery permite-lhe consultar a base de dados para extrair dados. Já vimos como criar uma instância da mesma:
O parâmetro do método createQuery é uma consulta HQL (Hibernate Query Language), uma linguagem semelhante ao SQL, mas que consulta classes em vez de tabelas. A consulta acima recupera uma 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
Vamos agora apresentar alguns dos métodos da interface IQuery:
retorna o resultado da consulta como uma lista de objetos T IList<Employee> employees = session.createQuery("select e from Employee e order by e.Name asc").List<Employee>(); | |
retorna o resultado da consulta como uma lista, em que cada elemento da lista representa uma linha da consulta SELECT na forma de uma matriz de objetos. IList rows = session.createQuery("select e.LastName, e.FirstName, e.SS from Employee e").List(); lines[i][j] representa a coluna j da linha i como um objeto. Assim, lines[10][1] é um objeto que representa o primeiro nome de uma pessoa. Geralmente, é necessário realizar a conversão de tipos para recuperar os dados no seu tipo exato. | |
retorna o primeiro objeto do resultado da consulta Employee e = session.createQuery("select e from Employee e where e.LastName='MARTIN'").UniqueResult<Employee>(); |
Uma consulta HQL pode ser parametrizada:
Na consulta HQL na linha 3, :num é um parâmetro ao qual deve ser atribuído um valor antes da consulta ser executada. Acima, o método SetString é utilizado para este fim. A interface IQuery fornece vários métodos Set para atribuir um valor a um parâmetro:
- - SetBoolean(string name, bool value)
- - SetSingle(string name, single value)
- - SetDouble(string nome, double valor)
- - SetInt32(string name, int32 value)
- ..
1.5. Alguns exemplos de código
Os exemplos seguintes baseiam-se na arquitetura discutida anteriormente e resumida abaixo. A base de dados é a base de dados MySQL [dbpam_nhibernate] também apresentada. Os exemplos são programas de consola [1] que utilizam o framework NHibernate [3] para manipular a base de dados [2].
![]() |
O projeto C# que contém os exemplos que se seguem é o já apresentado:
![]() |
- em [1], as DLLs necessárias para o projeto:
- [NHibernate]: a DLL do framework NHibernate
- [MySql.Data]: a DLL do conector ADO.NET para o SGBD MySQL 5
- [log4net]: a DLL para uma ferramenta de registo
- em [2], as classes que representam as tabelas da base de dados
- em [3], o ficheiro [App.config] que configura toda a aplicação, incluindo a estrutura [NHibernate]
- em [4], aplicações de consola de teste. São estas que iremos apresentar em parte.
1.5.1. Recuperação do conteúdo da base de dados
O programa [ShowDataBase.cs] exibe o conteúdo da base 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;
// main program
static void Main(string[] args)
{
// factory initialization NHibernate
sessionFactory = new Configuration().Configure().BuildSessionFactory();
try
{
// database content display
Console.WriteLine("Affichage base -------------------------------------");
ShowDataBase1();
}
catch (Exception ex)
{
// we display the exception
Console.WriteLine(string.Format("L'erreur suivante s'est produite : [{0}]", ex.ToString()));
}
finally
{
if (sessionFactory != null)
{
sessionFactory.Close();
}
}
// keyboard wait
Console.ReadLine();
}
// test1
static void ShowDataBase1()
{
// session opening
using (ISession session = sessionFactory.OpenSession())
{
// start of transaction
using (ITransaction transaction = session.BeginTransaction())
{
// retrieve the list of employees
IList<Employe> employes = session.CreateQuery(@"select e from Employe e order by e.Nom asc").List<Employe>();
// we display it
Console.WriteLine("--------------- liste des employés");
foreach (Employe e in employes)
{
Console.WriteLine(e);
}
// retrieve the list of benefits
IList<Indemnites> indemnites = session.CreateQuery(@"select i from Indemnites i order by i.Indice asc").List<Indemnites>();
// we display it
Console.WriteLine("--------------- liste des indemnités");
foreach (Indemnites i in indemnites)
{
Console.WriteLine(i);
}
// retrieve the list of contributions
Cotisations cotisations = session.CreateQuery(@"select c from Cotisations c").UniqueResult<Cotisations>();
Console.WriteLine("--------------- tableau des taux de cotisations");
Console.WriteLine(cotisations);
// commit transaction
transaction.Commit();
}
}
}
}
}
Explicações:
- linha 19: o objeto SessionFactory é criado. É isto que nos permitirá obter objetos Session.
- linha 24: o conteúdo da base de dados é apresentado
- Linhas 31–37: O SessionFactory é fechado na cláusula finally do bloco try.
- linha 43: o método que exibe o conteúdo da base de dados
- Linha 46: Obtemos 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 liga a entidade Employee à entidade Compensation, cada funcionário terá a sua remuneração.
- Linha 60: consulta HQL para recuperar a lista de subsídios.
- Linha 68: consulta HQL para recuperar 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 é encerrada automaticamente
- Linha 74: Fim do `using Isession` da linha 46 – a sessão é encerrada automaticamente.
Exibição no ecrã:
Note-se que nas linhas 3 e 4, ao consultar um funcionário, a sua remuneração também foi apresentada.
1.5.2. Inserção de dados na base de dados
O programa [FillDataBase.cs] permite inserir dados na base 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;
// main program
static void Main(string[] args)
{
// factory initialization NHibernate
sessionFactory = new Configuration().Configure().BuildSessionFactory();
try
{
// delete database contents
Console.WriteLine("Effacement base -------------------------------------");
ClearDataBase1();
Console.WriteLine("Affichage base -------------------------------------");
ShowDataBase();
Console.WriteLine("Remplissage base -------------------------------------");
FillDataBase1();
Console.WriteLine("Affichage base -------------------------------------");
ShowDataBase();
}
catch (Exception ex)
{
// exception is displayed
Console.WriteLine(string.Format("L'erreur suivante s'est produite : [{0}]", ex.ToString()));
}
finally
{
if (sessionFactory != null)
{
sessionFactory.Close();
}
}
// keyboard wait
Console.ReadLine();
}
// test1
static void ShowDataBase()
{
// see previous example
}
// ClearDataBase1
static void ClearDataBase1()
{
// session opening
using (ISession session = sessionFactory.OpenSession())
{
// start of transaction
using (ITransaction transaction = session.BeginTransaction())
{
// retrieve the list of employees
IList<Employe> employes = session.CreateQuery(@"select e from Employe e").List<Employe>();
// we cut all employees
Console.WriteLine("--------------- suppression des employés associés");
foreach (Employe e in employes)
{
session.Delete(e);
}
// retrieve the list of allowances
IList<Indemnites> indemnites = session.CreateQuery(@"select i from Indemnites i").List<Indemnites>();
// we do away with allowances
Console.WriteLine("--------------- suppression des indemnités");
foreach (Indemnites i in indemnites)
{
session.Delete(i);
}
// retrieve the list of contributions
Cotisations cotisations = session.CreateQuery(@"select c from Cotisations c").UniqueResult<Cotisations>();
Console.WriteLine("--------------- suppression des taux de cotisations");
if (cotisations != null)
{
session.Delete(cotisations);
}
// commit transaction
transaction.Commit();
}
}
}
// FillDataBase
static void FillDataBase1()
{
// session opening
using (ISession session = sessionFactory.OpenSession())
{
// start of transaction
using (ITransaction transaction = session.BeginTransaction())
{
// two allowances are created
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 };
// we create two employees
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 };
// we create the contribution rates
Cotisations cotisations = new Cotisations() { Id = 0, CsgRds = 3.49, Csgd = 6.15, Secu = 9.39, Retraite = 7.88 };
// save it all
session.Save(e1);
session.Save(e2);
session.Save(cotisations);
// commit transaction
transaction.Commit();
}
}
}
}
}
Explicações
- linha 19: o SessionFactory é criado
- Linhas 37–43: É encerrada na cláusula `finally` do bloco `try`
- Linha 55: O método ClearDataBase1, que limpa a base de dados. O processo é o seguinte:
- Recuperamos todos os funcionários (linha 64) para uma lista
- eliminamo-los um a um (linhas 67–70)
- Linha 93: O método FillDataBase1 insere alguns dados na base de dados
- criamos duas entidades Indemnites (linhas 102, 103)
- criamos dois funcionários com estes subsídios (linhas 105, 106)
- Criamos um objeto Cotisations na linha 108.
- Linhas 110, 111: As duas entidades Employee são persistidas na base de dados
- linha 112: a entidade Cotisations é, por sua vez, gravada
- Pode parecer surpreendente que as entidades «Indemnities» nas linhas 102 e 103 não tenham sido guardadas. Na verdade, foram guardadas ao mesmo tempo que as entidades «Employee». Para compreender isto, temos de analisar o mapeamento da entidade «Employee»:
<?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 Employee e a entidade Allowances, possui o atributo cascade="save-update", o que significa que as operações "save" e "update" na entidade Employee são propagadas para a entidade interna Allowances.
Exibição do ecrã obtida:
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]
[3.49|6.15|9.39|7.88]
1.5.3. Pesquisar um funcionário
O programa [Program.cs] contém vários métodos que demonstram como aceder e manipular dados da base de dados. Apresentamos aqui alguns deles.
O método [FindEmployee] permite-lhe encontrar um funcionário pelo seu número de segurança social:
// FindEmployee
static void FindEmployee() {
try {
// session opening
using (ISession session = sessionFactory.OpenSession()) {
// start of transaction
using (ITransaction transaction = session.BeginTransaction()) {
// search for an employee using his SS number
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é...");
}
// commit transaction
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 para ser executada
- linha 11: Atribuição de um valor ao parâmetro numSecu e execução do método UniqueResult para devolver um único resultado.
Exibição obtida no ecrã:
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 seguinte tenta guardar uma entidade [Employee] não inicializada.
// SaveEmptyEmployee
static void SaveEmptyEmployee() {
try {
// session opening
using (ISession session = sessionFactory.OpenSession()) {
// start of transaction
using (ITransaction transaction = session.BeginTransaction()) {
// create an empty employee
Employe e = new Employe();
// we create a non-existent indemnity
Indemnites i = new Indemnites() { Id = 0, Indice = 3, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
// associated with the employee
e.Indemnites = i;
// save the employee, leaving the other fields empty
session.Save(e);
// commit transaction
transaction.Commit();
}
}
} catch (Exception e) {
Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
}
}
Explicações
Vamos rever o código da classe [Employee]:
namespace PamNHibernateDemos {
public class Employe {
// automatic properties
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; }
// manufacturers
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 [Employee] não inicializado terá um valor nulo para todos os seus campos de cadeia de caracteres. Ao inserir o registo na tabela [employees], o NHibernate deixará as colunas correspondentes a esses campos vazias. No entanto, na tabela [employees], todas as colunas têm o atributo not null, o que impede que as colunas fiquem sem valor. O controlador ADO.NET lançará então uma exceção:
1.5.5. Criação de dois subsídios com o mesmo índice numa transação
Na tabela [indemnizações], a coluna [índice] foi declarada com o atributo único, o que impede que duas linhas tenham o mesmo índice. O método seguinte cria duas indemnizações com o mesmo índice dentro de uma transação:
// CreateIndemnites1
static void CreateIndemnites1() {
try {
// session opening
using (ISession session = sessionFactory.OpenSession()) {
// start of transaction
using (ITransaction transaction = session.BeginTransaction()) {
// we create two allowances with the same index
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 };
// we save them
session.Save(i1);
session.Save(i2);
// commit transaction
transaction.Commit();
}
}
} catch (Exception e) {
Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
}
}
Explicações
- Nas linhas 9 e 10, são criadas duas entidades Indemnites com o mesmo índice. No entanto, na base de dados, a coluna INDEX tem a restrição UNIQUE.
- As linhas 12 e 13 colocam as duas entidades Indemnites no contexto de persistência. Este contexto é sincronizado com a base de dados quando a transação é confirmada na linha 15. Esta sincronização irá desencadear duas instruções INSERT. A segunda irá causar uma exceção devido à restrição de unicidade na coluna INDICE. Como estamos dentro de uma transação, a primeira instrução INSERT será revertida.
O resultado é o seguinte:
Linha 9: Podemos ver que a tabela [indemnites] está vazia. Não foram feitas inserções.
1.5.6. Criação de dois subsídios com o mesmo índice sem uma transação
O método seguinte cria dois subsídios com o mesmo índice sem utilizar uma transação:
// CreateIndemnites2
static void CreateIndemnites2() {
try {
// session opening
using (ISession session = sessionFactory.OpenSession()) {
// we create two allowances with the same index
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 };
// we save them
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 uma transação.
- A sincronização do contexto de persistência com a base de dados ocorrerá quando este contexto for fechado, na linha 13 (ao fechar a sessão). A sincronização irá acionar duas instruções INSERT. A segunda irá falhar devido à restrição de unicidade na coluna INDICE. No entanto, uma vez que não estamos numa transação, a primeira instrução INSERT não será revertida.
O resultado é o seguinte:
A base de dados estava vazia antes da execução do método. Na linha 6, podemos ver que a tabela [indemnites] tem uma linha.























