我在尝试保存实体时遇到NonUniqueObjectException。我完全理解为什么会抛出异常,但我不确定如何正确解决问题。
这是我的架构:
以下是我对应的hbm.xml文件:
Video.hbm.xml:
<class name="Video" table="[Videos]" lazy="false" mutable="false">
<id name="Id" length="11" type="String">
<generator class="assigned"></generator>
</id>
<property name="Title" not-null="true" />
<property name="Duration" not-null="true" />
<property name="Author" not-null="true" />
</class>
PlaylistItem.hbm.xml:
<class name="PlaylistItem" table="[PlaylistItems]" lazy="false" >
<id name="Id" unsaved-value="00000000-0000-0000-0000-000000000000">
<generator class="guid" />
</id>
<property name="Title" not-null="true" />
<many-to-one name="Playlist" column="PlaylistId" />
<many-to-one name="NextItem" column="NextItemId" />
<many-to-one name="PreviousItem" column="PreviousItemId" />
<many-to-one name="Video" column="VideoId" not-null="true" cascade="save-update" />
</class>
Playlist.hbm.xml:
<class name="Playlist" table="[Playlists]" lazy="false" >
<id name="Id" unsaved-value="00000000-0000-0000-0000-000000000000">
<generator class="guid" />
</id>
<property name="Title" not-null="true" />
<bag name="Items" inverse="true" cascade="all-delete-orphan" >
<key column="PlaylistId" />
<one-to-many class="PlaylistItem" />
</bag>
<many-to-one name="Stream" column="StreamId" />
<many-to-one name="NextPlaylist" column="NextListId" />
<many-to-one name="PreviousPlaylist" column="PreviousListId" />
<many-to-one name="FirstItem" column="FirstItemId" />
</class>
请随意批评这些文件,因为我对NHibernate还是比较天真的,当然这些文件写得更好。
无论如何,以下场景会生成NonUniqueObjectException:
我想要的结果是,由于视频是不可变的,NHibernate应该只是愉快地使用已经在会话中的视频(或者默默地忽略NonUniqueObjectException,因为视频是不可变的。)
我知道对此有一个常见的解决方法是使用Session.Merge。我不明白如何将它应用于这种情况。每次需要播放列表保存时,迭代播放列表的所有子节点(以验证/合并视频对象)似乎是不切实际的。
我在这里遗漏了什么?建议赞赏!
编辑:以下是一个突出显示失败的测试用例:
/// <summary>
/// Create a new PlaylistItem with a Video whose ID is in the database.
/// No update should happen to the Video as it is immutable.
/// </summary>
[Test]
public void CreateItem_VideoAlreadyExists_ItemCreatedVideoNotUpdated()
{
// Save this Video before continuining so that it exists before adding the PlaylistItem.
string randomVideoId = Guid.NewGuid().ToString().Substring(0, 11);
var videoNotInDatabase = new Video(randomVideoId, "Video", 999, "Author");
VideoManager.Save(videoNotInDatabase);
// Change the title for videoInDatabase to check that cascade-update does not affect title. Videos are immutable.
const string videoTitle = "A video title";
var videoInDatabase = new Video(randomVideoId, videoTitle, 999, "Author");
// Create a new PlaylistItem and write it to the database.
string title = videoInDatabase.Title;
var playlistItem = new PlaylistItem(title, videoInDatabase);
Playlist.AddItem(playlistItem);
PlaylistManager.SavePlaylistItem(playlistItem);
// Remove entity from NHibernate cache to force DB query to ensure actually created.
NHibernateSessionManager.Instance.Clear();
// Ensure that the Video was NOT updated by comparing the new title to the old one.
Video videoFromDatabase = VideoDao.Get(randomVideoId);
Assert.AreNotEqual(videoFromDatabase.Title, videoTitle);
// Ensure that the PlaylistItem was created.
PlaylistItem itemFromDatabase = PlaylistItemDao.Get(playlistItem.Id);
Assert.NotNull(itemFromDatabase);
// Pointers should be self-referential with only one item in the Playlist.
Assert.AreEqual(itemFromDatabase.NextItem, itemFromDatabase);
Assert.AreEqual(itemFromDatabase.PreviousItem, itemFromDatabase);
}
测试用例失败:
PlaylistManager.SavePlaylistItem(playlistItem);
看起来像:
public void SavePlaylistItem(PlaylistItem playlistItem)
{
try
{
NHibernateSessionManager.Instance.BeginTransaction();
playlistItem.ValidateAndThrow();
playlistItem.Video.ValidateAndThrow();
PlaylistItemDao.Save(playlistItem);
NHibernateSessionManager.Instance.CommitTransaction();
}
catch (Exception exception)
{
Logger.Error(exception);
NHibernateSessionManager.Instance.RollbackTransaction();
throw;
}
}
错误消息:
“已经有一个具有相同标识符值的不同对象 与会话相关联:f859e2a5-2f,实体: Streamus.Domain.Video“
这是有道理的。我试图保存第二个视频对象,其ID与缓存中已存在的ID相同。我想向NHibernate传达一个事实,即由于视频是缓存且不可变的,因此不需要任何操作(无法更新/无法插入)。这可能吗?