【问题标题】:Nhibernate QueryOver don't get latest database changesNhibernate QueryOver 不获取最新的数据库更改
【发布时间】:2013-03-26 03:10:29
【问题描述】:

我正在尝试使用QueryOver 从数据库更新记录。 我的代码最初创建一个实体并保存在数据库中,然后在外部(从其他程序,手动或在其他机器上运行的同一程序)更新数据库中的相同记录,当我通过字段更改调用queryOver过滤时,查询获取记录但没有最新更改。

这是我的代码:

//create the entity and save in database
MyEntity myEntity = CreateDummyEntity();
myEntity.Name = "new_name";

MyService.SaveEntity(myEntity);

// now the entity is updated externally changing the name property with the 
// "modified_name" value (for example manually in TOAD, SQL Server,etc..)

//get the entity with QueryOver
var result = NhibernateHelper.Session
                 .QueryOver<MyEntity>()
                 .Where(param => param.Name == "modified_name")
                 .List<T>();

前面的语句得到了一个只有一条记录的集合(好),但是的 name 属性是用旧值而不是“modified_name”建立的。

我该如何解决这种行为?一级缓存打扰我了?

也会出现同样的问题
CreateCriteria<T>();

由于应用程序框架的要求,我的 NhibernateHelper 中的会话不会在任何时候关闭,只会为与 session.Save() 关联的每个提交创建事务。 如果我打开一个新会话来执行查询,我显然会从数据库中获得最新的更改,但这种方法是设计要求不允许的。

我还在 NHibernate SQL 输出中检查了一个带有 WHERE 子句的选择正在执行(因此 Nhibernate 会命中数据库)但不更新返回的对象!!!

更新

这是调用 session.Save 后 SaveEntity 中的代码:完成对 Commit 方法的调用

public virtual void Commit() 
{ 
  try 
  { 
    this.session.Flush(); 
    this.transaction.Commit();
  } 
  catch 
  { 
    this.transaction.Rollback(); 
    throw; 
  } 
  finally 
  { 
    this.transaction = this.session.BeginTransaction();
  } 
}

NHibernate 为 SaveEntity 生成的 SQL:

NHibernate: INSERT INTO MYCOMPANY.MYENTITY (NAME) VALUES (:p0);:p0 = 'new_name'. 

NHibernate 为 QueryOver 生成的 SQL:

NHibernate: SELECT this_.NAME as NAME26_0_ 
            FROM MYCOMPANY.MYENTITY this_ 
            WHERE this_.NAME = :p0;:p0 = 'modified_name' [Type: String (0)]. 

由于公司保密政策,查询已被修改。

帮助非常感谢。

【问题讨论】:

  • 这听起来可能很傻,但有一次在一个项目中,我们将数据库从 SQLServer 转移到 Oracle,并且我们通过 oracle PL/SQL 开发人员连接到它。我会通过 oracle 客户端手动对其进行更改,然后运行我的应用程序并返回旧值,而不是更新后的值。结果我不得不手动 COMMIT 数据库插入/更新。
  • @Carlos。只是为了确认您在执行QueryOver 之前检查数据库MyEntity.Name 是否填充了“modified_name”?此外,this.session.Flush(); 行也是不必要的,因为对Commit() 的调用将为您执行刷新。
  • @penfold:当然,在执行queryOver()之前,该名称已成功填充到数据库中,因为我在执行查询之后设置了一个断点,并且我检查了数据库是否已更新。感谢您提供有关session.FLush().的更多信息
  • @mridula,当你说 原来我必须手动提交数据库插入/更新时,我不明白你的意思。。你做了什么来获得最新的变化?
  • 好吧,在使用 oracle 客户端对记录进行更改后,我必须运行commit 命令才能使更改在数据库中生效。我习惯使用 SQLServer,它不需要本手册 commit。所以当我运行我的应用程序时,这些更改是不可见的,因为我没有commited 他们。

标签: nhibernate nhibernate-criteria queryover


【解决方案1】:

我得到了一些非常相似的东西,并尝试调试 NHibernate。 在我的场景中,会话在相关集合 (cascade:all) 中创建了一个包含几个孩子的对象,然后调用ISession.Flush()。 记录被写入数据库,会话需要继续而不关闭。同时,另外两条子记录被写入数据库并提交。 一旦原始会话尝试使用 QueryOverJoinAlias 重新加载图形,生成的 SQL 语句看起来非常好,并且行正在正确返回,但是发现应该接收这些新子的集合具有已经在会话中初始化(应该如此),并且基于该 NH 出于某种原因决定完全忽略相应的行。 我认为 NH 在这里做了一个无效的假设,即如果集合已经标记为“已初始化”,则不需要从查询中重新加载。 如果更熟悉 NHibernate 内部结构的人能加入进来,那就太好了。

【讨论】:

【解决方案2】:

这里的人不想打电话给Session.Clear(),因为它太强大了。
另一方面,当事先不知道对象时,Session.Evict() 可能看起来不适用。
实际上它仍然可以使用。
您需要先使用查询检索缓存的对象,然后对它们调用Evict()。然后再次检索再次调用相同查询的新对象。
如果对象一开始没有被缓存,这种方法效率会稍低——因为那时实际上会有两个“新”查询——但似乎没有太多可以解决这个缺点......
顺便说一句,Evict() 也无例外地接受空参数 - 如果查询的对象实际上不存在于数据库中,这很有用。

var cachedObjects = NhibernateHelper.Session
             .QueryOver<MyEntity>()
             .Where(param => param.Name == "modified_name")
             .List<T>();

foreach (var obj in cachedObjects) 
    NhibernateHelper.Session.Evict(obj);

var freshObjects = NhibernateHelper.Session
             .QueryOver<MyEntity>()
             .Where(param => param.Name == "modified_name")
             .List<T>()

【讨论】:

    【解决方案3】:

    经过搜索、搜索、思考和思考......我找到了解决方案。

    修复:它包括打开一个新会话,在这个会话中调用QueryOver&lt;T&gt;(),数据被成功刷新。如果您的子集合未初始化,您可以调用HibernateUtil.Initialize(entity) 或在映射中设置lazy="false"。在大型集合中特别注意lazy="false",因为您可能会获得较差的性能。要解决此问题(加载大型集合的性能问题),请在集合映射中设置lazy="true",并调用受影响集合的上述方法HibernateUtil.Initialize(entity) 从数据库中获取子记录;例如,您可以从表中获取所有记录,如果您需要访问特定实体的所有子记录,请仅为感兴趣的对象调用HibernateUtil.Initialize(collection)

    注意:正如@martin ernst 所说,更新问题可能是休眠中的错误,我的解决方案只是临时修复,必须在休眠中解决。

    【讨论】:

      【解决方案4】:

      尝试将您的最后一个查询更改为:

       var result = NhibernateHelper.Session
                   .QueryOver<MyEntity>()
                   .CacheMode(CacheMode.Refresh)
                   .Where(param => param.Name == "modified_name")
      

      如果仍然不起作用,请尝试在查询后添加:

      NhibernateHelper.Session.Refresh(result);
      

      【讨论】:

      • 感谢您的帮助。我之前尝试过使用您的第二种方法 NhibernateHelper.Session.Refresh(result); 成功,但我想避免使用刷新方法。您的第一种方法仍然无效。其他时间非常感谢您。
      • 如果第二种方法有效,那么它看起来像 NHibernate 中的一个错误 - 您可以尝试在查询之前调用 Session.Clear()
      • 是的,Session.Clear() 有效,但所有对象都是 Evict,我不想要这个。
      • 您可以为 NHibernate 记录错误报告,和/或自己修复它,因为它是开源的...
      【解决方案5】:

      调用session.Save(myEntity) 不会立即将更改保存到数据库中*。当框架本身或您自己调用session.Flush() 时,这些更改将保持不变。有关刷新以及何时调用它的更多信息,请参阅this question 和nhibernate documentation about flushing。

      另外执行查询不会导致一级缓存被命中。这是因为一级缓存仅适用于GetLoad,即如果MyEntity 之前已经加载了id 为1,session.Get&lt;MyEntity&gt;(1) 将命中一级缓存,而session.QueryOver&lt;MyEntity&gt;().Where(x =&gt; x.id == 1) 将不是。

      有关 NHibernate 缓存功能的更多信息,请参阅 Ayende Rahien 的 this 帖子。

      总的来说,您有两个选择:

      1. SaveEntity 方法中使用事务,即

        using (var transaction = Helper.Session.BeginTransaction())
        {
          Helper.Session.Save(myEntity);
          transaction.Commit();
        }
        
      2. SaveEntity 方法中调用session.Flush(),即

          Helper.Session.Save(myEntity);
          Helper.Session.Flush();
        

      第一个选项在几乎所有场景中都是最好的。

      *我知道这条规则的唯一例外是使用Identity 作为 id 生成器类型。

      【讨论】:

      • 谢谢@penfold,我忘了告诉你,我的名为 SaveEntity 的服务也会执行你指示的所有步骤(我执行刷新和提交),因此保存过程总是正常的。我继续这个问题,因为无论如何你的方法正在我的代码中执行。
      • @Carlos。您是否可以更新问题以包括您对SaveEntity 的实现,以及 NHibernate 为“SaveEntity”和随后的“QueryOver”生成的 SQL。
      • 当然@penfold,这里是SaveEntity中调用session.Save后的代码:完成了对Commit方法的调用 --> ` public virtual void Commit() { try { this.session.Flush (); this.transaction.Commit(); } 捕捉 { this.transaction.Rollback();扔; } 最后 { this.transaction = this.session.BeginTransaction(); } }`
      • -NHibernate 为 SaveEntity 生成的 SQL:NHibernate: INSERT INTO MYCOMPANY.MYENTITY (NAME) VALUES (:p0);:p0 = 'new_name'。 -NHibernate 为 QueryOver 生成的 SQL:NHibernate: SELECT this_.NAME as NAME26_0_ FROM MYCOMPANY.MYENTITY this_ WHERE this_.NAME = :p0;:p0 = 'modified_name' [Type: String (0)]。由于公司保密政策,查询已被修改。
      • 我坚持说保存过程没问题,休眠的错误结果是问题所在。我不明白为什么 hibernare 执行选择查询但没有填充返回的对象并从缓存中获取!
      【解决方案6】:

      据我所知,您有几种选择:

      • 通过调用sessionFactory.OpenStatelesSession() 而不是sessionFactory.OpenSession() 将您的会话设置为IStatelessSession
      • 在数据库中持久化实体后执行Session.Evict(myEntity)
      • QueryOver 之前执行Session.Clear()
      • QueryOver 之前将会话的CacheMode 设置为Ignore, Put or Refresh(从未测试过)

      我想选择将取决于您对长时间运行的会话的使用情况(恕我直言,这似乎带来的问题多于解决方案)

      【讨论】:

      • 非常感谢@jbl,但我已经尝试了你指出的所有选项,由于设计要求,在我的上下文中没有什么是有效的(整个应用程序生命周期只有一个会话)
      • @Carlos :这些应该可以解决一级缓存问题。您的应用程序是否对延迟加载、拦截器、二级缓存使用了一些自定义行为?问题可能就在那里。
      • 不,二级缓存未被引用,具有相关更改的唯一特定情况是在集合映射中(1:N关系):&lt;bag name="MyChild" inverse="true" cascade="all"&gt; &lt;key&gt; &lt;column name="MYCOLUMN" not-null="true" /&gt; &lt;/key&gt; &lt;one-to-many class="MyChild" /&gt; &lt;/bag&gt;
      • @Carlos :从您提供的代码来看,您似乎正在尝试实现字符串集合。您似乎也在更改键列。您应该将映射添加到您的问题中。
      • 不,没有字符串集合,是自定义对象集合。它是具有某些属性的单个对象,在此上下文中不需要更多相关信息。非常感谢您的关注。
      猜你喜欢
      • 2012-07-05
      • 1970-01-01
      • 1970-01-01
      • 2011-09-30
      • 1970-01-01
      • 2021-01-02
      • 1970-01-01
      • 2013-05-22
      • 1970-01-01
      相关资源
      最近更新 更多