NHibernateが多対多の関係を維持しない
-
03-07-2019 - |
質問
現在、NHibernateをデータアクセスレイヤーとして使用し、Fluent NHibernateを使用してマッピングファイルを作成しています。 TripItemとTripItemAttributeValueの2つのクラスがあり、それらの間には多対多の関係があります。
マッピングは次のとおりです。
public class TripItemMap : ClassMap<TripItem2>
{
public TripItemMap()
{
WithTable("TripItemsInt");
NotLazyLoaded();
Id(x => x.ID).GeneratedBy.Identity().WithUnsavedValue(0);
Map(x => x.CreateDate, "CreatedOn").CanNotBeNull();
Map(x => x.ModifyDate, "LastModified").CanNotBeNull();
/* snip */
HasManyToMany<TripItemAttributeValue>(x => x.Attributes).AsBag()
.WithTableName("TripItems_TripItemAttributeValues_Link")
.WithParentKeyColumn("TripItemId")
.WithChildKeyColumn("TripItemAttributeValueId")
.LazyLoad();
}
}
public class TripItemAttributeValueMap : ClassMap<TripItemAttributeValue>
{
public TripItemAttributeValueMap()
{
WithTable("TripItemAttributeValues");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Name).CanNotBeNull();
HasManyToMany<TripItem2>(x => x.TripItems).AsBag()
.WithTableName("TripItems_TripItemAttributeValues_Link")
.WithParentKeyColumn("TripItemAttributeValueId")
.WithChildKeyColumn("TripItemId")
.LazyLoad();
}
}
アプリケーションのある時点で、データベースから既存の属性を取得し、それらをtripItem.Attributesに追加してから、tripItemオブジェクトを保存します。最終的に、TripItems_TripItemAttributeValues_Linkは新しいレコードを取得しないため、リレーションが保持されなくなります。
それが役立つ場合、これらはこれらのクラスに対してFluent NHibernateによって生成されたマッピングファイルです。
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-lazy="true" assembly="ETP.Core" namespace="ETP.Core.Domain">
<class name="TripItem2" table="TripItemsInt" xmlns="urn:nhibernate-mapping-2.2" lazy="false">
<id name="ID" column="ID" type="Int32" unsaved-value="0">
<generator class="identity" />
</id>
<property name="CreateDate" column="CreatedOn" type="DateTime" not-null="true">
<column name="CreatedOn" />
</property>
<property name="ModifyDate" column="LastModified" type="DateTime" not-null="true">
<column name="LastModified" />
</property>
<bag name="Attributes" lazy="true" table="TripItems_TripItemAttributeValues_Link">
<key column="TripItemId" />
<many-to-many column="TripItemAttributeValueId" class="ETP.Core.Domain.TripItemAttributeValue, ETP.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</bag>
</class>
</hibernate-mapping>
and
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-lazy="true" assembly="ETP.Core" namespace="ETP.Core.Domain">
<class name="TripItemAttributeValue" table="TripItemAttributeValues" xmlns="urn:nhibernate-mapping-2.2">
<id name="Id" column="Id" type="Int32">
<generator class="identity" />
</id>
<property name="Name" column="Name" length="100" type="String" not-null="true">
<column name="Name" />
</property>
<bag name="TripItems" lazy="true" table="TripItems_TripItemAttributeValues_Link">
<key column="TripItemAttributeValueId" />
<many-to-many column="TripItemId" class="ETP.Core.Domain.TripItem2, ETP.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</bag>
</class>
</hibernate-mapping>
ここで何が間違っているのですか?
解決
@efdee
私は同じ問題を抱えていて、これにほぼ2日間を費やしました。多対多の関係があり、リンクテーブルも更新されていませんでした。私はNHibernateを初めて使います。それを学ぼうとしているので、私が言うすべてを一粒の塩で取ります。
まあ、それはFluent NHibernateでもマッピングでもないことがわかりましたが、私はNHibernateが多対多でどのように機能するかを理解していません。多対多の関係では、両方のエンティティのコレクションにデータが入力されていない場合、NHibernateはリンクテーブルにデータを永続化しません。
このエンティティが多対多の関係にあるとしましょう:
partial class Contact
{
public string ContactName {get; set;}
public IList Locations {get; set;}
}
partial class Location
{
public string LocationName {get; set;}
public string LocationAddress {get;set;}
public IList Contacts {get;set;}
}
Contact.Locationsに場所を追加するとき、location.Contacts内にも連絡先が存在することを確認する必要があります。
場所を追加するために、Contactクラス内にこのメソッドがあります。
public void AddLocation(Location location)
{
if (!location.Contacts.Contains(this))
{
location.Contacts.Add(this);
}
Locations.Add(location);
}
これで問題は解決したようですが、NHibernateを取り上げて学習していると言ったように、もっと良い方法があるかもしれません。より良い解決策があれば、投稿してください。
これは、両方のコレクションを確認するように指示した投稿です。 http://www.coderanch.com/t/217138/Object-Relational-Mapping/link-table-of-ManyToMany-annotation
他のヒント
Session.Flush()を呼び出すか、トランザクションを使用します。
Fluent NHibernateでどのように実行するのかわかりませんが、バッグ( TripItems
)のカスケードオプションを設定する必要があります。いつものように、 Ayendeには、カスケードオプションに関する有用な投稿があります。
簡単なGoogleから、試してみることをお勧めします:
HasManyToMany<TripItem2>(x => x.TripItems).AsBag()
.WithTableName("TripItems_TripItemAttributeValues_Link")
.WithParentKeyColumn("TripItemAttributeValueId")
.WithChildKeyColumn("TripItemId")
.LazyLoad()
/*-->*/ .Cascade.All(); /*<-- this is the bit that should make it work */
私も同じ問題を抱えていました-多対多の結合データは保持されませんでした。別の多対1の関係からマッピングをコピーしました(多対多のrelに変更しました)が、inverse =&quot; true&quot;を保持しました。属性。この属性を削除すると、問題は解決しました。
David Kempが正しい:バッグにカスケードを追加したい。
私は常にマッピングファイルを手動で編集(および手動で作成)しているので、私の自然な傾向はそこに置くことです。次のようにできます:
<bag name="TripItems" lazy="true" table="TripItems_TripItemAttributeValues_Link" cascade="all">
<key column="TripItemAttributeValueId" />
<many-to-many column="TripItemId" class="ETP.Core.Domain.TripItem2, ETP.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</bag>
クラスを「純粋」に保ち、.hbm.xmlファイルでnHibernateに関連するすべてのものを保持することで、ソリューションがよりきれいに保たれることがわかりました。そのようにして、使用したい新しいORMソフトウェアがある場合、マッピングファイルを置き換えるだけで、クラスを書き換えません。ユニットテストを使用してクラスをテストし、xmlにテスト容易性を与えますが、Fluent NHibernateのメソッドのようなものを実行します。
まったく同じ問題を抱えていますが、NHibernate.JetDriverを使用しています。成功せずに推奨された回答を使用してみました。 NHibernate.JetDriverに多対多に関して制限があるかどうかは誰にもわかりますか?
誰かがたまたまそれらのレビューに興味を持っている場合の私のhbmファイルは次のとおりです。
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="Ace.Docs.Core.Domain" assembly="Ace.Docs.Core">
<class name="Ace.Docs.Core.Domain.Address, Ace.Docs.Core" table="Addresses" lazy="true">
<id name="Id" column="ID">
<generator class="identity" />
</id>
<property name="Address1" column="Address1" />
<property name="Address2" column="Address2" />
<property name="City" column="City" />
<property name="EmailAddress" column="EmailAddress" />
<property name="Phone1" column="Phone1" />
<property name="Phone2" column="Phone2" />
<property name="PostalCode" column="PostalCode" />
<property name="StateOrProvince" column="StateOrProvince" />
<many-to-one name="AddressTypeMember" column="AddressTypeID" class="AddressType" />
<bag name="HasPersonalInfo" table="Link_PersonalInfo_Addresses" lazy="true" cascade="save-update" inverse="true" >
<key column="AddressID"></key>
<many-to-many column="PersonalInfoID" class="PersonalInfo" />
</bag>
</class>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="Ace.Docs.Core.Domain" assembly="Ace.Docs.Core">
<class name="Ace.Docs.Core.Domain.PersonalInfo, Ace.Docs.Core" table="PersonalInfo" lazy="true">
<id name="Id" column="ID">
<generator class="identity" />
</id>
<property name="Prefix" column="Prefix" />
<property name="FirstName" column="FirstName" />
<property name="MiddleName" column="MiddleName" />
<property name="LastName" column="LastName" />
<property name="SIN" column="SIN" />
<property name="Birthdate" column="Birthdate" />
<property name="Note" column="Notes" />
<bag name="HasAddress" table="Link_PersonalInfo_Addresses" lazy="true" cascade="save-update" inverse="true" >
<key column="PersonalInfoID"></key>
<many-to-many column="AddressID" class="Address" />
</bag>
</class>
私はそれを手に入れ、これが他の誰かに役立つことを願っています。問題は、両方のバッグでinverse = 'true'があったことです。以下の抜粋を読むと、バッグの1つだけで逆にtrueに設定する必要があることに注意してください。
inverse =&quot; true&quot;の使用に注意してください。繰り返しますが、この設定はカテゴリコレクションへの変更を無視し、関連付けと反対側のアイテムコレクションをデータベースと同期する表現として使用するようNHibernateに指示します。
Cascade.All()は私の問題の解決策ではないのではないかと心配しています。私が試したものの1つでした。問題は、コレクションに追加されたアイテムが保存されないことではなく、コレクションに追加された時点で既にデータベースに存在していることです。リンクテーブルのエントリが作成されていないだけです。さらに、Cascade.All()によって子アイテムも削除されると思いますが、これは私のシナリオでは望ましくない動作です。 Cascade.SaveUpdate()を使用してみましたが、指摘したように、これは本当に私の問題ではない何かを解決します:-)
ただし、念のため、このソリューションを再試行し、結果をお知らせします。
クラスを純粋に保つという点では、これはFluent NHibernateの場合の100%です。作成するクラスマッピングは、.hbm.xmlファイルとほぼ同じように、エンティティクラスと並んでいるC#コードファイルです。
私もこれに苦労していましたが、トラブルのまったく別の理由で来ました。私の例では、多対多のリレーションシップを持たないオブジェクトがある場合、saveOrUpdateを呼び出すだけで十分です。ただし、多対多の関係がある場合は、saveOrUpdate呼び出しがBeginTransactionおよびCommitTransaction内にあることを確認する必要がありました。流なNhibernateを初めて使用するので、これが明らかな場合は謝罪します。しかし、私にはそうではありませんでした。