如何避免NHibernate.NonUniqueObjectException

时间:2009-11-11 05:13:56

标签: nhibernate

我正在编写一个博客引擎作为学习练习。我知道那里有很多博客引擎,但请耐心等待......

我有一个BlogPost实体,它有一个属性标签,它是与之关联的标签的IList。 BlogPost.SetTags(string)方法拆分字符串,使用指定的标记名称创建新的Tag对象,并将它们添加到列表中。 BlogPost.AddTag(字符串tagName)也是如此。

我想要发生的是,当我调用BlogPost.AddTag(“foo”)时,名为“foo”的标记实体已经存在并且在数据库中持久化,nHibernate只是意识到这一点,并将帖子连接起来现有的标签。

在BlogRepository.Save()方法中,我检查标签列表中的每个标签是否已经存在。如果没有,我通过调用TagRepository.Save(tag);

来保存它

问题是,在下面的示例代码中,我收到一个错误“NHibernate.NonUniqueObjectException:具有相同标识符值的另一个对象已经与会话关联:标记1,实体:CMS.Core.Model .Tag“当我尝试使用现有标记持久保存BlogPost对象时。当我坚持只使用新标签的BlogPost对象时,它们就被创建了,一切都很好。

注意我还使用TagName作为bp_Tags表的数据库中的主键。当表只存储唯一的Tag名称时,使用整数或GUID PK似乎是多余的。

我的nHibernate配置如下:

  <class name="CMS.Core.Model.Tag,CMS.Core" table="bp_Tags">
    <id column="TagName" name="TagName" type="String" unsaved-value="">
      <generator class="assigned" />
    </id>
  </class>

  <class name="CMS.Core.Model.BlogPost,CMS.Core" table="bp_Content">
    <id name="Id" column="Id" type="Int32" unsaved-value="0">
      <generator class="native"></generator>
    </id>
    <property name="SubmittedBy" column="SubmittedBy" type="string" length="256" not-null="true" />
    <property name="SubmittedDate" column="SubmittedDate" type="datetime" not-null="true" />
    <property name="PublishDate" column="PublishDate" type="datetime" not-null="true" />
    ...    
    <bag name="_tagsList" table="bp_Tags_Mappings" lazy="false" cascade="all">
      <key column="Target_Id" />
      <many-to-many class="CMS.Core.Model.Tag,CMS.Core" column="TagName" lazy="false" />
    </bag>

NHibernate.NonUniqueObjectException:具有相同标识符值的另一个对象已与会话关联:标记1,实体:Bariliant.CMS.Core.Model.Tag

    BlogPost post, post2;

    using (UnitOfWork.Start())
    {  
        post = BlogPostFactory.CreateBlogPost("test post", "test body");
        post.Publish();
        BlogRepository.Save(post);
        UnitOfWork.Current.Flush();

        post.SetTags("tag 1, tag 2");
        BlogRepository.Save(post);
        UnitOfWork.Current.Flush();
    }

    using (UnitOfWork.Start())
    {
        post2 = BlogPostFactory.CreateBlogPost("test post2", "test body");
        post2.Publish();
        BlogRepository.Save(post2);
        UnitOfWork.Current.Flush();

        post2.AddTag("tag 1");
        BlogRepository.Save(post2);  // throws

...

对我做错了什么以及如何解决它的想法?

2 个答案:

答案 0 :(得分:8)

由于TagName是ID,因此您正在运行NHibernate的身份映射。它的身份映射已经知道具有相同ID的对象,因此它会为您提供该异常。

您可能想要尝试查看该标记是否已存在于该会话中的内容,如果存在,则将该现有标记与第二篇文章相关联。

Psuedo-code示例:

var tag = session.Get<Tag>("Tag 1");

if (tag != null)
{
   post.AddTag(tag);
}
else
{
   post.AddTag(new Tag("Tag 1"));
}

此博客文章将为您提供详细说明: NHibernate - Cross session operations

答案 1 :(得分:4)

你这样做的方式不是我的方式,但这里是如何解决你的问题。通常在面向对象编程中,以下两个对象不相等:

var object1 = new Tag("hello");
var object2 = new Tag("hello");

var areSame = (object1 == object2); // false

你已经制作了2个具有相同状态的单独对象,但是它们是两个不同的对象,所以如果你将它们相等,那么它们就不一样了。显然,当谈到NHibernate时,这些对象实际上是同一个实体。

我们通过重写Object Class的2个方法为NHibernate解决这个问题。 GetHashCode()和Equals()

GetHashCode()基本上返回基于对象状态的唯一哈希码。 Equals()比较两个对象是否相等

像这样:

public override int GetHashCode()
{
    return (this.GetType() + "|" + _tagName).GetHashCode();
}

public override bool Equals(object obj)
{
    return this.GetHashCode() == obj.GetHashCode();
}

基本上,GetHashCode将对象类型和标记名称连接成一个字符串,即App.Domain.Tag|nameoftag 并为该字符串生成哈希码

然后,Equals()将第一个对象的GetHashCode()结果与第二个对象的GetHashCode()结果进行比较,以测试是否相等。如果使用上面定义的两个对象执行此操作,则两个哈希码将相同,因此Equals()的比较将为true。当NHibernate在其内部工作中测试两个对象是否相等时,它将确定它们是相同的并且它应该解决您的问题。