【问题标题】:Impact on NHibernate caching for searches with results including calculated value mapped as a formula (e.g. rank)对搜索结果的 NHibernate 缓存的影响,包括映射为公式的计算值(例如排名)
【发布时间】:2010-01-26 02:21:12
【问题描述】:

在 NHibernate 中使用公式定义计算属性时,如果公式根据查询限制改变其结果,特别是在查询缓存方面,这意味着什么?

更具体地说,考虑以下简单的 C# 类:

public class Entity
{
    public Entity() { }
    public virtual int Id { get; protected set; }
    public virtual string Key { get; protected set; }
    public virtual string Value { get; protected set; }
    public virtual int Rank { get; protected set; }
}

使用以下简单的 NHibernate 映射进行映射:

<class name="Entity" mutable="false">
    <id name="Id">
        <generator class="native">
    </id>
    <property name="Key"/>
    <property name="Value"/>
    <property name="Rank" formula="row_number() over(order by value)">
</class>

使用会话工厂运行,hibernate.cache.use_query_cache 选项设置为true,并通过以下方式查询:

ICriteria criteria = session.CreateCriteria(typeof(Entity));
criteria.SetCacheable(true);
criteria.SetCacheRegion("SearchResults");
IList<Entity> queryResult1 = criteria.List<Entity>();

criteria = session.CreateCriteria(typeof(Entity));
criteria.SetCacheable(true);
criteria.SetCacheRegion("SearchResults");
criteria.Add(Restrictions.Like("Key", "a", MatchMode.Anywhere));
IList<Entity> queryResult2 = criteria.List<Entity>();

Entity directResult = session.Load<Entity>(id);

NHibernate 会以合理的方式处理返回的实体吗?或者,一个缓存查询的“Rank”值是否会由于查询缓存而污染另一个查询的 Rank 值?在 NHibernate 映射中使用这样的公式时是否还有其他问题?

编辑:

还可能值得注意的是,在我的特定情况下,“实体”不是一流的业务实体,而是一种元实体。它映射到其他一流实体的索引数据库视图,并专门用于搜索(session.Load(id) 调用是人为设计的,实际上不应该发生)。

而且,如果我怀疑缓存对缓存有影响,那么类似的用例可能存在哪些替代方案来避免潜在问题?

【问题讨论】:

  • 不是您问题的答案,但您可能想查看 NHibernate.Lucene。这更容易实现,并且以您尝试的方式进行搜索的执行速度更快。
  • 此示例仅用于说明问题。我们实际上正在使用 SQL Server 的全文搜索,如果/当我们遇到功能或可伸缩性限制时,Lucene 将在评估列表的顶部。
  • Rank 属性并不是实体上真正的持久值 - 它是实体在执行的特定查询中返回的结果。恕我直言,将它放在您的域类上是不对的。而且您还可以在单​​个会话中获取查询,相互“干扰”(或如您所说的那样相互“污染”)。
  • 明白了——这就是问题所在。您能否详细说明答案中的潜在问题(而不是评论),甚至可能提供替代建议以更好地处理检索此类数据? (注意:已编辑问题以提供有关用例的更多详细信息)

标签: sql-server nhibernate calculated-columns rank


【解决方案1】:

经过进一步的实验:是的,存在可能导致结果不一致的缓存影响; NHibernate 无法自动知道该公式可以在查询之间更改具有相同标识符的实体结果的值(并假设它不会)。

具有问题中的类映射将导致排名与其余实体数据一起存储。这使得后续查询最终可能会返回来自其他查询而不是正在运行的查询的排名值,因此排名不符合预期。

NHibernate 有单独的 query 和实体缓存(实际上有两个实体缓存 - session cachesecond level entity cache),其影响取决于正在使用的缓存。

当查询缓存未启用时,如果你使 同一会话中的两个不同查询共享一个结果但具有不同的排名。在这种情况下,同一会话的第二个查询不会覆盖来自第一个查询的会话中已经存在的实体数据(因为该工作单元可能已更改),因此返回的排名值将与第一个查询相同查询而不是第二个查询的实际排名。从第一个查询中驱逐结果应该避免这个问题(但不是推荐的解决方案;见下文)

启用查询缓存 时,在执行了具有不同排名的结果的其他查询之后重复相同查询时,也可能会收到不正确的排名值。在这种情况下,第一个查询执行将结果标识符添加到查询缓存中,并将实体(及其排名)添加到实体缓存中。交错查询(在另一个会话中运行时)可能导致实体缓存中与实体一起存储的排名值发生变化。当重新执行第一个查询时,缓存的标识符用于查找缓存的实体(具有更改的等级)。


可以通过将实体更改为仅包含实体的持久值(即不包括排名)来完全解决该问题。然后,对于查询,使用投影来提取该查询的标识符和排名:

ICriteria criteria = session.CreateCriteria(typeof(Entity));
criteria.SetCacheable(true);
criteria.SetCacheRegion("SearchResults");
criteria.SetProjection
    (Projections.Id(), 
     Projections.SqlProjection("row_number() over(order by value) as Rank",
                               new[] { "Rank" },
                               new[] { NHibernateUtil.Int32 }));

在这种情况下,由于排名是一种值类型,因此查询缓存会将排名与该特定查询的查询结果标识符一起存储。然后,使用第二个查询,使用投影标识符查找实体值。棘手的部分是,在执行实体查询时,您需要避免 N+1 类型问题,并且您需要创建另一个数据结构来结合 Entity 及其与该查询相关的排名。

您必须使用两个单独的查询而不是单个查询,这有点烦人,但这似乎是以适当方式使用缓存的唯一方法。

【讨论】:

    猜你喜欢
    • 2020-10-20
    • 2015-04-20
    • 1970-01-01
    • 1970-01-01
    • 2010-11-22
    • 2011-09-25
    • 1970-01-01
    • 2012-03-01
    • 2023-03-06
    相关资源
    最近更新 更多