对 NHibernate 缓存搜索的影响,其结果包括映射为公式的计算值(例如排名)

发布于 2024-08-09 04:25:57 字数 1656 浏览 2 评论 0 原文

当在 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) 调用是人为的,在实践中永远不应该发生)。

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

When defining a calculated property using a formula in NHibernate, what are the implications for when the formula varies its result depending on the query restrictions, especially with regards to query caching?

More specifically, consider the following simple C# class:

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; }
}

Mapped with the following simple NHibernate mapping:

<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>

Running with a session factory with hibernate.cache.use_query_cache option set to true, and queried in the following ways:

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);

Will NHibernate behave in a reasonable manner for the returned Entities? Or could the "Rank" value from one cached query pollute the Rank value of another query due to the query cache? Are there any other concerns when using such a formula in NHibernate mappings?

EDIT:

It might also be worth noting that in my particular case, "Entity" is not a first-class business entity, but sort of a meta-entity. It maps to an indexed database view over other first-class entities and is used exclusively for searching (the session.Load(id) call is contrived and should never actually happen in practice).

And, if there are implications to caching, as I suspect, what alternatives might exist for a similar use-case to avoid potential problems?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

苍白女子 2024-08-16 04:25:57

经过进一步的实验:是的,存在缓存影响,可能会导致结果不一致; 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 与该查询的相关排名。

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

After further experimentation: Yes, there are cache implications that could result in inconsistent results; NHibernate cannot automatically know that the formula could change values between queries for entity results with the same identifier (and assumes it won't).

Having a class mapping as those in the question would result in the rank being stored with the rest of the entity data. This makes it possible that a subsequent query will end up returning a rank value from some other query rather than the query being run and thus have ranks that are not sequential as expected.

NHibernate has separate query and entity caches (there are actually two entity caches - the session cache and the second level entity cache) and the impacts depend on which ones are being used.

When the query cache is not enabled, incorrect rank values can be received if you make
two different queries within the same session that share a result but with different ranks. In this case, the second query of the same session will not override the entity data already in the session from the first query (since it might have changed for that unit of work), so the rank value returned will be the same from the first query rather than the actual rank from the second query. Evicting the results from the first query should avoid this issue (but is not the recommended solution; see below)

When the query cache is enabled, incorrect rank values can also be received when repeating the same query after some other query has executed that had a result with a different rank. In this case, the first query execution adds the result identifiers to the query cache and the entities (with their rank) to the entity cache. The interleaved query (when run in another session) could result in a change to the rank value stored with the entity in the entity cache. When the first query is re-executed, the cached identifiers are used to lookup the cached entities (with the changed ranks).


The problem can be addressed completely by changing the entity to only include the persisted values for the entity (i.e. excluding the rank). Then, for the query, use a projection to extract the identifier and the rank for that query:

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 }));

In this case, since the rank is a value type, the query cache will store the rank along side the query result identifiers for that specific query. Then, using a second query, lookup the entity values using the projected identifiers. The tricky part is that you'll want to avoid an N+1 type issue when performing the entity query and you will need to create another data structure to marry the Entity and its associated rank for that query.

It's a little annoying that you have to use two separate queries rather than a single query, but this seems to be the only way to use the caches in an appropriate manner.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文