我是否可以强制nHibernate持久保存与已加载实体具有相同ID的新实体?

时间:2010-11-24 20:26:48

标签: nhibernate object-model

我的项目有一个Ticket实体,其OwnedBy属性。我正在使用nHibernate将票证持久保存到数据库中。

潜在故障单所有者的规范来源是Active Directory。由于我不想每次加载票证时都要查询Active Directory,因此我Ticket.OwnedBy保留到数据库并在获取时从加载票。

重新分配故障单的所有者后,我从Active Directory获取新的Owner并将其分配给Ticket.OwnedBy,然后调用Session.SaveOrUpdate(故障单)。当我提交事务时,NHibernate会抛出NonUniqueObjectException,因为具有相同ID的Owner已经与会话关联。

类定义

class Ticket {
    public int Id { get; set; }
    public Owner OwnedBy { get; set; }
    /* other properties, etc */
}
class Owner {
    public Guid Guid { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    /* other properties, etc */
}

流利的nHibernate映射

class TicketMap : ClassMap<Ticket> {
    public TicketMap() {
        Id(x => x.Id);
        References(x => x.OwnedBy)
            .Cascade.SaveUpdate()
            .Not.Nullable();
        /* other properties, etc */
    }    
}
class OwnerMap : ClassMap<Owner> {
    public OwnerMap() {
        Id(x => x.Guid)
            .GeneratedBy.Assigned()
        Map(x => x.Name);
        Map(x => x.Email);
        /* other properties, etc */
    }
}

示例代码

// unitOfWork.Session is an instance of NHibernate.ISession
Ticket ticket = unitOfWork.Session.Get<Ticket>(1);
Owner newOwner = activeDirectoryRepo.FindByGuid(/* guid of new owner, from user */);
ticket.OwnedBy = newOwner;
unitOfWork.Session.SaveOrUpdate(ticket);
unitOfWork.Commit(); // Throws NonUniqueObjectException

我希望nHibernate使用未附加的属性覆盖现有Owner的属性。 (我从AD获取的对象中的名称或电子邮件可能不同,AD应该是规范来源。)我尝试在SaveOrUpdate(票证)之前调用Session.SaveOrUpdateCopy(ticket.OwnedBy)Session.Merge(ticket.OwnedBy),但是异常仍然被抛出。我还阅读了关于NonUniqueObjectException的{​​{3}},但调用Session.Lock()也不起作用。

我有两个问题:

  1. 是否有一种简单的方法可以将nHibernate弯曲到我的意愿?
  2. 我可能在尝试将我从AD中获取的所有者视为与我在DB中存储的所有者相同的类型时,进行了体系结构错误。我怎样才能改进这个设计,所以我不会需要将nHibernate弯曲到我的意愿?

2 个答案:

答案 0 :(得分:2)

合并工作,最可能的问题是你没有正确调用它。 Merge将使用新对象属性更新现有对象,但Merge不会附加新对象。所以你必须使用现有的。如果在合并后使用新对象,则仍会出现相同的错误。

以下代码应解决问题:

//Merge the new Owner
unitOfWork.Session.Merge(newOwner);

//Get a valid Owner by retrieving from the session
Owner owner = session.Get<Owner>(newOwner.Id);

// Set the ticket to this owner instance instead of the new one
ticket.OwnedBy = owner;

unitOfWork.Session.Update(ticket);
unitOfWork.Commit(); 

Get会话从会话中检索到的所有者将拥有newOwner属性,但对会话也有效。

答案 1 :(得分:0)

为了保持现有Owner-entity的分离实例,只需在Owner实例上调用merge而不调用SaveOrUpdate就足够了。然后它将插入实体或更新现有实体。

如果合并不起作用,则出现问题。在这种情况下发布更多代码并发布您的映射。

顺便说一下:你也坚持门票吗?如果是这样,映射似乎很奇怪。你应该在Ticket和Map OwnedBy上有一个唯一的ID作为参考,可能是一个带有级联的逆映射。

更新: 你应该从两侧绘制它。使用Cascade和反向映射将所有者身份作为HasMany映射到故障单。将故障单侧映射为Cascade.None()并作为参考。

public TicketMap() {
    Id(x => x.Id);
    References(x => x.OwnedBy)
        .Cascade.None()
        .Not.Nullable();
    /* other properties, etc */
}   

class OwnerMap : ClassMap<Owner> {
public OwnerMap() {
    Id(x => x.Guid)
        .GeneratedBy.Assigned()
    Map(x => x.Name);
    Map(x => x.Email);
     HasMany<Ticket>(x => x.Tickets).KeyColumn("TicketId").Cascade.AllDeleteOrphan().LazyLoad().Inverse().NotFound.Ignore();
    /* other properties, etc */
}

这应该很好用。