2008-09-19 5 views
37

Ich habe eine MVC-basierte Site, die ein Repository/Service-Muster für den Datenzugriff verwendet. Die Dienste sind so geschrieben, dass sie in den meisten Anwendungen (Konsole, Winform und Web) verwendet werden. Derzeit kommunizieren die Controller direkt mit den Diensten. Dies hat die Möglichkeit eingeschränkt, ordnungsgemäßes Caching anzuwenden.Zwischenspeichern von Datenobjekten bei Verwendung von Repository/Service Pattern und MVC

Ich sehe meine Optionen wie die folgenden:

  • einen Wrapper für die Web-App schreiben, die die IWhatEverService implementiert, das Caching der Fall ist.
  • Wenden Sie das Zwischenspeichern in jedem Controller an, indem Sie die ViewData für jede Aktion zwischenspeichern.
  • Machen Sie sich keine Sorgen über Daten-Caching und implementieren Sie nur OutputCaching für jede Aktion.

Ich kann die Vor- und Nachteile von jedem sehen. Was ist/sollte die beste Vorgehensweise für das Caching mit dem Repository/Service sein?

Antwort

15

Der einfachste Weg wäre, das Caching in Ihrem Repository-Provider zu handhaben. Auf diese Weise müssen Sie im Rest Ihrer App keinen Code ändern. Die Tatsache, dass die Daten nicht aus dem Repository, sondern aus einem Cache stammen, wird nicht beachtet.

Also würde ich eine Schnittstelle erstellen, die die Controller verwenden, um mit dem Back-End zu kommunizieren, und in der Implementierung von diesem würde ich die Caching-Logik hinzufügen. Wickeln Sie alles mit ein paar DI in eine schöne Verbeugung und Ihre App wird zum einfachen Testen eingestellt.

+1

Wie kann ich dies tun und die Repositories und Services unabhängig halten? Ich möchte das R/S für andere non-web Anwendungen wiederverwenden. – LaptopHeaven

+2

Hmmm .... Ich sehe hier kein Problem. Sie können ein Standard-Repository oder ein CacheableRepository erstellen, das eine ICache-Abhängigkeit (was immer auch immer sein mag) verwendet. Nur weil Sie "caching" bedeutet nicht, dass Sie httpcache – Will

+1

verwenden müssen Sie können eine ICache implementieren, die den ASP.NET-Cache (der übrigens in einer Winforms oder einer anderen App verwendet werden kann) verwendet, oder die verwendet Den Cache der P & P-Gruppe oder Ihre eigene Cache-Implementierung. – Will

34

Steve Smith hat zwei großartige Blogbeiträge verfasst, die zeigen, wie man sein CachedRepository-Muster verwendet, um das gewünschte Ergebnis zu erzielen.

Introducing the CachedRepository Pattern

Building a CachedRepository via Strategy Pattern

In diesen beiden Posten er zeigt Ihnen, wie dieses Muster einzurichten und erklärt auch, warum es sinnvoll ist. Wenn Sie dieses Muster verwenden, erhalten Sie Caching, ohne dass der vorhandene Code Caching-Logik erkennt. Im Wesentlichen verwenden Sie das zwischengespeicherte Repository wie jedes andere Repository.

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

Sehr schöne Artikel! – Flappy

4

Basierend auf Antwort von Brendan vorgesehen ist, definiert ich eine generische gecached Repository für der Sonderfall relativ kleine Listen, die selten geändert werden, aber lesen Sie stark.

1. Die Schnittstelle

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. Normale/Repository nicht zwischengespeicherten

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.Generisches gecached Repository basiert auf nicht-zwischengespeichert ein

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

ein DI-Framework (ich benutze Ninject), kann man sich leicht definieren, wenn ein Repository im Cache gespeichert werden soll oder nicht:

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

So Das Lesen aus zwischengespeicherten Repositories erfolgt ohne Wissen über das Caching. Um sie zu ändern, müssen Sie jedoch von ICacheRepository<> injizieren und die entsprechenden Methoden aufrufen.

+0

Gute Arbeit! wegen des generischen Ansatzes abgestimmt :) –

Verwandte Themen