客户端尝试通过父对象两次保存相同的项后抛出NonUniqueObjectException

时间:2013-07-01 01:25:10

标签: c# nhibernate

我在尝试保存实体时遇到NonUniqueObjectException。我完全理解为什么会抛出异常,但我不确定如何正确解决问题。

这是我的架构:

  • 视频:不可变的实体。该ID由API客户端提供(不是由NHibernate生成)。视频不知道其父实体。
  • PlaylistItem:视频实体的父级。这是多对一关系,因为许多PlaylistItem实体可能引用相同的视频实体。
  • 播放列表: PlaylistItem实体的父级。这是一种倒置的,一对多的关系。

以下是我对应的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:

  • 客户想要创建一个新的PlaylistItem。 PlaylistItem的视频ID为“1234567890A”
  • 服务器接收来自客户端的请求。 PlaylistItem和Video适当地保存到数据库中。服务器响应成功。
  • 客户想要创建一个新的PlaylistItem。 PlaylistItem的视频是相同的视频。因此,视频的ID为“1234567890A。”
  • 服务器接收来自客户端的请求。服务器尝试使用相同的视频创建新的PlaylistItem。服务器意识到它创建的Video对象具有与Session中相同的id。服务器抛出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传达一个事实,即由于视频是缓存且不可变的,因此不需要任何操作(无法更新/无法插入)。这可能吗?

0 个答案:

没有答案