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

时间:2012-01-06 16:42:50

标签: .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>();

使用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;

依此类推。显然,它不会缓存整个查询或图形,只缓存根实体。第一个“缓存查询”行实际上具有它应该具有的所有join条件 - 它肯定正确地缓存查询本身,而不是实体,显然。

我使用SysCache,SysCache2甚至是HashTable缓存提供程序尝试了这一点,我似乎总是得到同样的行为(NH版本3.2.0)。

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

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

我已阅读nhibernate.info documentation on SysCache and SysCache2并且似​​乎没有任何我遗漏的内容。我已尝试将cacheRegion行添加到查询中涉及的所有表的Web.config文件中,但它不会更改任何内容(而AFAIK这些元素只是无效缓存,所以无论如何它们都无关紧要。)

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

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

2 个答案:

答案 0 :(得分:37)

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

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

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

  • 二级缓存会跟踪每个实体的实际数据。当NHibernate需要通过其ID加载任何实体时(凭借Session.LoadSession.Get,延迟加载的关系,或者在上面的情况下,是一个实体“引用”,它是缓存查询的一部分),它将首先查看二级缓存。

当然,事后看来这很有道理,但是当你听到“查询缓存”和“二级缓存”在很多地方几乎可以互换使用时,它就不那么明显了。

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

1。启用两个缓存

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

<property name="cache.use_second_level_cache">true</property>
<property name="cache.use_query_cache" >true</property>

在Fluent NHibernate中,就是这样:

.Cache(c => c
    .UseQueryCache()
    .UseSecondLevelCache()
    .ProviderClass<SysCacheProvider>())

请注意上面的UseSecondLevelCache因为(在发布时)Fluent NHibernate wiki page上提到从不;有几个启用查询缓存而不是二级缓存的示例!

2。为每个实体启用缓存

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

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

<cache usage="read-write"/>

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

Cache.ReadWrite().Region("Configuration");

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

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


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

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

因此,使用查询缓存几乎没有任何意义,除非您已明确启用整个图中所有实体的缓存,即便如此,您我希望非常小心(通过到期,依赖等)缓存查询不会比它们应该检索的实体更久,否则你最终会使性能变差。

答案 1 :(得分:4)

缓存查询仅存储实体的ID,而不是实体的值。在缓存实体中,仅缓存相关实体的ID。因此,如果您不缓存所有涉及的实体以及将相关实体标记为缓存,则最终可能会选择n + 1.