使用存储库/服务模式和 MVC 时缓存数据对象

Posted

技术标签:

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

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

我的选择如下:

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

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

【问题讨论】:

【参考方案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;
    

【讨论】:

感谢分享,这是史蒂夫·史密斯关于这个主题的最后一篇博文,使用网络核心:Building a CachedRepository in ASPNET Core【参考方案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; 注入并调用适当的方法。

【讨论】:

干得好!由于通用方法而投票赞成:)

以上是关于使用存储库/服务模式和 MVC 时缓存数据对象的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 4 使用存储库模式(由 Eloquent 支持)和缓存的可扩展设计

游戏设计模式——面向数据编程(新)

使用 EmberData 在本地存储中缓存远程数据

缓存策略

实现缓存模式的好方法是什么?

ASP.NET MVC 中的数据缓存存储