代码之家  ›  专栏  ›  技术社区  ›  Jason Barile

如何避免nhibernate.nonuniqueobjectException

  •  8
  • Jason Barile  · 技术社区  · 16 年前

    我正在写一个博客引擎作为学习练习。我知道那里有很多博客引擎,但请容忍我…

    我有一个blogpost实体,该实体有一个属性标记,它是与之关联的标记列表。settags(string)方法拆分字符串,创建具有指定标记名的新标记对象,并将它们添加到列表中。与blogpost.addtag(字符串标记名)相同。

    我希望发生的是,当我调用blogpost.addtag(“foo”)时,一个名为“foo”的标记实体已经存在,并被持久保存在数据库中,nhibernate只会意识到这一点,并将日志与现有的标记连接起来。

    在blogrepositiony.save()方法中,我检查标签列表中的每个标签是否已经存在。如果没有,我会通过调用tagrepository.save(tag)来保存它;

    问题是,在下面的示例代码中,我收到一个错误“nhibernate.nonuniqueobjectException:具有相同标识符值的另一个对象已与会话关联:tag 1,属于实体:cms.core.model.tag”,当我尝试使用现有标记持久化blogpost对象时。当我持久化一个只使用新标签的blogpost对象时,它们被创建并且一切都很好。

    注意,我还使用标记名作为bp_标记表数据库中的主键。当表只存储唯一的标记名时,使用整数或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:具有相同标识符值的另一个对象已与会话关联:tag 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 回复  |  直到 16 年前
        1
  •  8
  •   Daniel Auger    16 年前

    因为Tagname是ID,所以您正在运行NHibernate的标识映射。它的标识映射已经知道一个具有相同ID的对象,所以它会给出这个异常。

    您可能想尝试一些方法,看看该标记是否已经存在于该会话中,如果已经存在,那么将该预先存在的标记与第二个日志相关联。

    psuedo代码示例:

    var tag = session.Get<Tag>("Tag 1");
    
    if (tag != null)
    {
       post.AddTag(tag);
    }
    else
    {
       post.AddTag(new Tag("Tag 1"));
    }
    

    这篇博文会给你一个详细的解释: NHibernate - Cross session operations

        2
  •  4
  •   reach4thelasers    16 年前

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

    var object1 = new Tag("hello");
    var object2 = new Tag("hello");
    
    var areSame = (object1 == object2); // false
    

    您已经创建了两个状态相同的独立对象,但它们是两个不同的对象,因此如果您将它们进行相等比较,那么它们就不相同了。显然,当涉及到nHibernate时,这些对象实际上是相同的实体。

    我们通过覆盖对象类的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将对象类型和标记名称连接为字符串ie App.Domain.Tag|nameoftag 并为该字符串生成哈希代码

    equals()然后将第一个对象的getHashCode()结果与第二个要测试相等性的对象的getHashCode()结果进行比较。如果对上面定义的两个对象执行此操作,两个哈希代码将相同,因此equals()的比较将为真。当NHibernate测试两个对象的内部工作是否相等时,它将确定它们是相同的,它应该解决您的问题。