NHibernate:多対多リレーションは最初に子オブジェクトの保存に失敗します(“ nullを挿入できません”または:“一時オブジェクト”)
-
10-07-2019 - |
質問
FreightDateTimeクラスを集約する委託クラスがあります。同時に、FreightDateTimeクラスもGoodsItemクラスによって集計されます。同じ方法で、FreightDateTimeは、私が今は省略した他の多くのクラスに関連付けられています。
ConsignmentId外部キー、GoodsItemId外部キーなどを含むデータベーステーブルFreightDateTimeを回避するために、関連付けは多対多であると判断しました。この方法では、NHibernateは各関係(ConsigmentFreightDateTimes、GoodsItemFreightDateTimes)の代わりに関連テーブルを生成しますが、これはより意味があります。
つまり、マッピングファイルでは、関連付けはたとえばこのような:
<bag name="DateTimes" table="FreightDateTimes" lazy="false" cascade="all">
<key column="ConsignmentId"/>
<many-to-many class="Logistics.FreightDateTime, Logistics" column="DateTimeId" />
</bag>
カスケードを「すべて」に設定する収量:
System.Data.SqlClient.SqlException: Cannot insert the value NULL into column 'DateTimeId', table 'LogiGate.dbo.FreightDateTimes'; column does not allow nulls. INSERT fails.
カスケードを「なし」に設定収量:
NHibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: Logistics.FreightDateTime
どちらの場合も、子のFreightDateTimeインスタンスは保存されていませんが、NHibernateが委託インスタンスを保存しようとしていることを意味します。前者の場合、外部キーは依然として「null」であるため、結果のテーブルに挿入できません。後者の場合、NHibernateはインスタンスがまだ保存されていないことを認識しているため、例外をスローします。
そのため、NHibernateに明示的に指示せずに最初にすべての子インスタンスを保存する方法を質問します。 DateTimeId列でnullを許可するとうまくいくと思いますが、それは望ましくも不可能でもないと思います。
解決
反対側の関連付けもマッピングしてみますが、inverse =&quot; true&quot;その側の属性。したがって、FreightDateTimeマッピングファイルでバッグを作成して、委託と多対多の関連付けをマッピングします。
また、ここで同様の質問に答えました: NHibernateで多対多の関係を定義して、削除を許可するが重複レコードを回避する正しい方法は何ですか
質問と私の答えを読むことで、多対多の関係で何が起こっているのかを理解し、解決策のヒントを得ることができます。最終的な推奨事項は、最後の手段です。
上記の質問の答えは、他の人の別の問題をどのように経験しているのかを知ることです。
解決策は、アソシエーションテーブルを明示的にマップすることです。 テーブルがPerson、Noteであり、関連付けテーブル(Xテーブル)がPersonNoteである場合 マッピングは次のようになります。
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="..."
namespace="...">
<class name="Person" table="Person" lazy="true">
<id name="PersonId">
<generator class="native" />
</id>
<property name="FirstName" />
.....
<bag name="PersonNotes" generic="true" inverse="true" lazy="true" cascade="none">
<key column="PersonId"/>
<one-to-many class="PersonNote"/>
</bag>
<bag name="Notes" table="PersonNote" cascade="save-update">
<key column="PersonId"></key>
<many-to-many class="Note" column="NoteId"></many-to-many>
</bag>
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="..."
namespace="...">
<class name="Note" table="Note" lazy="true">
<id name="NoteId" unsaved-value="0">
<generator class="native" />
</id>
<property name="Title" />
....
<bag name="PersonNotes" inverse="true" lazy="true" cascade="all-delete-orphan">
<key column="NoteId"/>
<one-to-many class="PersonNote"/>
</bag>
<bag name="People" table="PersonNote" inverse="true" cascade="save-update" generic="true">
<key column="NoteId"></key>
<many-to-many class="Person" column="PersonId"></many-to-many>
</bag>
</class>
</hibernate-mapping>
上記のとおり、次のことができます。
- 個人を削除し、メモを削除せずに関連付けテーブルのエントリのみを削除します
- メモを削除し、Personエンティティを削除せずに関連付けテーブルのエントリのみを削除します
- Person.Notesコレクションにデータを入力し、Personを保存して、Person側のみからカスケードで保存します。
- Note.Peopleではinverse = trueが必要であるため、こちら側からカスケード保存を行う方法はありません。 Note.Peopleコレクションを作成してからNoteオブジェクトを保存すると、Noteテーブルへの挿入とPersonテーブルへの挿入が行われますが、関連付けテーブルへの挿入は行われません。これがNHibernateの動作方法だと思いますが、それを回避する方法をまだ見つけていません。
- NoteエンティティのPersonNotesコレクションに新しいアイテムを追加することにより、関連付けテーブルにエントリを明示的にカスケードすることができます。
上記はすべて単体テストでテストされています。 上記を機能させるには、PersonNoteクラスマッピングファイルとクラスを作成する必要があります。
別のクラスにメモを付ける必要がある場合、Organizationと言うと、OrgnanisationNoteと呼ばれる別の関連付けテーブルをスキーマに追加するだけで、上記と同じことをOrganizationマッピングファイルとNoteマッピングファイルに行います。
これは、彼/彼女の多対多の関連付けを完全に制御したい人にとって、これが最後の選択肢であるべきだということをもう一度言わなければなりません。