net mvc idistributedcache control cache asp asp.net-mvc caching design-patterns

asp.net mvc - idistributedcache - Almacenamiento en memoria caché de objetos de datos cuando se utiliza el repositorio/patrón de servicio y MVC



net core cache (4)

Tengo un sitio basado en MVC, que está usando un patrón de repositorio / servicio para el acceso a los datos. Los Servicios están escritos para ser utilizados en la mayoría de las aplicaciones (consola, winform y web). Actualmente, los controladores se comunican directamente con los servicios. Esto ha limitado la capacidad de aplicar el almacenamiento en caché adecuado.

Veo mis opciones como las siguientes:

  • Escriba un contenedor para la aplicación web, que implementa el IWhatEverService que hace el almacenamiento en caché.
  • Aplique el almacenamiento en caché en cada controlador guardando en caché ViewData para cada Acción.
  • No se preocupe por el almacenamiento en caché de datos y simplemente implemente OutputCaching para cada Acción.

Puedo ver los pros y los contras de cada uno. Cuál es / debería ser la mejor práctica para el almacenamiento en caché con Repository / Service


Con base en la respuesta proporcionada por , definí un repositorio en caché genérico para el caso especial de listas relativamente pequeñas que rara vez se modifican, pero que son muy leídas .

1. La interfaz

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. Repositorio normal / no almacenado en caché

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. El repositorio en caché genérico se basa en uno no almacenado en caché

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

Usando un marco DI (estoy usando Ninject), uno puede definir fácilmente si un repositorio debe ser almacenado en caché o no:

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

Por lo tanto, la lectura de repositorios almacenados en caché se realiza sin conocer el almacenamiento en caché. Sin embargo, cambiarlos requiere inyectar desde ICacheRepository<> y llamar a los métodos apropiados.


La forma más fácil sería manejar el almacenamiento en caché en su proveedor de repositorio. De esta forma, no tiene que cambiar ningún código en el resto de su aplicación; no tendrá en cuenta el hecho de que los datos fueron servidos desde un caché en lugar del repositorio.

Entonces, crearía una interfaz que los controladores usarían para comunicarme con el backend, y en la implementación de esto agregaría la lógica de almacenamiento en caché. Envuélvelo todo en una buena reverencia con algo de DI, y tu aplicación estará configurada para una prueba fácil.


Steve Smith hizo dos excelentes publicaciones en el blog que demuestran cómo usar su patrón CachedRepository para lograr el resultado que está buscando.

Presentamos el patrón CachedRepository

Construyendo un CachedRepository a través del Patrón de Estrategia

En estas dos publicaciones, él le muestra cómo configurar este patrón y también explica por qué es útil. Al usar este patrón, obtiene el almacenamiento en caché sin que su código existente vea la lógica de almacenamiento en caché. Básicamente, utiliza el repositorio en caché como si fuera cualquier otro repositorio.

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