【问题标题】:Caching Data Objects when using Repository/Service Pattern and MVC使用存储库/服务模式和 MVC 时缓存数据对象
【发布时间】:2010-09-11 07:14:24
【问题描述】:

我有一个基于 MVC 的站点,它使用存储库/服务模式进行数据访问。 服务被编写用于大多数应用程序(控制台、winform 和 Web)。目前,控制器直接与服务通信。这限制了应用适当缓存的能力。

我的选择如下:

  • 为 Web 应用程序编写一个包装器,该应用程序实现了执行缓存的 IWhatEverService。
  • 通过缓存每个 Action 的 ViewData 在每个控制器中应用缓存。
  • 不用担心数据缓存,只需为每个操作实现 OutputCaching。

我可以看到每种方法的优缺点。使用 Repository/Service 进行缓存的最佳实践是/应该是什么

【问题讨论】:

    标签: asp.net-mvc caching design-patterns


    【解决方案1】:

    Steve Smith 写了两篇很棒的博文,展示了如何使用他的 CachedRepository 模式来实现您正在寻找的结果。

    Introducing the CachedRepository Pattern

    Building a CachedRepository via Strategy Pattern

    在这两篇文章中,他向您展示了如何设置此模式,并解释了它为何有用。通过使用这种模式,您可以获得缓存,而您现有的代码不会看到任何缓存逻辑。本质上,您使用缓存的存储库就像它是任何其他存储库一样。

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

    【讨论】:

    【解决方案2】:

    最简单的方法是在存储库提供程序中处理缓存。这样您就不必在应用程序的其余部分更改任何代码;它将忘记数据是从缓存而不是存储库中提供的这一事实。

    因此,我将创建一个控制器用来与后端通信的接口,并在实现中添加缓存逻辑。用一些 DI 将所有这些都用一个漂亮的蝴蝶结包装起来,您的应用将设置为易于测试。

    【讨论】:

    • 我怎样才能做到这一点并保持存储库和服务不可知?我想将 R/S 重用于其他非 Web 应用程序。
    • 嗯....我在这里没有看到问题。您可以创建一个标准的存储库或一个采用 ICache 依赖项(无论可能是什么)的 CacheableRepository。仅仅因为你在“缓存”并不意味着你必须使用 httpcache
    • 您可以实现一个使用 ASP.NET 缓存的 ICache(顺便说一句,可以在 winforms 或任何其他应用程序中使用),或者使用 P&P 组的缓存,或者您自己的缓存实现.
    【解决方案3】:

    根据Brendan 提供的答案,我为很少更改但大量阅读的相对较小列表的特殊情况定义了一个通用缓存存储库

    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&lt;&gt; 注入并调用适当的方法。

    【讨论】:

    • 干得好!由于通用方法而投票赞成:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-05-07
    • 1970-01-01
    • 2017-09-19
    • 2011-10-08
    • 1970-01-01
    • 2015-09-01
    • 1970-01-01
    相关资源
    最近更新 更多