Кэширование объектов данных при использовании шаблона репозитория/службы и MVC

StackOverflow https://stackoverflow.com/questions/102913

Вопрос

У меня есть сайт на основе MVC, который использует шаблон репозитория/службы для доступа к данным.Службы созданы для использования в большинстве приложений (консольных, WinForm и веб-приложений).В настоящее время контроллеры напрямую общаются со службами.Это ограничило возможность применения надлежащего кэширования.

Свои варианты я вижу следующие:

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

Я вижу плюсы и минусы каждого.Какова наилучшая практика кэширования с помощью репозитория/службы?

Это было полезно?

Решение

Самый простой способ — управлять кешированием в вашем поставщике репозитория.Таким образом, вам не придется менять какой-либо код в остальной части вашего приложения;он не будет обращать внимания на тот факт, что данные были переданы из кэша, а не из репозитория.

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

Другие советы

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

Знакомство с шаблоном CachedRepository

Создание CachedRepository с помощью шаблона стратегии

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

public class CachedAlbumRepository : IAlbumRepository
{
    private readonly IAlbumRepository _albumRepository;

    public CachedAlbumRepository(IAlbumRepository albumRepository)
    {
        _albumRepository = albumRepository;
    }

    private static readonly object CacheLockObject = new object();

    public IEnumerable<Album> GetTopSellingAlbums(int count)
    {
        Debug.Print("CachedAlbumRepository:GetTopSellingAlbums");
        string cacheKey = "TopSellingAlbums-" + count;
        var result = HttpRuntime.Cache[cacheKey] as List<Album>;
        if (result == null)
        {
            lock (CacheLockObject)
            {
                result = HttpRuntime.Cache[cacheKey] as List<Album>;
                if (result == null)
                {
                    result = _albumRepository.GetTopSellingAlbums(count).ToList();
                    HttpRuntime.Cache.Insert(cacheKey, result, null, 
                        DateTime.Now.AddSeconds(60), TimeSpan.Zero);
                }
            }
        }
        return result;
    }
}

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

1.Интерфейс

public interface IRepository<T> : IRepository
    where T : class
{
    IQueryable<T> AllNoTracking { get; }

    IQueryable<T> All { get; }
    DbSet<T> GetSet { get; }

    T Get(int id);

    void Insert(T entity);
    void BulkInsert(IEnumerable<T> entities);
    void Delete(T entity);
    void RemoveRange(IEnumerable<T> range);
    void Update(T entity);
}

2.Обычный/некэшируемый репозиторий

public class Repository<T> : IRepository<T> where T : class, new()
{
    private readonly IEfDbContext _context;

    public Repository(IEfDbContext context)
    {
        _context = context;
    }

    public IQueryable<T> All => _context.Set<T>().AsQueryable();

    public IQueryable<T> AllNoTracking => _context.Set<T>().AsNoTracking();

    public IQueryable AllNoTrackingGeneric(Type t)
    {
        return _context.GetSet(t).AsNoTracking();
    }

    public DbSet<T> GetSet => _context.Set<T>();

    public DbSet GetSetNonGeneric(Type t)
    {
        return _context.GetSet(t);
    }

    public IQueryable AllNonGeneric(Type t)
    {
        return _context.GetSet(t);
    }

    public T Get(int id)
    {
        return _context.Set<T>().Find(id);
    }

    public void Delete(T entity)
    {
        if (_context.Entry(entity).State == EntityState.Detached)
            _context.Set<T>().Attach(entity);

        _context.Set<T>().Remove(entity);
    }

    public void RemoveRange(IEnumerable<T> range)
    {
        _context.Set<T>().RemoveRange(range);
    }

    public void Insert(T entity)
    {
        _context.Set<T>().Add(entity);
    }

    public void BulkInsert(IEnumerable<T> entities)
    {
        _context.BulkInsert(entities);
    }

    public void Update(T entity)
    {
        _context.Set<T>().Attach(entity);
        _context.Entry(entity).State = EntityState.Modified;
    }

}

3.Общий кэшированный репозиторий основан на некешированном

public interface ICachedRepository<T> where T : class, new()
{
    string CacheKey { get; }

    void InvalidateCache();
    void InsertIntoCache(T item);
}

public class CachedRepository<T> : ICachedRepository<T>, IRepository<T> where T : class, new()
{
    private readonly IRepository<T> _modelRepository;
    private static readonly object CacheLockObject = new object();

    private IList<T> ThreadSafeCacheAccessAction(Action<IList<T>> action = null)
    {
        // refresh cache if necessary
        var list = HttpRuntime.Cache[CacheKey] as IList<T>;
        if (list == null)
        {
            lock (CacheLockObject)
            {
                list = HttpRuntime.Cache[CacheKey] as IList<T>;
                if (list == null)
                {
                    list = _modelRepository.All.ToList();
                    //TODO: remove hardcoding
                    HttpRuntime.Cache.Insert(CacheKey, list, null, DateTime.UtcNow.AddMinutes(10), Cache.NoSlidingExpiration);
                }
            }
        }

        // execute custom action, if one is required
        if (action != null)
        {
            lock (CacheLockObject)
            {
                action(list);
            }
        }

        return list;
    }

    public IList<T> GetCachedItems()
    {
        IList<T> ret = ThreadSafeCacheAccessAction();
        return ret;
    }

    /// <summary>
    /// returns value without using cache, to allow Queryable usage
    /// </summary>
    public IQueryable<T> All => _modelRepository.All;

    public IQueryable<T> AllNoTracking
    {
        get
        {
            var cachedItems = GetCachedItems();
            return cachedItems.AsQueryable();
        }
    }

    // other methods come here
    public void BulkInsert(IEnumerable<T> entities)
    {
        var enumerable = entities as IList<T> ?? entities.ToList();
        _modelRepository.BulkInsert(enumerable);

        // also inserting items within the cache
        ThreadSafeCacheAccessAction((list) =>
        {
            foreach (var item in enumerable)
                list.Add(item);
        });
    }

    public void Delete(T entity)
    {
        _modelRepository.Delete(entity);

        ThreadSafeCacheAccessAction((list) =>
        {
            list.Remove(entity);
        });
    }
}

Используя инфраструктуру DI (я использую Ninject), можно легко определить, следует ли кэшировать репозиторий или нет:

// IRepository<T> should be solved using Repository<T>, by default
kernel.Bind(typeof(IRepository<>)).To(typeof(Repository<>));

// IRepository<T> must be solved to Repository<T>, if used in CachedRepository<T>
kernel.Bind(typeof(IRepository<>)).To(typeof(Repository<>)).WhenInjectedInto(typeof(CachedRepository<>));

 // explicit repositories using caching
 var cachedTypes = new List<Type>
 {
    typeof(ImportingSystem), typeof(ImportingSystemLoadInfo), typeof(Environment)
 };

 cachedTypes.ForEach(type =>
 {
    // allow access as normal repository
    kernel
       .Bind(typeof(IRepository<>).MakeGenericType(type))
       .To(typeof(CachedRepository<>).MakeGenericType(type));

     // allow access as a cached repository
     kernel
        .Bind(typeof(ICachedRepository<>).MakeGenericType(type))
        .To(typeof(CachedRepository<>).MakeGenericType(type));
  });

Итак, чтение из кешированных репозиториев осуществляется без знания о кешировании.Однако для их изменения требуется внедрить из ICacheRepository<> и вызов соответствующих методов.

Проверьте мою реализацию службы кэширования:
Как кэшировать данные в приложении MVC
(я не хочу повторять здесь ответ...)
Не стесняйтесь комментировать!

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top