Кэширование объектов данных при использовании Repository / Service Pattern и MVC - PullRequest
40 голосов
/ 19 сентября 2008

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

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

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

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

Ответы [ 4 ]

34 голосов
/ 04 июня 2011

Стив Смит сделал два отличных сообщения в блоге, которые демонстрируют, как использовать его шаблон 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;
    }
}
17 голосов
/ 19 сентября 2008

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

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

4 голосов
/ 19 августа 2016

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

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<> и вызвать соответствующие методы.

2 голосов
/ 08 декабря 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...