Tag Archives: orm

3 em 1 – Mapeamento Objeto-Relacional com NHibernate

No NHibernate existem três formas de fazer o mapeamento entre suas classes de domínio e suas tabelas relacionais, são elas:

  1. Arquivos XML;
  2. Atributos (Anotações);
  3. Mapeamento Fluente;

Vou explicar como fazer o mapeamento usando cada uma destas opções bem como apontar as vantagens e desvantagens de cada uma delas.

1. Arquivos XML

Este é o mais comun pois se trata da forma mais antiga de se fazer o mapeamento. Como o NHibernate surgiu do Hibernate (Java), e em Java era muito comum utilizar arquivos XML para fazer configurações (ainda é comum), foi assim que o NHibernate começou também. Nesta opção nós temos um arquivo XML para cada entidade. Veja este exemplo.

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" auto-import="true">
  <class name="MyApplication.Domain.Customer, MyApplication.Domain" lazy="true">
    <id name="Id">
      <generator class="identity" />
    </id>
    <property name="FirstName" type="String" column ="FirstName" length="40" not-null="true" />
    <property name="LastName" type="String" column="LastName" length="100" not-null="true" />
    <property name="Birthday" type="DateTime" column="Birthday" not-null="false" />
  </class>
</hibernate-mapping>

E a entidade ficaria assim.

public class Customer
{
    public virtual int ID { get; set; }
    public virtual string FirstName { get; set; }
    public virtual string LastName { get; set; }
    public virtual DateTime Birthday { get; set; }
}

Simples assim, não tem muito segredo.

  • Vantagens
    • É o mais maduro pois já está a bastante tempo em uso;
  • Desvantagens
    • Configuração complicada;
    • É necessário lembrar de compilar junto com os Assemblies;
    • Está muito propício ao erro, se alterar a entidade precisa lembrar de alterar o mapeamento;

2. Atributos (Anotações)

Atributo é o nome dado às classes em C# que são usadas para decorar propriedades ou métodos. É equivalente à anotação em Java. Nesta opção agora não temos mais arquivos externos, todo o mapeamento é feito na própria classe. Olha só um exemplo do mesmo mapeamento que fizemos acima.

[Class]
public class Customer
{
    [Id(Name = "Id")]
    [Generator(1, Class = "Identity")]
    public virtual int ID { get; set; }

    [Property(Name="FirstName", NotNull = true, Length = 40)]
    public virtual string FirstName { get; set; }

    [Property(Name="LastName", NotNull = true, Length = 100)]
    public virtual string LastName { get; set; }

    [Property(Name="Birthday", NotNull = false, )]
    public virtual DateTime Birthday { get; set; }
}
  • Vantagens
    • Compilação facilitada;
    • Alteração fica centralizada em apenas um lugar;
  • Desvantagens
    • Poluição da classe, começa a ter mais linhas, fica menos legível e cria uma forte dependência com o NHibernate;

3. Mapeamento Fluente

Este é a mais nova opção de mapeamento e apenas disponível para .Net. Trata-se de uma combinação do melhor de cada um dos modelos acima. Olha como fica.

public class Customer
{
    public virtual int ID { get; set; }
    public virtual string FirstName { get; set; }
    public virtual string LastName { get; set; }
    public virtual DateTime Birthday { get; set; }
}

Veja que minha entidade não mudou nada com relação ao primeiro exemplo.

public class CustomerMap : ClassMap<Customer>
{
    public CustomerMap()
    {
        Id(x => x.Id);
        Map(x => x.FirstName).Length(40).Not.Nullable();
        Map(x => x.LastName).Length(100).Not.Nullable();
        Map(x => x.Birthday).Nullable();
    }
}

Agora sim chegamos numa solução que eu chamaria de perfeita. Para saber mais sobre o Fluent NHibernate acesse o site oficial.

  • Vantagens
    • Compilação facilitada;
    • Classes pequenas, simples e limpas;
  • Desvantagens
    • Este framework é bem novo quando comparado com os outros dois, por isso pode apresentar alguns defeitos. Entretanto, até agora nunca tive problemas.

Fico pensando se existe alguma outra forma de realizar os mapeamentos. Me parecem que todas elas já foram exploradas. Acho que ficou claro que eu gosto bastante do Fluent NHibernate, e você?

Configurar NHibernate em um projeto Web

Neste tutorial vou explicar como dar o primeiro passo com o NHibernate. A configuração não é nada trivial e por isso gera bastante dúvidas.

Vou estar usando a versão mais recente do NHibernate até o momento, se você quiser usar a mesma versão, utilize este link para fazer o download.

Primero passo, criando a estrutura de projetos

Abra o Visual Studio, crie um projeto Web Application e uma Class Library na mesma solução. Vai ficar assim:

Obs.: Eu tenho o costume de deletar a pasta App_Data e a classe Class1 porque não vou utilizar.

Segundo passo, criando o arquivo de configuração

Dentro do projeto Class Library, crie um xml e chame-o de hibernate.cfg.xml. Atenção, é hibernate e não nhibernate!

Dentro deste arquivo ficam as configurações do banco de dados que iremos usar. Aqui será informado qual o provider (sqlserver, mysql, oracle etc), qual a string de conexão, dialeto etc.

Estou usando MSSQL Server e meu arquivo de configuração ficou assim:


<?xml version="1.0" encoding="utf-8"?>
<hibernate-configuration  xmlns="urn:nhibernate-configuration-2.2" >
 <session-factory name="NHibernate">
 <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
 <property name="connection.connection_string">Server=(local);initial catalog=MinhaBase;Integrated Security=SSPI</property>
 <property name="show_sql">false</property>
 <property name="dialect">NHibernate.Dialect.MsSql2000Dialect</property>
 <property name="query.substitutions">true 1, false 0, yes 'Y', no 'N'</property>
 <property name="proxyfactory.factory_class">NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</property>
 <mapping assembly="MercadoVader.NHibernateModule" />
 </session-factory>
</hibernate-configuration>

Obs.: A única configuração que muda entre meu arquivo e o seu será a string de conexão. No meu caso estou usando localhost com um banco de dados chamado e usando windows authentication.

Clique com o botão direito no arquivo hibernate.cfg.xml e vá em suas propriedades, na opção Copy to Output Directory escolha o valor Copy Always.

Terceiro passo, banco de dados

Não há muito segredo aqui, apenas crie uma base de dados e rode este script para criar uma tabela de produtos.


CREATE TABLE [dbo].[PRODUTO](
 [ID] [int] IDENTITY(1,1) NOT NULL,
 [NOME] [varchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
 [PRECO] [decimal](18, 2) NOT NULL,
 [DATA_CADASTRO] [datetime] NOT NULL,
 CONSTRAINT [PK_PRODUTO] PRIMARY KEY CLUSTERED
(
 [ID] ASC
)WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

Terceiro passo, banco de dados

Não há muito segredo aqui, apenas crie uma base de dados e rode este script para criar uma tabela de produtos.


CREATE TABLE [dbo].[PRODUTO](
 [ID] [int] IDENTITY(1,1) NOT NULL,
 [NOME] [varchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
 [PRECO] [decimal](18, 2) NOT NULL,
 [DATA_CADASTRO] [datetime] NOT NULL,
 CONSTRAINT [PK_PRODUTO] PRIMARY KEY CLUSTERED
(
 [ID] ASC
)WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

Quarto passo, criando um HttpModule pro NHibernate

Essa parte é mais chata, primeiro de tudo, selecione o projeto Class Library e adicione referência à NHibernate.dll que está na pasta Required_Bin dentro do arquivo zip que foi baixado no início do tutorial. Adicione também uma referência à NHibernate.ByteCode.Castle que também veio no arquivo zip. Por último, adicione referência à System.Web, será necessário para poder criar uma classe que herde da System.Web.IHttpModule.

Crie uma classe chamada NHibernateSessionModule dentro da Class Library e coloque este conteúdo nela:

public class NHibernateSessionModule : IHttpModule
{
    public static readonly string KEY = "NHibernateSession";
    private static ISession _session;

    public void Dispose()
    {
    }

    public void Init(HttpApplication context)
    {
        context.BeginRequest += new EventHandler(context_BeginRequest);
        context.EndRequest += new EventHandler(context_EndRequest);
    }

    private void context_EndRequest(object sender, EventArgs e)
    {
        HttpApplication application = (HttpApplication)sender;
        HttpContext context = application.Context;

        ISession session = context.Items[KEY] as ISession;
        if (session != null)
        {
            try
            {
                session.Flush();
                session.Close();
            }
            catch { }
        }
        context.Items[KEY] = null;
    }

    private static ISessionFactory factory = null;

    private static ISessionFactory GetFactory()
    {
        if (factory == null)
        {
            Configuration config = new Configuration();
            if (config == null)
                throw new InvalidOperationException("NHibernate configuration is null.");

            config.Configure();

            factory = config.BuildSessionFactory();
            if (factory == null)
                throw new InvalidOperationException("Call to Configuration.BuildSessionFactory() returned null.");
        }
        return factory;

    }

    public static ISession OpenSession()
    {
        ISession session;
        session = GetFactory().OpenSession();
        if (session == null)
            throw new InvalidOperationException("Call to factory.OpenSession() returned null.");
        return session;
    }

    public static ISession CurrentSession
    {
        get
        {
            if (HttpContext.Current == null)
            {
                if (_session != null)
                {
                    return _session;
                }
                else
                {
                    _session = OpenSession();
                    return _session;
                }
            }
            else
            {
                HttpContext currentContext = HttpContext.Current;
                ISession session = currentContext.Items[KEY] as ISession;
                if (session == null)
                {
                    session = OpenSession();
                    currentContext.Items[KEY] = session;
                }
                return session;
            }
        }
    }

    private void context_BeginRequest(object sender, EventArgs e)
    {
        HttpApplication application = (HttpApplication)sender;
        HttpContext context = application.Context;
        context.Items[KEY] = OpenSession();
    }
}

Pelo fato de esta classe herdar de IHttpModule, ela será chamada toda vez que um HttpRequest for feito para sua aplicação Web. Esta classe cria uma objeto do tipo Session para cada request e só destroi ele no final.Isso garante que teremos apenas uma Sessão aberta para cada request.

Essa parte não é necessária fazer, mas é considerado uma boa prática trabalhar com apenas uma Sessão ao invés de sair criando várias.

Abra o arquivo web.config e procure pela tag httpModules, dentro desta tag adicione mais um valor, que será referência ao módulo que acabamos de criar. Fica assim:

<httpModules>
 <add name="NHibernateSessionModule" type="MercadoVader.NHibernateModule.NHibernateSessionModule, MercadoVader.NHibernateModule"/>
 <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</httpModules>

Quinto passo, o mapeamento

Ufa, quase acabando :)

Dentro da Class Library crie uma classe chamada Produto. Crie também uma pasta chamada Mapping e dentro dela crie o arquivo Produto.hbm.xml.

A classe produto fica assim:

public class Produto
{
    public int Id { get; set; }
    public string Nome { get; set; }
    public DateTime DataCadastro { get; set; }
    public decimal Preco { get; set; }
}

O arquivo Produto.hbm.xml fica assim:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="MercadoVader.NHibernateModule" assembly="MercadoVader.NHibernateModule">

  <class name="Produto" table="PRODUTO">

    <id name="Id">
      <column name="ID" sql-type="int" not-null="true"/>
      <generator class="identity" />
    </id>

    <property name="Nome" type="String">
      <column name="NOME" length="100" not-null="true" />
    </property>

    <property name="Preco" type="Decimal">
      <column name="PRECO" precision="18" scale="2" not-null="true" />
    </property>

    <property name="DataCadastro" type="DateTime">
      <column name="DATA_CADASTRO" not-null="true" />
    </property>
  </class>

</hibernate-mapping>

O que estamos fazendo aqui é criar uma classe que é espelho da nossa tabela no banco de dados e também criando um arquivo de mapeamento que liga a classe com a tabela, nada muito complexo.

Clique com o botão direito no arquivo Produto.hbm.xml e vá em suas propriedades. Na opção Build Action selecione o valor “Embedded Resource”, isso fará com que o arquivo seja compilado dentro de uma DLL.

Sexto passo, será que funciona funciona?

Para testar, você deve selecionar o projeto Web e adicionar referência ao projeto Class Library e também à NHibernate.dll.

Feito isso, entre no Default.aspx.cs e coloque isso no Page_Load:

protected void Page_Load(object sender, EventArgs e)
{
    Produto produto = new Produto();
    produto.Nome = "Biscoito Trakinas";
    produto.Preco = 1.10M;
    produto.DataCadastro = DateTime.Now;
    NHibernateSessionModule.CurrentSession.Save(produto);
}

Se tudo der certo, deverá haver um registro salvo no banco de dados agora.
Deu certo? Parabéns, o NHibernate está configurando e funcionando.
Não deu? Faça um comentário descrevendo o erro e verei se consigo ajudar.

Documentação do NHibernate está aqui: https://www.hibernate.org/hib_docs/nhibernate/1.2/reference/en/html/

Utilize a documentação para aprender sobre HQL e como buscar dados do banco de dados.
Conforme eu havia comentado neste post, o site summer of nhibernate é muito bom para aprender.
Se eu escrevi algo errado, por favor, me corrijam, já é meio tarde e o sono já está batendo.

Aqui está o projeto que eu criei para download.

Clique aqui para fazer o download de 'MercadorVader, NH + ASP.NET' (1.09 MB)

Até a próxima!

Sharing Buttons by Linksku