我是否可以强制nHibernate保留一个新实体,该实体的ID与已加载的实体相同?

我是否可以强制nHibernate保留一个新实体,该实体的ID与已加载的实体相同?,nhibernate,object-model,Nhibernate,Object Model,我的项目有一个票证实体,该实体具有一个所有者属性。我正在使用nHibernate将票据持久化到数据库 潜在票证所有者的规范源是Active Directory。由于我不希望每次加载票证时都必须查询Active Directory,因此我还将Ticket.OwnedBy持久化到数据库中,并在获取票证时从那里加载它 重新分配票证的所有者时,我从Active Directory获取新的所有者,并将其分配给ticket.OwnedBy,然后调用Session.SaveOrUpdate(票证)。当我提交事

我的项目有一个
票证
实体,该实体具有一个
所有者
属性。我正在使用nHibernate将票据持久化到数据库

潜在票证所有者的规范源是Active Directory。由于我不希望每次加载票证时都必须查询Active Directory,因此我还将
Ticket.OwnedBy
持久化到数据库中,并在获取票证时从那里加载它

重新分配票证的所有者时,我从Active Directory获取新的
所有者
,并将其分配给
ticket.OwnedBy
,然后调用Session.SaveOrUpdate(票证)。当我提交事务时,NHibernate抛出一个
unonqueObjectException
,因为具有相同ID的
所有者已与会话关联

类定义 Fluent-nHibernate映射
classticketmap:ClassMap{
公开票证地图(){
Id(x=>x.Id);
引用(x=>x.OwnedBy)
.Cascade.SaveUpdate()
.Not.Nullable();
/*其他财产等*/
}    
}
类所有者映射:类映射{
公共所有者映射(){
Id(x=>x.Guid)
.GeneratedBy.Assigned()
Map(x=>x.Name);
Map(x=>x.Email);
/*其他财产等*/
}
}
示例代码
//unitOfWork.Session是NHibernate.ISession的一个实例
票证=unitOfWork.Session.Get(1);
所有者newOwner=activeDirectoryRepo.FindByGuid(/*新所有者的guid,来自用户*/);
ticket.OwnedBy=新所有者;
unitOfWork.Session.SaveOrUpdate(票证);
unitOfWork.Commit();//抛出非请求对象异常
我希望nHibernate用未附加所有者的属性覆盖现有所有者的属性。(我从AD获取的对象中的名称或电子邮件可能不同,AD应该是规范源。)我尝试在SaveOrUpdate(ticket)之前调用
Session.SaveOrUpdateCopy(ticket.OwnedBy)
Session.Merge(ticket.OwnedBy)
,但仍会引发异常。我也读过关于
unonqueObjectException
,但是调用Session.Lock()也不起作用

我有两个问题:

  • 有没有一种简单的方法可以让nHibernate服从我的意愿
  • 我可能在试图将我从广告中获取的所有者与我存储在DB中的所有者视为同一类型时犯了架构错误。我怎样才能改进这个设计,这样我就不需要按照自己的意愿弯曲nHibernate了

  • 为了持久化现有所有者实体的分离实例,在不调用SaveOrUpdate的情况下对所有者实例调用merge就足够了。然后,它将插入实体或更新现有实体

    如果合并不起作用,那么就有问题了。在这种情况下发布更多代码并发布映射

    顺便问一下:你也坚持买票吗?如果是这样的话,这种映射似乎相当奇怪。您应该在Ticket和Map OwnedBy上有一个唯一的ID作为引用,可能是一个带有级联的反向映射

    更新: 你应该从两边把它画出来。将所有者端映射为带有级联的HasMany-to-ticket和反向映射。将票证端映射为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 */
    }
    
    public TicketMap(){
    Id(x=>x.Id);
    引用(x=>x.OwnedBy)
    .Cascade.None()
    .Not.Nullable();
    /*其他财产等*/
    }   
    类所有者映射:类映射{
    公共所有者映射(){
    Id(x=>x.Guid)
    .GeneratedBy.Assigned()
    Map(x=>x.Name);
    Map(x=>x.Email);
    HasMany(x=>x.Tickets).KeyColumn(“TicketId”).Cascade.AllDeleteOrphan().LazyLoad().Inverse().NotFound.Ignore();
    /*其他财产等*/
    }
    

    这应该可以很好地工作。

    合并工作正常,最可能的问题是您没有正确调用它。合并将使用新对象属性更新现有对象,但合并不会附加新对象。因此,您必须使用现有对象。如果在合并后使用新对象,您仍然会收到相同的错误

    以下代码应修复此问题:

    //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(); 
    
    //合并新所有者
    unitOfWork.Session.Merge(新所有者);
    //通过从会话检索来获取有效的所有者
    所有者=session.Get(newOwner.Id);
    //将票证设置为此所有者实例而不是新实例
    ticket.OwnedBy=所有者;
    unitOfWork.Session.Update(票据);
    unitOfWork.Commit();
    

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

    假设SecondOwner是新所有者,那么您为什么不能只执行
    Ticket.OwnedBy=SecondOwner
    ,然后调用SaveOrUpdate(Ticket)?也许你应该实现一个
    IInterceptor
    ?你能展示一下你在保存票据时如何替换所有者的代码吗?映射也会有帮助。@Will,@James:我用映射和我用来更改所有者的代码更新了帖子。是的,票据是根域实体。我不会直接保留所有者,只会通过其他域实体tities喜欢Ticket。我也用映射和更多的代码更新了这个问题。在这种情况下,从两边映射是解决方案吗?
    所有者目前没有
    Tickets
    集合,我的应用程序也没有任何真正的需要。我承认我对NHibernate没有很好的理解,但这到底会是什么是吗?您想要一个双向映射。从逻辑上看,所有者有许多票证。票证只包含对所有者的引用,但不包含一个。我相信Merge将返回一个合并的对象实例,这样您就可以执行Owner Owner=unitOfWork.Session.Merge(newOwner);Good call,为您保存get语句。您必须强制转换对象,并且应该可以:ticket.OwnedBy=unitOfWork.Session.Merge(newOwner)作为所有者在我的问题中,我说我尝试了这个,并且仍然得到了异常,但在仔细查看代码后,我发现
    // 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
    
    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 */
    }
    
    //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();