Relacionamentos MxN com NHibernate

Todos sabemos que manter um relacionamento MxN é chato. E claro que ao utilizar uma ferramenta ORM podemos minimizar essa dor de cabeça.  Imaginem que em seu banco de dados exista a seguinte situação:

 

digrama-sql

Nosso modelo de objetos está da seguinte forma:

ClassDiagram1

Observe que em nenhum momento temos alguma classe que representa a tabela associativa TURMA_X_ALUNO. É no mapeamento das classes que vamos indicar a existência dessa tabela e informar ao NHibernate que a coleção Turmas e Alunos devem utilizá-la. Vamos ver primeiramento o mapeamento da classe Aluno.

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Dominio"  namespace="Dominio" >
  <class name="Aluno" table="ALUNO" >
    <id name="Id" column="ID_ALUNO" type="Int32" unsaved-value="0">
        <generator class="hilo"/>
    </id>
    <property name="Nome" column="NOME" type="string" length="100" not-null="true" />
    <idbag name="Turmas" table="TURMA_X_ALUNO" generic="true" lazy="true" cascade="save-update" inverse="true">
      <collection-id type="Int32" column="ID_TURMA_X_ALUNO">
        <generator class ="hilo"/>
      </collection-id>
      <key column="ID_ALUNO" />
      <many-to-many class="Turma" column="ID_TURMA"/>
    </idbag>

  </class>
</hibernate-mapping>

Destaquei em azul a parte que nos interessa. Veja que utilizamos uma idbag para representar a coleção de  Turmas. Ela foi utilizada porque nossa tabela associativa possui uma chave primária própria, que está mapeada na tag colection-id. Em vermelho estão destacadas coisas importantes também. Definimos que a tabela que mantém a coleção de Turmas é nossa tabela associativa e informamos ao NHibernate que não é por aqui que vamos realizar as associações. Isso quer dizer que não vamos adicionar turmas aos alunos mas sim, alunos às turmas existentes. Então se eu fizer algo assim:

Aluno.Turmas.Add(novaTurma);

Essa nova associação não será salva, porque a marcamos com inverse=true. Depois dizemos que essa mesma associação é many-to-many, informando também de qual classe. Dessa forma, automaticamente o NHibernate já sabe como preencher a lista Turmas.   Quanto ao mapeamento de Turma, é semelhante, veja.

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Dominio"  namespace="Dominio" >
  <class name="Turma" table="TURMA" >
    <id name="Id" column="ID_TURMA" type="Int32" unsaved-value="0">
      <generator class="hilo"/>
    </id>
    <property name="Descricao" column="DESCRICAO" type="string" length="100" not-null="true" />

    <idbag name="Alunos" table="TURMA_X_ALUNO" generic="true" lazy="true" cascade="save-update">
      <collection-id type="Int32" column="ID_TURMA_X_ALUNO">
        <generator class ="hilo"/>
      </collection-id>

      <key column="ID_TURMA" />
      <many-to-many class="Aluno" column="ID_ALUNO"/>
    </idbag>
  </class>
</hibernate-mapping>

A diferença é que não especificamos aqui a tag inverse=true  é porque é por aqui que vamos realizar as associações. Veja um teste feito:

            Configuration cfg = new Configuration();
            cfg.Configure();
            cfg.AddAssembly(typeof(Aluno).Assembly);
            ISessionFactory factory = cfg.BuildSessionFactory();
            ISession session = factory.OpenSession();
            ITransaction transaction = session.BeginTransaction();

            Aluno objAluno1 = new Aluno();
            objAluno1.Nome = "Paulo Quicoli";

            Aluno objAluno2 = new Aluno();
            objAluno2.Nome = "Sem Nome";

            session.Save(objAluno1);
            session.Save(objAluno2);

            Turma objTurma = new Turma();
            objTurma.Descricao = "Teste de MxN";

            objTurma.Alunos.Add(objAluno1);
            objTurma.Alunos.Add(objAluno2);

            session.Save(objTurma);
            try
            {
                transaction.Commit();
                session.Flush();
            }
            catch
            {
                transaction.Rollback();
            }
            finally
            {
                session.Close();
                Console.ReadLine();
            }

É muito simples manter esse tipo de relacionamento com o NHibernate, o detalhe como sempre, é acertar no mapeamento.   Observe também que não estou usando mais ids do tipo identity, após recomendação do meu último post…

Abraço a todos…

http://cid-b27ccfaa07b93be8.skydrive.live.com/embedrowdetail.aspx/Public/DemoMany-to-Many/DemoMany-to-Many.rar

Esse post foi publicado em NHibernate. Bookmark o link permanente.

Uma resposta para Relacionamentos MxN com NHibernate

  1. Alexandre disse:

    Olá Quicoli,

    A partir da leitura do seu post corrigi um comportamento não esperado na minha aplicação já que estava usando em vez de . Com a utilização do idbag consegui fazer consultas, inserts e updates nas minhas tabelas.

    Olhando o log SQL da minha aplicação notei um comportamento não ideal que explico abaixo:

    Tenho duas entidades: entidade1 e entidade2 que se relacionam em MxN. entidade 1 tem uma lista de entidade2 e vice-versa. Quando realizo uma consulta para obter uma lista de entidade1 o NHibernate realiza o select com os devidos left joins para a tabela de relacionamento e a tabela entidade2. Até aí tudo bem.

    Em seguida no log eu noto que o nHibernate realiza um select para cada entidade2 associada a uma entidade1. Creio que ele está fazendo isto para popular a lista de entidade1 contida em entidade2.

    Como eliminar este comportamento, ou seja, fazer com que o nHibernate realize apenas um select para popular a lista de entidades?

    Outro comportamento indesejado que notei foi quando insiro um novo item na lista de entidades e mando salvar o objeto. Para esta operação o nHibernate deleta todos os registros na tabela de relacionamento e insere-os novamente incluindo o novo registro. Esperava que o nHibernate realizasse apenas o insert do novo registro, ou seja, ele está refazendo toda a lista quando se inclui um ou mais registros.

    Coloquei os mapeamentos das entidades no forum do nHibernate caso você tenha um tempinho para olhar.

    https://groups.google.com/forum/?fromgroups#!topic/nhibernate-hispano/jSbfqTJUwpo

    Obs: Estou usando NH3.3 CR1 + unhAddins

    Desde já agradeço.

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