我正在编写一个博客引擎作为学习练习。我知道那里有很多博客引擎,但请耐心等待......
我有一个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
...
对我做错了什么以及如何解决它的想法?
答案 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在其内部工作中测试两个对象是否相等时,它将确定它们是相同的并且它应该解决您的问题。