NHibernate не сохраняет отношения «многие ко многим»
-
03-07-2019 - |
Вопрос
В настоящее время я использую NHibernate в качестве уровня доступа к данным, используя Fluent NHibernate для создания файлов сопоставления.У меня есть два класса, TripItem и TripItemAttributeValue, между которыми существует связь «многие ко многим».
Отображение выглядит следующим образом:
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>
и
<?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
У меня была та же проблема, и я потратил на это почти два дня.У меня были отношения многие-ко-многим, и таблица ссылок тоже не обновлялась.Я новичок в 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, но вам нужно установить опцию Cascade на сумке (TripItems
).По-прежнему, У Айенде есть полезная статья о вариантах каскадирования..
Из быстрого 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 */
У меня также была та же проблема - данные соединения для многих-ко-многим не сохранялись.Я скопировал сопоставление из другого отношения «многие к одному» (изменил его для отношения «многие ко многим»), но сохранил атрибут inverse="true".Когда я удалил этот атрибут, проблема была решена.
Дэвид Кемп прав:вы хотите добавить каскад в свою сумку.
Я всегда вручную редактировал (и создавал вручную) файлы сопоставления, поэтому мое естественное желание — поместить их туда.Вы можете сделать это следующим образом:
<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>
Я обнаружил, что сохранение «чистоты» моих классов и сохранение всего, что связано с nHibernate, в файле .hbm.xml делает мое решение более чистым.Таким образом, если я хочу использовать новое программное обеспечение 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'.Если вы прочитаете отрывок ниже, вы заметите, что значение inverse должно быть установлено в true только для одной из сумок:
Обратите внимание на использование inverse="true".Опять же, этот параметр указывает NHibernate игнорировать изменения, внесенные в коллекцию категорий, и использовать другой конец ассоциации — коллекцию элементов — в качестве представления, которое должно быть синхронизировано с базой данных.
Боюсь, Cascade.All() на самом деле не является решением моей проблемы — это была одна из вещей, которые я пробовал.Проблема не в том, что элементы, добавленные в коллекцию, не сохраняются — они уже находятся в базе данных на момент добавления в коллекцию.Просто записи в таблице ссылок не создаются.Кроме того, я думаю, что Cascade.All() также приведет к удалению дочерних элементов, что нежелательно в моем сценарии.Я пробовал использовать Cascade.SaveUpdate(), но, как я уже говорил, это решает то, что на самом деле не является моей проблемой :-)
Однако на всякий случай я попробую это решение еще раз и сообщу вам результат.
Что касается сохранения чистоты классов, то это на 100% справедливо для Fluent NHibernate.Сопоставления классов, которые вы создаете, представляют собой файлы кода C#, которые идут рядом с вашими классами сущностей, почти так же, как это делают файлы .hbm.xml.
Я тоже боролся с этим, и у моих проблем возникла совершенно другая причина.В моем примере, если бы у меня был объект без каких-либо связей «многие ко многим», я мог бы просто вызвать saveOrUpdate, и все было бы хорошо.Но если бы у меня были отношения «многие ко многим», мне нужно было убедиться, что мой вызов saveOrUpdate находился внутри BeginTransaction и CommitTransaction.Я новичок в свободном использовании Nhibernate, поэтому прошу прощения, если это очевидно.Но это было не для меня.