Влияние на кэширование NHibernate для результатов поиска с результатами, включая вычисленное значение, отображаемое в виде формулы (например, ранг)

При определении вычисляемого свойства с использованием формулы в 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) является надуманным и никогда не должен происходить на практике).

И, если есть последствия для кеширования, как я подозреваю, какие альтернативы могут существовать для аналогичного варианта использования, чтобы избежать потенциальных проблем?


person iammichael    schedule 26.10.2009    source источник
comment
Не ответ на ваш вопрос, но вы можете посмотреть NHibernate.Lucene. Это намного проще реализовать, и поиск выполняется так, как вы пытаетесь.   -  person Paco    schedule 27.10.2009
comment
Этот пример был приведен только для иллюстрации вопроса. На самом деле мы используем полнотекстовый поиск SQL Server, и если / когда мы столкнемся с ограничением функции или масштабируемости, Lucene окажется в верхней части списка для оценки.   -  person iammichael    schedule 27.10.2009
comment
Свойство Rank на самом деле не является постоянным значением сущности - это результат того, как сущность была возвращена в конкретном запросе, который был выполнен. ИМХО это неправильно иметь в своем доменном классе. И вы также можете получать запросы в рамках одного сеанса, мешающие друг другу (или загрязняющие друг друга, как вы это выразились).   -  person John Rayner    schedule 28.10.2009
comment
Разобрался - отсюда и вопрос. Не могли бы вы подробно рассказать о потенциальных проблемах в ответе (вместо комментария), возможно, даже предоставить альтернативные предложения по более эффективному способу получения таких данных? (Примечание: отредактированный вопрос, чтобы предоставить более подробную информацию о варианте использования)   -  person iammichael    schedule 28.10.2009


Ответы (1)


После дальнейших экспериментов: Да, есть последствия для кеширования, которые могут привести к противоречивым результатам; 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 и связанный с ним ранг для этого запроса.

Немного раздражает то, что вам приходится использовать два отдельных запроса, а не один, но, похоже, это единственный способ использовать кеши соответствующим образом.

person iammichael    schedule 25.01.2010