Melhor maneira de modelar relacionamentos muitos para um no NHibernate ao lidar com um banco de dados legado?

StackOverflow https://stackoverflow.com/questions/10915

  •  08-06-2019
  •  | 
  •  

Pergunta

Aviso - sou muito novo no NHibernate.Eu sei que esta pergunta parece simples - e tenho certeza de que há uma resposta simples, mas já faz algum tempo que estou perdendo tempo com essa questão.Estou lidando com um banco de dados legado que realmente não pode ser alterado estruturalmente.Tenho uma tabela de detalhes que lista os planos de pagamento aceitos por um cliente.Cada plano de pagamento possui um ID que leva a uma tabela de referência para obter os termos, condições do plano, etc.No meu modelo de objeto, tenho uma classe AcceptedPlan e uma classe Plan.Originalmente, usei um relacionamento muitos-para-um da tabela de detalhes até a tabela ref para modelar esse relacionamento no NHibernate.Também criei um relacionamento um-para-muitos indo na direção oposta da classe Plan para a classe AcceptedPlan.Tudo bem enquanto eu estava simplesmente lendo dados.Eu poderia ir ao meu objeto Plan, que era uma propriedade da minha classe AcceptedPlan para ler os detalhes do plano.Meu problema surgiu quando tive que começar a inserir novas linhas na tabela de detalhes.Pela minha leitura, parece que a única maneira de criar um novo objeto filho é adicioná-lo ao objeto pai e salvar a sessão.Mas não quero ter que criar um novo objeto Plan pai toda vez que quiser criar um novo registro detalhado.Isso parece uma sobrecarga desnecessária.Alguém sabe se estou fazendo isso da maneira errada?

Foi útil?

Solução

Eu evitaria ter um objeto filho contendo seu pai lógico, pois pode ficar muito confuso e muito recursivo rapidamente quando você faz isso.Eu daria uma olhada em como você pretende usar o modelo de domínio antes de fazer esse tipo de coisa.Você ainda pode facilmente ter as referências de ID nas tabelas e simplesmente deixá-las não mapeadas.

Aqui estão dois exemplos de mapeamentos que podem levá-lo na direção certa. Tive que adlibar nomes de tabelas, etc., mas isso poderia ajudar.Provavelmente também sugeriria mapear o StatusId para uma enumeração.

Preste atenção na forma como a bolsa mapeia efetivamente a tabela de detalhes em uma coleção.

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping default-cascade="save-update" xmlns="urn:nhibernate-mapping-2.2">
    <class lazy="false" name="Namespace.Customer, Namespace" table="Customer">
        <id name="Id" type="Int32" unsaved-value="0">
            <column name="CustomerAccountId" length="4" sql-type="int" not-null="true" unique="true" index="CustomerPK"/>
            <generator class="native" />
        </id>

        <bag name="AcceptedOffers" inverse="false" lazy="false" cascade="all-delete-orphan" table="details">
          <key column="CustomerAccountId" foreign-key="AcceptedOfferFK"/>
          <many-to-many
            class="Namespace.AcceptedOffer, Namespace"
            column="AcceptedOfferFK"
            foreign-key="AcceptedOfferID"
            lazy="false"
           />
        </bag>

  </class>
</hibernate-mapping>


<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping default-cascade="save-update" xmlns="urn:nhibernate-mapping-2.2">
    <class lazy="false" name="Namespace.AcceptedOffer, Namespace" table="AcceptedOffer">
        <id name="Id" type="Int32" unsaved-value="0">
            <column name="AcceptedOfferId" length="4" sql-type="int" not-null="true" unique="true" index="AcceptedOfferPK"/>
            <generator class="native" />
        </id>

        <many-to-one 
          name="Plan"
          class="Namespace.Plan, Namespace"
          lazy="false"
          cascade="save-update"
        >
        <column name="PlanFK" length="4" sql-type="int" not-null="false"/>
        </many-to-one>

        <property name="StatusId" type="Int32">
            <column name="StatusId" length="4" sql-type="int" not-null="true"/>
        </property>

  </class>
</hibernate-mapping>

Outras dicas

Não vi o diagrama do seu banco de dados enquanto escrevia.

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping default-cascade="save-update" xmlns="urn:nhibernate-mapping-2.2">
    <class lazy="false" name="Namespace.Customer, Namespace" table="Customer">
        <id name="Id" type="Int32" unsaved-value="0">
            <column name="customer_id" length="4" sql-type="int" not-null="true" unique="true" index="CustomerPK"/>
            <generator class="native" />
        </id>

        <bag name="AcceptedOffers" inverse="false" lazy="false" cascade="all-delete-orphan">
            <key column="accepted_offer_id"/>
            <one-to-many class="Namespace.AcceptedOffer, Namespace"/>
        </bag>

  </class>
</hibernate-mapping>


<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping default-cascade="save-update" xmlns="urn:nhibernate-mapping-2.2">
    <class lazy="false" name="Namespace.AcceptedOffer, Namespace" table="Accepted_Offer">
        <id name="Id" type="Int32" unsaved-value="0">
            <column name="accepted_offer_id" length="4" sql-type="int" not-null="true" unique="true" />
            <generator class="native" />
        </id>

        <many-to-one name="Plan" class="Namespace.Plan, Namespace" lazy="false" cascade="save-update">
            <column name="plan_id" length="4" sql-type="int" not-null="false"/>
        </many-to-one>

  </class>
</hibernate-mapping>

Provavelmente deveria resolver o problema (eu só fiz mapeamentos de exemplo para as coleções, você terá que adicionar outras propriedades).

A abordagem que eu adotaria para modelar isso é a seguinte:

O objeto Customer contém um ICollection <PaymentPlan> PaymentPlans que representa os planos que o cliente aceitou.

O PaymentPlan para o Cliente seria mapeado usando um pacote que usa a tabela de detalhes para estabelecer quais IDs de clientes são mapeados para quais PaymentPlans.Usando cascade all-delete-orphan, se o cliente fosse excluído, tanto as entradas de detalhes quanto os PaymentPlans de propriedade do cliente seriam excluídos.

O objeto PaymentPlan contém um objeto PlanTerms que representa os termos do plano de pagamento.

Os PlanTerms seriam mapeados para um PaymentPlan usando um salvamento e atualização em cascata de mapeamento muitos para um que apenas inseriria uma referência ao objeto PlanTerms relevante no PaymentPlan.

Usando esse modelo, você poderia criar PlanTerms de forma independente e, ao adicionar um novo PaymentPlan a um cliente, criaria um novo objeto PaymentPlan passando o objeto PlanTerms relevante e, em seguida, adicioná-lo à coleção no Cliente relevante.Finalmente, você salvaria o Cliente e deixaria o nhibernate colocar em cascata a operação de salvamento.

Você acabaria com um objeto Customer, um objeto PaymentPlan e um objeto PlanTerms com o Customer (tabela de clientes) possuindo instâncias de PaymentPlans (a tabela de detalhes) que aderem a PlanTerms específicos (a tabela de planos).

Tenho alguns exemplos mais concretos da sintaxe de mapeamento, se necessário, mas provavelmente é melhor trabalhar com seu próprio modelo e não tenho informações suficientes sobre as tabelas do banco de dados para fornecer exemplos específicos.

Não sei se isso ocorre porque minha experiência com o NHibernate é limitada, mas você poderia criar uma classe BaseDetail que tenha apenas as propriedades dos detalhes, pois eles são mapeados diretamente para a tabela Detail.

Em seguida, crie uma segunda classe que herda da classe BaseDetail que possui o objeto Plano Pai adicional para que você possa criar uma classe BaseDetail quando desejar apenas criar uma linha Detail e atribuir o PlanId a ela, mas se precisar preencher um Detail completo registro com o objeto do plano Pai, você pode usar a classe Detail herdada.

Não sei se isso faz muito sentido, mas me avise e esclarecerei melhor.

Acho que o problema que você tem aqui é que seu objeto AcceptedOffer contém um objeto Plan e, em seguida, seu objeto Plan parece conter uma coleção AcceptedOffers que contém objetos AcceptedOffer.A mesma coisa com os clientes.O fato de os objetos serem filhos um do outro é o que causa o seu problema, eu acho.

Da mesma forma, o que torna seu AcceptedOffer complexo é que ele tem duas responsabilidades:indica ofertas incluídas em um plano, indica aceitação por parte de um cliente.Isso viola o Princípio da Responsabilidade Única.

Talvez seja necessário diferenciar entre uma Oferta que está sob um Plano e uma Oferta que é aceita pelos clientes.Então aqui está o que vou fazer:

  1. Crie um objeto de oferta separado que não tenha um estado, por exemplo, não tenha um cliente e não tenha um status - ele só tem um OfferId e o plano ao qual pertence como atributos.
  2. Modifique seu objeto Plan para ter uma coleção Offers (não precisa ter uma oferta aceita em seu contexto).
  3. Por fim, modifique seu objeto AcceptedOffer para que ele contenha uma Oferta, o Cliente e um Status.O cliente permanece o mesmo.

Eu acho que isso irá resolver suficientemente seus mapeamentos do NHibernate e problemas de salvamento de objetos.:)

Uma dica que pode (ou não) ser útil no NHibernate:você pode mapear seus objetos em relação às Views como se a View fosse uma tabela.Basta especificar o nome da visualização como um nome de tabela;contanto que todos os campos NOT NULL estejam incluídos na visualização e no mapeamento, ele funcionará bem.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top