如何制作NHibernate缓存获取的子集合?

Aar*_*ght 14 .net nhibernate syscache2

我有一个相当简单的条件查询来获取子集合,如下所示:

var order = Session.CreateCriteria<Order>()
    .Add(Restrictions.Eq("Id", id))
    .SetFetchMode("Customer", FetchMode.Eager)
    .SetFetchMode("Products", FetchMode.Eager)
    .SetFetchMode("Products.Category", FetchMode.Eager)
    .SetCacheable(true)
    .UniqueResult<Order>();
Run Code Online (Sandbox Code Playgroud)

使用NH Prof,我已经验证这只使用冷缓存只进行一次数据库往返(如预期); 但是,在连续执行时,它仅从Order缓存中检索,然后使用SELECT(N + 1)为图中的每个子实体命中数据库,如下所示:

Cached query: SELECT ... FROM Order this_ left outer join Customer customer2 [...]
SELECT ... FROM Customer WHERE Id = 123;
SELECT ... FROM Products WHERE Id = 500;
SELECT ... FROM Products WHERE Id = 501;
...
SELECT ... FROM Categories WHERE Id = 3;
Run Code Online (Sandbox Code Playgroud)

等等等等.显然,它不缓存整个查询或图形,只缓存根实体.第一个"缓存查询"行实际上具有join它应该具有的所有条件 - 它肯定正确地缓存了查询本身,显然不是实体.

我已经尝试过使用SysCache,SysCache2甚至是HashTable缓存提供程序,而且我似乎总是得到同样的行为(NH版本3.2.0).

谷歌搜索出现了一些古老的问题,例如:

然而,这些似乎都很久以前就已得到解决,无论我使用哪个提供商,我都会遇到同样的不良行为.

我已经阅读了关于SysCache和SysCache2nhibernate.info文档,似乎没有任何我遗漏的内容.我已经尝试为查询中涉及的所有表添加cacheRegionWeb.config文件,但它不会更改任何内容(而AFAIK这些元素只是为了使缓存无效,所以无论如何它们都无关紧要).

所有这些超级老问题似乎都得到修复/解决,我认为这不可能仍然是NHibernate中的一个错误,它一定是我做错了.但是什么?

将NHibernate中的提取指令与二级缓存组合时,我需要做些什么特别的事情吗?我在这里错过了什么?

Aar*_*ght 37

我确实设法解决了这个问题,所以其他人终于可以得到一个直接的答案:

总结一下,我对第二级缓存和查询缓存之间的区别感到困惑; 杰森的答案在技术上是正确的,但它不知何故没有点击我.以下是我将如何解释它:

  • 查询缓存会跟踪查询发出的实体.它并没有缓存整个结果集.它相当于Session.Load在一个懒惰的实体上做一个; 它知道/期望一个存在但不跟踪任何其他有关它的信息,除非特别要求,此时它将实际加载真实实体.

  • 二级缓存跟踪每个实体的实际数据.当NHibernate的需要通过它的ID加载任何实体(凭借着Session.Load,Session.Get,延迟加载的关系,或者,在上述的情况下,一个实体"参照",这是一个高速缓存的查询的一部分),它看起来在第二级先缓存.

当然,这在事后看来是完全合理的,但当你听到"查询缓存"和"二级缓存"在很多地方几乎可以互换使用时,它就不那么明显了.

基本上,您需要配置两组两个设置,以便通过查询缓存查看预期结果:

1.启用两个缓存

在XML配置中,这意味着添加以下两行:

<property name="cache.use_second_level_cache">true</property>
<property name="cache.use_query_cache" >true</property>
Run Code Online (Sandbox Code Playgroud)

在Fluent NHibernate中,它是这样的:

.Cache(c => c
    .UseQueryCache()
    .UseSecondLevelCache()
    .ProviderClass<SysCacheProvider>())
Run Code Online (Sandbox Code Playgroud)

请注意UseSecondLevelCache以上内容,因为(在发布时)在Fluent NHibernate wiki页面从未提及过; 有几个启用查询缓存而不是二级缓存的示例!

2.为每个实体启用缓存

只是启用二级缓存几乎没有什么,这就是我被绊倒的地方.必须不仅启用二级缓存,而且还要为要缓存的每个单个实体类进行配置.

在XML中,这是在<class>元素内部完成的:

<cache usage="read-write"/>
Run Code Online (Sandbox Code Playgroud)

在Fluent NHibernate(非自动化)中,它在ClassMap构造函数中完成,或者在您放置其余映射代码的任何位置完成:

Cache.ReadWrite().Region("Configuration");
Run Code Online (Sandbox Code Playgroud)

必须为要缓存的每个实体执行此操作.可能可以在一个地方设置作为约定,但是你几乎错过了使用区域的能力(在大多数系统中,你不希望像配置数据一样缓存事务数据).

就是这样.这真的不是那么难,但很难找到一个好的,完整的例子,特别是对于FNH.


最后一点:这样做的自然结果是,当与查询缓存一起使用时,它使得急切的加入/获取策略非常难以预测.显然,如果NHibernate的看到一个查询缓存,它会使没有任何努力首先检查是否所有甚至任何实际的实体缓存.它几乎只是假设它们是,并试图单独加载每个.

这就是SELECT N + 1灾难的原因; 如果NH注意到实体不在二级缓存中并且只是正常执行查询,就像书面一样,使用提取和期货等等,那就没什么大不了的了.但它没有那样做; 相反,它会尝试加载每一个实体,和它的关系,及其子关系,其细分子关系,等等,一次一个.

因此,除非您已经为整个图中的所有实体显式启用了缓存,否则几乎没有必要使用查询缓存,即使这样,您也要非常小心(通过到期,依赖等). )缓存的查询不会超出他们应该检索的实体,否则你最终会使性能变差.