警告 - 私は NHibernate を初めて使用します。この質問が単純に思えることはわかっています。単純な答えがあると確信していますが、私はこれについてしばらくの間、車輪を回転させてきました。構造的に変更できないレガシーデータベースを扱っています。顧客が承認した支払いプランを一覧表示する詳細テーブルがあります。各支払いプランには、プランの条件などを取得するための参照テーブルにリンクする ID があります。私のオブジェクト モデルには、AcceptedPlan クラスと Plan クラスがあります。当初、詳細テーブルから ref テーブルへの多対 1 の関係を使用して、NHibernate でこの関係をモデル化しました。また、Plan クラスから AcceptedPlan クラスへの反対方向の 1 対多の関係も作成しました。単にデータを読んでいる間はこれで問題ありませんでした。計画の詳細を読み取るために、AcceptedPlan クラスのプロパティである Plan オブジェクトに移動できます。詳細テーブルに新しい行を挿入しなければならないときに問題が発生しました。私の読書によると、新しい子オブジェクトを作成する唯一の方法は、それを親オブジェクトに追加してからセッションを保存することです。しかし、新しい詳細レコードを作成するたびに新しい親 Plan オブジェクトを作成する必要はありません。これは不要なオーバーヘッドのようです。私がこれについて間違った方法で行っているかどうかは誰にも分かりますか? 新しい詳細レコードを作成するたびに、新しい親 Plan オブジェクトを作成する必要がありません。これは不要なオーバーヘッドのようです。私がこれについて間違った方法で行っているかどうかは誰にも分かりますか? 新しい詳細レコードを作成するたびに、新しい親 Plan オブジェクトを作成する必要がありません。これは不要なオーバーヘッドのようです。私がこれについて間違った方法で行っているかどうかは誰にも分かりますか?
6 に答える
論理的な親を含む子オブジェクトを持たないようにします。そうすると、非常に面倒で非常に再帰的になる可能性があります。そのようなことを行う前に、ドメイン モデルをどのように使用するつもりなのかを調べてみたいと思います。テーブル内に ID 参照を簡単に保持し、マップされていないままにしておくことができます。
正しい方向にあなたを微調整するかもしれない2つのマッピング例を次に示します.テーブル名などをアドリブする必要がありましたが、おそらく役立つでしょう. StatusId を列挙型にマッピングすることもお勧めします。
バッグが詳細テーブルをコレクションに効果的にマップする方法に注意してください。
<?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>
私が書いている間、あなたのデータベース図を見ませんでした。
<?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>
おそらくうまくいくはずです (私はコレクションのマッピングの例を作成しただけなので、他のプロパティを追加する必要があります)。
これをモデル化するために私が取るアプローチは次のとおりです。
Customerオブジェクトには、顧客が受け入れたプランを表すICollection<PaymentPlan>PaymentPlansが含まれています。
顧客へのPaymentPlanは、詳細テーブルを使用してどの顧客IDがどのPaymentPlanにマップされるかを確立するバッグを使用してマップされます。カスケードall-delete-orphanを使用して、顧客が削除された場合、詳細からのエントリと顧客が所有していたPaymentPlansの両方が削除されます。
PaymentPlanオブジェクトには、支払いプランの条件を表すPlanTermsオブジェクトが含まれています。
PlanTermsは、関連するPlanTermsオブジェクトへの参照をPaymentPlanに挿入するだけの、多対1のマッピングカスケード保存更新を使用してPaymentPlanにマッピングされます。
このモデルを使用すると、PlanTermsを独立して作成できます。次に、新しいPaymentPlanを顧客に追加するときに、関連するPlanTermsオブジェクトを渡す新しいPaymentPlanオブジェクトを作成し、それを関連する顧客のコレクションに追加します。最後に、顧客を保存し、nhibernateに保存操作をカスケードさせます。
最終的には、Customerオブジェクト、PaymentPlanオブジェクト、およびPlanTermsオブジェクトが作成され、Customer(customerテーブル)がPaymentPlans(詳細テーブル)のインスタンスを所有します。これらはすべて特定のPlanTerms(プランテーブル)に準拠しています。
必要に応じて、マッピング構文のより具体的な例をいくつか示しますが、独自のモデルでそれを実行するのがおそらく最善であり、特定の例を提供するためのデータベーステーブルに関する十分な情報がありません。
これがおそらく私のNHibernateの経験が限られているためかどうかはわかりませんが、Detailsテーブルに直接マップされるDetailsのプロパティのみを持つBaseDetailクラスを作成できますか。
次に、追加の親プランオブジェクトを持つBaseDetailクラスを継承する2番目のクラスを作成します。これにより、Detail行を作成してPlanIdを割り当てるだけで、完全なDetailを設定する必要がある場合に、BaseDetailクラスを作成できます。親プランオブジェクトを使用して記録すると、継承されたDetailクラスを使用できます。
それが非常に理にかなっているのかどうかはわかりませんが、私に知らせてください。さらに明確にします。
ここでの問題は、AcceptedOffer オブジェクトに Plan オブジェクトが含まれており、Plan オブジェクトに AcceptedOffer オブジェクトを含む AcceptedOffers コレクションが含まれているように見えることだと思います。顧客と同じこと。オブジェクトがお互いの子であるという事実が問題の原因だと思います。
同様に、AcceptedOffer を複雑にしているのは、プランに含まれるオファーを示すことと、顧客による受け入れを示すことの 2 つの責任があることです。それは単一責任の原則に違反しています。
プランの下にあるオファーと、顧客に受け入れられたオファーを区別する必要がある場合があります。だからここに私がやろうとしていることがあります:
- 状態を持たない別のオファー オブジェクトを作成します。たとえば、顧客もステータスもありません。属性として、OfferId とそれが属するプランのみを持ちます。
- Plan オブジェクトを変更して、Offers コレクションを含めます (そのコンテキストでオファーを受け入れる必要はありません)。
- 最後に、Offer、Customer、および Status が含まれるように AcceptedOffer オブジェクトを変更します。お客様は相変わらずです。
これにより、NHibernate のマッピングとオブジェクトの保存の問題が十分に解決されると思います。:)
NHibernate で役に立つ (または役に立たない) ヒント: ビューがテーブルであるかのように、ビューに対してオブジェクトをマップできます。ビュー名をテーブル名として指定するだけです。すべての NOT NULL フィールドがビューとマッピングに含まれている限り、正常に機能します。