Caching Data Objects when using Repository/Service Pattern and MVC

前端 未结 4 1984
囚心锁ツ
囚心锁ツ 2020-12-12 19:03

I have an MVC-based site, which is using a Repository/Service pattern for data access. The Services are written to be using in a majority of applications (console, winform,

4条回答
  •  刺人心
    刺人心 (楼主)
    2020-12-12 19:43

    Based on answer provided by Brendan, I defined a generic cached repository for the special case of relatively small lists that are rarely changed, but heavily read.

    1. The interface

    public interface IRepository : IRepository
        where T : class
    {
        IQueryable AllNoTracking { get; }
    
        IQueryable All { get; }
        DbSet GetSet { get; }
    
        T Get(int id);
    
        void Insert(T entity);
        void BulkInsert(IEnumerable entities);
        void Delete(T entity);
        void RemoveRange(IEnumerable range);
        void Update(T entity);
    }
    

    2. Normal/non-cached repository

    public class Repository : IRepository where T : class, new()
    {
        private readonly IEfDbContext _context;
    
        public Repository(IEfDbContext context)
        {
            _context = context;
        }
    
        public IQueryable All => _context.Set().AsQueryable();
    
        public IQueryable AllNoTracking => _context.Set().AsNoTracking();
    
        public IQueryable AllNoTrackingGeneric(Type t)
        {
            return _context.GetSet(t).AsNoTracking();
        }
    
        public DbSet GetSet => _context.Set();
    
        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().Find(id);
        }
    
        public void Delete(T entity)
        {
            if (_context.Entry(entity).State == EntityState.Detached)
                _context.Set().Attach(entity);
    
            _context.Set().Remove(entity);
        }
    
        public void RemoveRange(IEnumerable range)
        {
            _context.Set().RemoveRange(range);
        }
    
        public void Insert(T entity)
        {
            _context.Set().Add(entity);
        }
    
        public void BulkInsert(IEnumerable entities)
        {
            _context.BulkInsert(entities);
        }
    
        public void Update(T entity)
        {
            _context.Set().Attach(entity);
            _context.Entry(entity).State = EntityState.Modified;
        }
    

    }

    3. Generic cached repository is based on non-cached one

    public interface ICachedRepository where T : class, new()
    {
        string CacheKey { get; }
    
        void InvalidateCache();
        void InsertIntoCache(T item);
    }
    
    public class CachedRepository : ICachedRepository, IRepository where T : class, new()
    {
        private readonly IRepository _modelRepository;
        private static readonly object CacheLockObject = new object();
    
        private IList ThreadSafeCacheAccessAction(Action> action = null)
        {
            // refresh cache if necessary
            var list = HttpRuntime.Cache[CacheKey] as IList;
            if (list == null)
            {
                lock (CacheLockObject)
                {
                    list = HttpRuntime.Cache[CacheKey] as IList;
                    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 GetCachedItems()
        {
            IList ret = ThreadSafeCacheAccessAction();
            return ret;
        }
    
        /// 
        /// returns value without using cache, to allow Queryable usage
        /// 
        public IQueryable All => _modelRepository.All;
    
        public IQueryable AllNoTracking
        {
            get
            {
                var cachedItems = GetCachedItems();
                return cachedItems.AsQueryable();
            }
        }
    
        // other methods come here
        public void BulkInsert(IEnumerable entities)
        {
            var enumerable = entities as IList ?? 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);
            });
        }
    }
    

    Using a DI framework (I am using Ninject), one can easily define if a repository should be cached or not:

    // IRepository should be solved using Repository, by default
    kernel.Bind(typeof(IRepository<>)).To(typeof(Repository<>));
    
    // IRepository must be solved to Repository, if used in CachedRepository
    kernel.Bind(typeof(IRepository<>)).To(typeof(Repository<>)).WhenInjectedInto(typeof(CachedRepository<>));
    
     // explicit repositories using caching
     var cachedTypes = new List
     {
        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));
      });
    

    So, reading from cached repositories is done without knowing about the caching. However, changing them requires to inject from ICacheRepository<> and calling the appropriate methods.

提交回复
热议问题