NHibernate:使用 merge() 时子复合 ID 不会更新

发布于 2024-10-04 23:13:19 字数 2397 浏览 11 评论 0原文

映射:

    <class name="PhoneTypeTest" lazy="false" table="PhoneType">
  <cache usage="read-write"/>
  <id name ="Id" type="Int32" unsaved-value="0">
    <generator class="identity"/>
  </id>

  <bag name="Resources" table="PhoneTypeResource" lazy="false" cascade="all" inverse="true">
    <key column="PhoneTypeId" />
    <one-to-many class="PhoneTypeTestResource" not-found="ignore"/>
  </bag>  

</class>

<class name="PhoneTypeTestResource" lazy="false" table="PhoneTypeResource">
  <composite-id class="CCCC.ResourcesCompositeKey, DDDD" name="Id">
    <key-property name="OwnerId" column="PhoneTypeId"/>
    <key-property name="CultureId"/>
  </composite-id>
  <property name="Name"/>
</class>

实体:

public class PhoneTypeTest
{
    public PhoneTypeTest()
    {
        Resources = new List<PhoneTypeTestResource>();
    }

    public virtual int Id { get; set; }
    public virtual IList<PhoneTypeTestResource> Resources { get; set; }
}

public class PhoneTypeTestResource
{
    public virtual ResourcesCompositeKey Id { get; set; }
    public virtual string Name { get; set; }
}

单元测试:

        var ent = new PhoneTypeTest();
        ent.Resources.Add(new PhoneTypeTestResource { Id = new ResourcesCompositeKey { CultureId = En, OwnerId = 0 }, Name = "Name" });

        Session.Merge(ent);
        Session.Flush();
        Session.Clear();

nHib 生成的 SQL:

-- statement #1
INSERT INTO PhoneType
DEFAULT VALUES


select SCOPE_IDENTITY()


-- statement #2
SELECT phonetypet0_.PhoneTypeId as PhoneTyp1_61_0_,
       phonetypet0_.CultureId   as CultureId61_0_,
       phonetypet0_.Name        as Name61_0_
FROM   PhoneTypeResource phonetypet0_
WHERE  phonetypet0_.PhoneTypeId = 0 /* @p0 */
       and phonetypet0_.CultureId = 'en' /* @p1 */

-- statement #3
INSERT INTO PhoneTypeResource
           (Name,
            PhoneTypeId,
            CultureId)
VALUES     ('Name' /* @p0 */,
            0 /* @p1 */,
            'en' /* @p2 */)

-- statement #4
ERROR: 
Could not synchronize database state with session

SO,如您所见,问题是 nHib 在保存其父级后不会更新子级的复合 ID,并且尝试保存子级失败。为什么??如何让 nHib 更新这些 id?另外,如果我使用 SaveOrUpdate() 而不是 Merge(),它工作得很好!但我必须使用合并。请帮忙!

Mapping:

    <class name="PhoneTypeTest" lazy="false" table="PhoneType">
  <cache usage="read-write"/>
  <id name ="Id" type="Int32" unsaved-value="0">
    <generator class="identity"/>
  </id>

  <bag name="Resources" table="PhoneTypeResource" lazy="false" cascade="all" inverse="true">
    <key column="PhoneTypeId" />
    <one-to-many class="PhoneTypeTestResource" not-found="ignore"/>
  </bag>  

</class>

<class name="PhoneTypeTestResource" lazy="false" table="PhoneTypeResource">
  <composite-id class="CCCC.ResourcesCompositeKey, DDDD" name="Id">
    <key-property name="OwnerId" column="PhoneTypeId"/>
    <key-property name="CultureId"/>
  </composite-id>
  <property name="Name"/>
</class>

Entities:

public class PhoneTypeTest
{
    public PhoneTypeTest()
    {
        Resources = new List<PhoneTypeTestResource>();
    }

    public virtual int Id { get; set; }
    public virtual IList<PhoneTypeTestResource> Resources { get; set; }
}

public class PhoneTypeTestResource
{
    public virtual ResourcesCompositeKey Id { get; set; }
    public virtual string Name { get; set; }
}

Unit test:

        var ent = new PhoneTypeTest();
        ent.Resources.Add(new PhoneTypeTestResource { Id = new ResourcesCompositeKey { CultureId = En, OwnerId = 0 }, Name = "Name" });

        Session.Merge(ent);
        Session.Flush();
        Session.Clear();

SQL generated by nHib:

-- statement #1
INSERT INTO PhoneType
DEFAULT VALUES


select SCOPE_IDENTITY()


-- statement #2
SELECT phonetypet0_.PhoneTypeId as PhoneTyp1_61_0_,
       phonetypet0_.CultureId   as CultureId61_0_,
       phonetypet0_.Name        as Name61_0_
FROM   PhoneTypeResource phonetypet0_
WHERE  phonetypet0_.PhoneTypeId = 0 /* @p0 */
       and phonetypet0_.CultureId = 'en' /* @p1 */

-- statement #3
INSERT INTO PhoneTypeResource
           (Name,
            PhoneTypeId,
            CultureId)
VALUES     ('Name' /* @p0 */,
            0 /* @p1 */,
            'en' /* @p2 */)

-- statement #4
ERROR: 
Could not synchronize database state with session

SO, as you can see, the problem is that nHib does NOT update child a composite-id after its parent was saved, and the attempt to save children fails. Why?? How can I make nHib to update these ids? Also, if I use SaveOrUpdate() instead Merge(), it works fine!! But I must use merge. Please help!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

隐诗 2024-10-11 23:13:19

我最终在保存后手动更新了子 ID——这只是 nHibernate 错误的另一个解决方法。不要使用复合 id,或者更好的解决方案,不要使用 nHibernate。

I ended up manually updating child ids after saving - just another workaround of nHibernate's bug. Do not use composite ids or, even better solution, do not use nHibernate..

简单气质女生网名 2024-10-11 23:13:19

我遇到了完全相同的问题。在进行插入之前,我必须对一些帖子进行更新。然后,当我准备插入时,我收到错误消息,表明该对象已在使用中。

SaveOrUpdate 给了我这个错误,ofc Update 也给了我这个错误。合并有效,但它只保存了父对象,而不保存子对象。

但联系是子对象是负责执行保存的对象(在映射对象中)。

  HasMany(x => x.Info)
            .Cascade.SaveUpdate();

所以我想公平地说,它有点按预期工作。

令人烦恼的部分是,如果您在父对象上使用 SaveOrUpdate ,它将实际上也更新了子级,因为它理解连接,但是如果您使用合并,它根本无法理解连接!

以下操作

public AlertEntity InsertCapAlerts(AlertEntity capMessage)
    {

        using (var transaction = _session.BeginTransaction(IsolationLevel.ReadCommitted))
        {
            var getCap = GetCapAlert(capMessage.Id);
            if (getCap != null)
            {
                InfoEntity infoToSave = capMessage.Infos.FirstOrDefault();
                _session.Merge(infoToSave);
                _session.Merge(capMessage);
            }
            else
                _session.Save(capMessage);

            transaction.Commit();
            return capMessage;
        }
    }

基本上,我所做的是,当我收到新的 capMessage 时,我将执行 :保存,我首先检查它是否已经存在于数据库中,如果不存在,那么只需执行 Save() 即可。但是如果它确实存在(这是 SaveOrUpdate 总是崩溃的地方),那么我只需创建子记录的一个新实例。 (我们只有一个孩子,因此 FirstOrDefault())。

第一次尝试我只是使用 Merge 将 Child 对象保存到数据库(因为 SaveOrUpdate 仍然给我带来错误,因为该对象在上一个会话中使用,是的,我这样做了)尝试 session.Clear() 和 Flush 等等)。它工作得很好,但后来我注意到我的子对象不再指向它的父对象。所以子对象现在是数据库街头哭泣的孤儿。所以我们需要做的就是将Parent ID再次指向子对象。

InfoEntity infoToSave = capMessage.Infos.FirstOrDefault();
infoToSave.AlertEntity_id = capMessage.Id;
_session.Merge(infoToSave);
_session.Merge(capMessage);

你为什么问?因为 nHibernate 非常可爱,所以当它运行 Merge 时,它​​不会给出有关子记录的详细信息(就像我之前提到的运行 SaveOrUpdate 时那样)。因此,当您使用合并时,您必须自己更新所有子帖子,并且必须再次指出父级。

如果有人知道更好的解决方案。请通知我。否则,我希望这个小代码示例能够以任何方式帮助任何人:)

I had the exact same problem. I had to run an update on some posts before I would do an insert. Then when I Was about to do my insert,I got the error that the object was already in use.

SaveOrUpdate gave me that error, and ofc Update too. Merge worked BUT it only saved the parent object and not the child objects.

The connection though is that the child object is the one responsible to perform the save (in the mapobject.

  HasMany(x => x.Info)
            .Cascade.SaveUpdate();

so I guess to be fair, it kinda works as intended.

The annoying part is that if you use SaveOrUpdate on a parent object, it will actually update the childs too as it understands the connection. But if you use Merge, it wont understand the connection at all!

Here's a bit of that code

public AlertEntity InsertCapAlerts(AlertEntity capMessage)
    {

        using (var transaction = _session.BeginTransaction(IsolationLevel.ReadCommitted))
        {
            var getCap = GetCapAlert(capMessage.Id);
            if (getCap != null)
            {
                InfoEntity infoToSave = capMessage.Infos.FirstOrDefault();
                _session.Merge(infoToSave);
                _session.Merge(capMessage);
            }
            else
                _session.Save(capMessage);

            transaction.Commit();
            return capMessage;
        }
    }

Basicly, what I did is that when I get my new capMessage that I'm going to save, I first check if it already exists in the db. If it doesnt, then fine, just do a Save(). But if it does exist (here's where SaveOrUpdate always crashed) then I just make a new instance of the child record (we only have one child, therefor FirstOrDefault()).

The first attempt I just saved the Child object to the db by using Merge (because SaveOrUpdate still gives me the error due the object being used in a previous session, and yes I did try session.Clear() and Flush and what not). It worked peachy but then I noticed that my childobject no longer points at its parent. So the childobject is now an orphan crying on the streets of the database. So what we need to do is to point the Parent ID again to the child object.

InfoEntity infoToSave = capMessage.Infos.FirstOrDefault();
infoToSave.AlertEntity_id = capMessage.Id;
_session.Merge(infoToSave);
_session.Merge(capMessage);

Why you ask? Because nHibernate is soooo cute that when it runs Merge, it doesn't give a flying dutch about the childrecords (as it does when you run SaveOrUpdate as I mentioned before). So when you use Merge, you have to update all childposts yourself AND you have to point out the Parent again.

If anyone knows a better solution to this. Please do inform me. Otherwise, I hope that this little code example helped anyone in any way :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文