2

在 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) 调用是人为的,实际上不应实际发生)。

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

4

1 回答 1

2

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

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

NHibernate 有单独的查询和实体缓存(实际上有两个实体缓存 -会话缓存二级实体缓存),影响取决于正在使用的缓存。

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

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


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

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结合该查询及其关联的排名。

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

于 2010-01-25T18:21:12.170 回答