使用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值?在NHibernate映射中使用这样的公式时还有其他问题吗?

编辑:

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

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

有帮助吗?

解决方案

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

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

NHibernate有单独的查询和实体缓存(那里)实际上是两个实体缓存 - 会话缓存和< a href =“http://nhforge.org/doc/nh/en/index.html#performance-cache”rel =“nofollow noreferrer”>二级实体缓存)影响取决于哪些是正在使用。

如果未启用查询缓存,则可以接收错误的排名值 在同一会话中共享结果但具有不同排名的两个不同查询。在这种情况下,同一会话的第二个查询将不会覆盖第一个查询中会话中已有的实体数据(因为它可能已针对该工作单元进行了更改),因此返回的等级值将与第一个相同查询而不是第二个查询的实际排名。从第一个查询中删除结果避免此问题(但不是推荐的解决方案;请参阅下文)

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


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

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 结合使用该查询的相关排名。

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top