Meilleur moyen de modéliser des relations plusieurs-à-un dans NHibernate lorsqu'il s'agit d'une base de données héritée ?

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

  •  08-06-2019
  •  | 
  •  

Question

Attention – je suis très nouveau sur NHibernate.Je sais que cette question semble simple – et je suis sûr qu’il existe une réponse simple, mais je tourne en rond depuis un certain temps sur celle-ci.J'ai affaire à une base de données héritée qui ne peut vraiment pas être modifiée structurellement.J'ai un tableau détaillé qui répertorie les plans de paiement acceptés par un client.Chaque plan de paiement possède un identifiant qui renvoie à une table de référence pour obtenir les termes, conditions, etc.Dans mon modèle objet, j'ai une classe AcceptedPlan et une classe Plan.À l’origine, j’utilisais une relation plusieurs-à-un depuis la table de détail jusqu’à la table de référence pour modéliser cette relation dans NHibernate.J'ai également créé une relation un-à-plusieurs allant dans la direction opposée de la classe Plan à la classe AcceptedPlan.C'était bien pendant que je lisais simplement des données.Je pouvais accéder à mon objet Plan, qui était une propriété de ma classe AcceptedPlan pour lire les détails du plan.Mon problème est survenu lorsque j'ai dû commencer à insérer de nouvelles lignes dans la table des détails.D'après ma lecture, il semble que le seul moyen de créer un nouvel objet enfant soit de l'ajouter à l'objet parent, puis d'enregistrer la session.Mais je ne veux pas avoir à créer un nouvel objet Plan parent à chaque fois que je souhaite créer un nouvel enregistrement détaillé.Cela semble être une surcharge inutile.Est-ce que quelqu'un sait si je m'y prends dans le mauvais sens ?

Était-ce utile?

La solution

J'éviterais d'avoir un objet enfant contenant son parent logique, cela peut devenir très compliqué et très récursif assez rapidement lorsque vous faites cela.J'examinerais comment vous comptez utiliser le modèle de domaine avant de faire ce genre de chose.Vous pouvez facilement conserver les références d'ID dans les tableaux et les laisser non mappées.

Voici deux exemples de mappages qui pourraient vous pousser dans la bonne direction. J'ai dû modifier les noms de tables, etc., mais cela pourrait éventuellement aider.Je suggérerais probablement également de mapper le StatusId à une énumération.

Faites attention à la façon dont le sac mappe efficacement le tableau des détails dans une collection.

<?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>

Autres conseils

Je n'ai pas vu votre diagramme de base de données pendant que j'écrivais.

<?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>

Cela devrait probablement faire l'affaire (je n'ai fait que des exemples de mappages pour les collections, vous devrez ajouter d'autres propriétés).

L'approche que j'adopterais pour modéliser cela est la suivante :

L'objet client contient un ICollection <PaymentPlan> PaymentPlans qui représente les plans acceptés par le client.

Le plan de paiement du client serait mappé à l'aide d'un sac qui utilise le tableau des détails pour établir quel identifiant client est mappé à quels plans de paiement.En utilisant la cascade all-delete-orphan, si le client était supprimé, les entrées des détails et les plans de paiement appartenant au client seraient supprimés.

L'objet PaymentPlan contient un objet PlanTerms qui représente les conditions du plan de paiement.

Les PlanTerms seraient mappés à un PaymentPlan à l'aide d'une mise à jour de sauvegarde en cascade de mappage plusieurs-à-un qui insérerait simplement une référence à l'objet PlanTerms pertinent dans le PaymentPlan.

En utilisant ce modèle, vous pouvez créer PlanTerms indépendamment, puis lorsque vous ajoutez un nouveau PaymentPlan à un client, vous créez un nouvel objet PaymentPlan en transmettant l'objet PlanTerms approprié, puis l'ajoutez à la collection sur le client concerné.Enfin, vous enregistreriez le client et laisseriez nhibernate effectuer l'opération de sauvegarde en cascade.

Vous vous retrouveriez avec un objet Customer, un objet PaymentPlan et un objet PlanTerms avec le Customer (table client) possédant des instances de PaymentPlans (la table des détails) qui adhèrent toutes à des PlanTerms spécifiques (la table du plan).

J'ai quelques exemples plus concrets de la syntaxe de mappage si nécessaire, mais il est probablement préférable de la travailler avec votre propre modèle et je n'ai pas suffisamment d'informations sur les tables de la base de données pour fournir des exemples spécifiques.

Je ne sais pas si cela est peut-être dû au fait que mon expérience NHibernate est limitée, mais pourriez-vous créer une classe BaseDetail qui possède uniquement les propriétés des détails car elles sont directement mappées à la table Détail.

Créez ensuite une deuxième classe qui hérite de la classe BaseDetail qui possède l'objet Plan parent supplémentaire afin que vous puissiez créer une classe BaseDetail lorsque vous souhaitez simplement créer une ligne de détail et lui attribuer le PlanId, mais si vous devez remplir un détail complet. avec l'objet Plan parent, vous pouvez utiliser la classe Detail héritée.

Je ne sais pas si cela a beaucoup de sens, mais faites-le-moi savoir et je clarifierai davantage.

Je pense que le problème que vous avez ici est que votre objet AcceptedOffer contient un objet Plan, puis votre objet Plan semble contenir une collection AcceptedOffers qui contient des objets AcceptedOffer.Même chose avec les clients.Le fait que les objets soient enfants les uns des autres est ce qui cause votre problème, je pense.

De même, ce qui rend votre AcceptedOffer complexe, c'est qu'elle a deux responsabilités :il indique les offres incluses dans un plan, il indique l'acceptation par un client.Cela viole le principe de responsabilité unique.

Vous devrez peut-être faire la différence entre une offre faisant partie d'un plan et une offre acceptée par les clients.Alors voici ce que je vais faire :

  1. Créez un objet Offre distinct qui n'a pas d'état, par exemple, il n'a pas de client et il n'a pas de statut - il a seulement un OfferId et le plan auquel il appartient comme attributs.
  2. Modifiez votre objet Plan pour avoir une collection Offers (il n'est pas nécessaire qu'il ait une offre acceptée dans son contexte).
  3. Enfin, modifiez votre objet AcceptedOffer pour qu'il contienne une offre, le client et un statut.Le client reste le même.

Je pense que cela démêlera suffisamment vos mappages NHibernate et vos problèmes de sauvegarde d'objets.:)

Un conseil qui peut (ou non) être utile dans NHibernate :vous pouvez mapper vos objets avec des vues comme si la vue était une table.Spécifiez simplement le nom de la vue comme nom de table ;tant que tous les champs NOT NULL sont inclus dans la vue et le mappage, cela fonctionnera correctement.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top