Подтвердить что ты не робот

Как активировать кеш второго уровня в ленивом загруженном свойстве с собственным типом пользователя?

Введение:
В моем приложении я храню необработанные данные WAV в базе данных как byte[]. В моей модели домена есть класс PcmAudioStream, который представляет эти необработанные данные WAV. Я создал реализацию NHibernate IUserType для преобразования между моим классом и byte[].
Существует несколько классов, которые используют класс PcmAudioStream, все из которых сопоставляются с таблицами базы данных. Чтобы избежать загрузки всех данных WAV при извлечении строки из такой таблицы, я создал реализацию Fluent NHibernate IUserTypeConvention, которая указывает, что эти свойства всегда должны быть ленивыми. Все это работает как прелесть.

Вопрос:
Поскольку содержимое этих PcmAudioStream редко менялось, я хочу поместить извлеченные экземпляры в кеш второго уровня. Теперь я знаю, как активировать кеш второго уровня для полного класса, но как мне добиться этого только для ленивого загруженного свойства?


Соответствующая часть моей модели домена выглядит следующим образом:

public class User : Entity
{
    public virtual string FirstName { get; set; }
    public virtual string LastName { get; set; }
    public virtual PcmAudioStream FullNameRecording { get; set; }
    // ...
}

Отображение прост (обратите внимание: это не мое сопоставление, я использую соглашение, но оно эквивалентно):

public class UserMap : ClassMap<User>
{
    public UserMap()
    {
        Id(x => x.Id);
        Map(x => x.FirstName);
        Map(x => x.LastName);
        Map(x => x.FullNameRecording).CustomType<PcmAudioStreamAsByteArray>();
    }
}
4b9b3361

Ответ 1

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

public class Entity
{
    public virtual int Id { get; protected set; }
}

public class PcmAudioStream
{}

public class User : Entity
{
    private static readonly IDictionary<int, PcmAudioStream> _fullNameRecordingCache;

    private PcmAudioStream _fullNameRecording;

    static User()
    {
        _fullNameRecordingCache = new Dictionary<int, PcmAudioStream>();
    }

    public virtual string FirstName { get; set; }
    public virtual string LastName { get; set; }
    public virtual PcmAudioStream FullNameRecording
    {
        get
        {
            if (_fullNameRecordingCache.ContainsKey(Id))
            {
                return _fullNameRecordingCache[Id];
            }
            // May need to watch for proxies here
            _fullNameRecordingCache.Add(Id, _fullNameRecording);
            return _fullNameRecording;
        }
        set
        {
            if (_fullNameRecordingCache.ContainsKey(Id))
            {
                _fullNameRecordingCache[Id] = value;
            }
            _fullNameRecording = value;
        }
    }
    // ...
}

Mapping:

public class UserMap : ClassMap<User>
{
    public UserMap()
    {
        Id(x => x.Id);
        Map(x => x.FirstName);
        Map(x => x.LastName);
        Map(x => x.FullNameRecording).CustomType<PcmAudioStreamAsByteArray>()
            .Access.CamelCaseField(Prefix.Underscore);
    }
}

Отредактировано в ответ на комментарии:

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

Подумав об этом еще, я все еще думаю, что мое исходное решение (или вариант) - лучший вариант. Я понимаю (и разделяю) ваше стремление к чистой модели домена, но иногда требуются компромиссы, и мое решение не изменяет публичных членов модели или не требует каких-либо дополнительных ссылок. Другим оправданием является то, что объект первым узнал, что запись изменилась и нуждается в замене или добавлении в кеш.

Ответ 2

Я не уверен в кэшировании только одного свойства, но я предполагаю, что это не так, как строится инфраструктура кэширования NH. IMHO вы можете поместить целые экземпляры классов или результаты запросов в кеш второго уровня.

Но я попытаюсь набросать решение.

До NH 3 и поддержки ленивых свойств, если вы не хотите загружать весь объект из БД (и в этом случае это имеет смысл!), вам пришлось хранить такие "дорогие" данные в ссылка, ленивый загруженный стол. По крайней мере, как я это решил.

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

В отдельном примечании, похоже, проблема с кешированием и QueryOver в NH3 +: https://nhibernate.jira.com/browse/NH-2740