Como mapear corretamente um objeto de valor usando Fluent NHibernate

03/09/2010 at 10:58 5 comentários

Estamos utilizando DDD na empresa onde trabalho, e analisando o modelo de domínio criado pelos nossos desenvolvedores encontrei relacionamentos entre entidades e objetos de valor com referências cruzadas.  Por exemplo, no relacionamento entre pessoa e telefone:

O que está errado neste diagrama? A entidade Pessoa referencia o objeto de valor Telefone, que também referencia Pessoa. O correto seria Telefone se relacionar com Pessoa como uma composição. Se eu excluir a Pessoa, o Telefone deve ser excluído. Sozinho o Telefone não faz mais sentido.  Pq estas classes foram modeladas desse jeito? Os desenvolvedores não tinham o conhecimento necessário de como mapear corretamente um objeto de valor quando existia uma relação de um-para-muitos com uma entidade utilizando o Fluent NHibernate. Neste caso, no diagrama de classes utilizamos uma composição para representar o relacionamento dessas duas classes, enquanto no banco de dados a  tabela de pessoa vai se relacionar com uma tabela de pessoa x telefones. Não vai existir no banco de dados uma tabela só para telefone. Vc não consegue relacionar um mesmo número de telefone com outros registros ou tabelas.

Agora vamos corrigir o modelo:

1º Retirar os atributos Id e Pessoa da classe Telefone. Telefone é um objeto de valor,  sozinho não faz sentido no domínio e não tem uma identidade;

2º Alterar o relacionamento entre Pessoa e Telefone para composição:

Desse modo o objeto de valor Telefone pode ser utilizado para compor outras entidades.

3º Corrigir o mapeamento da classe Pessoa:

public PessoaMap()
{
    Table("Pessoa");
    ...
    HasMany<Telefone>(x => x.Telefones)
        .Cascade.All()
        .Inverse();
    Alterar para:
    HasMany<Telefone>(x => x.Telefones)
         .Cascade.All() Não utilize o Inverse(), qdo persistir pessoa, os telefones devem ser persistidos tbm (não esqueça de utilizar uma transação explicita).
         .Table("PessoaTelefone") Nome da tabela pessoa x telefones
         .KeyColumn("Pessoa_Id") Nome da fk de pessoa
         .Component(x => {
                          x.Map(r => r.DDD);
                          x.Map(r => r.Numero);
                          x.Map(r => r.Tipo);
                          x.Map(r => r.Ramal);
                          }); Component é utilizado para mapear objeto de valor ou tipos complexos
         ...
}

4º Criar a tabela de pessoa x telefones:

CREATE TABLE [dbo].[PessoaTelefone](
    [Telefone_Id] [int] IDENTITY(1,1) NOT NULL, à Crie um campo identity para ser a PK
    [Pessoa_Id] [int] NULL,
    [DDD] [int] NULL,
    [Numero] [int] NULL,
    [Tipo] [int] NULL,
    [Ramal] [int] NULL,
    CONSTRAINT [PK_PessoaTelefone] PRIMARY KEY CLUSTERED
    (
        [Telefone_Id] ASC
    )
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[PessoaTelefone]  WITH CHECK ADD  CONSTRAINT [FK_PessoaTelefone_Pessoa] FOREIGN KEY([Pessoa_Id])
REFERENCES [dbo].[Pessoa] ([Id])
GO

Se vc quiser saber como mapear objeto de valor quando ele compõe a propriedade de uma entidade, consulte as seguintes referências:

http://wiki.fluentnhibernate.org/Fluent_mapping#Components

http://nhforge.org/blogs/nhibernate/archive/2008/09/06/a-fluent-interface-to-nhibernate-part-2-value-objects.aspx

Fluent NHibernate:

http://fluentnhibernate.org/

[]’s
Eduardo Ernandes da Silva
Arquiteto de sistemas

Anúncios

Entry filed under: Domain-Driven Design. Tags: , , , , .

Implemente inversão de controle (IoC) com Castle Windsor NHibernate 3.0.0 liberado

5 Comentários Add your own

  • 1. Edno  |  01/03/2011 às 22:06

    Muito bom o post! Só não entendi o motivo de ter a coluna Telefone_Id (PK) na tabela PessoaTelefone, daria para usar a coluna Pessoa_Id como PK e assim deixar claro até no banco que a classe Telefone não tem identidade.

    Responder
  • 2. eernandes  |  08/03/2011 às 9:57

    Edno, a tabela PessoaTelefone representa um relacionamento de 1 para n entre pessoa e telefone, ou seja, uma pessoa pode ter vários telefones. Se vc colocar a coluna Pessoa_Id como Pk, a pessoa vai poder ter apenas um telefone.

    Responder
    • 3. Edno  |  08/03/2011 às 10:47

      Tens razão, falha minha!

      Responder
  • 4. Edno  |  01/09/2011 às 16:39

    Olá novamente. Creio que este seu mapeamento só funciona bem no SQL Server, pois ele trabalha com IDENTITY (coluna com auto-incremento) e assim você não precisou mapear a coluna Id da sua tabela PessoaTelefone no Fluent NHibernate, tudo funcionaria automaticamente dentro do baco de dados e pronto. Se você estivesse usando Oracle ou PostgreSQL (que não têm auto-incremento), o mapeamento teria que mudar, possivelmente colocando uma property “Id” na classe Telefone (o que seria péssimo). Outra solução seria implementar o auto incremento no Oracle/PostgreSQL com triggers, mas isso eu também não acho legal, embora seja “menos pior” que estragar a classe Telefone com um Id. Sei lá, fico me perguntando se tem algo mais elegante do que isso…

    Responder
  • 5. Lucas  |  02/04/2012 às 8:34

    Muito bom o seu mapeamento, coloquei em um sistema meu e ficou bem legal, no entanto deu problema com o update. Se eu carrego do banco um objeto com uma lista de objetos dentro, depois eu adiciono um objeto na lista e mando dar um update no objeto inicial, ele não insere aquele objeto que eu adicionei na lista e da pau. Se puder me ajudar agradeço.

    Responder

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

Trackback this post  |  Subscribe to the comments via RSS Feed


Arquivos

Feeds

Calendário

setembro 2010
D S T Q Q S S
« ago   jan »
 1234
567891011
12131415161718
19202122232425
2627282930  

%d blogueiros gostam disto: