web-dev-qa-db-ja.com

リポジトリ/サービスパターンとMVCを使用する場合のデータオブジェクトのキャッシュ

データアクセスにリポジトリ/サービスパターンを使用しているMVCベースのサイトがあります。サービスは、大部分のアプリケーション(コンソール、Winform、およびWeb)で使用するように作成されています。現在、コントローラーはサービスと直接通信します。これにより、適切なキャッシュを適用する機能が制限されました。

私の選択肢は次のとおりです。

  • キャッシュを行うIWhatEverServiceを実装するWebアプリのラッパーを記述します。
  • 各アクションのViewDataをキャッシュすることにより、各コントローラーにキャッシュを適用します。
  • データキャッシングについて心配する必要はありません。各アクションにOutputCachingを実装するだけです。

それぞれの長所と短所を見ることができます。リポジトリ/サービスでキャッシュするためのベストプラクティスは何ですか?

41
LaptopHeaven

最も簡単な方法は、リポジトリプロバイダーでキャッシングを処理することです。そうすれば、アプリの残りの部分のコードを変更する必要はありません。データがリポジトリではなくキャッシュから提供されたという事実には気づかないでしょう。

そのため、コントローラーがバックエンドとの通信に使用するインターフェースを作成し、この実装でキャッシングロジックを追加します。すべてをDIで素敵な弓で包み込み、アプリを簡単にテストできるように設定します。

17
Will

Steve Smithは、彼のCachedRepositoryパターンを使用して目的の結果を得る方法を示す2つの優れたブログ投稿を行いました。

CachedRepositoryパターンの紹介

戦略パターンによるCachedRepositoryの構築

これらの2つの投稿で、彼はこのパターンを設定する方法を示し、なぜそれが役立つのかを説明します。このパターンを使用することにより、既存のコードがキャッシュロジックを一切見なくても、キャッシュを取得できます。基本的に、キャッシュされたリポジトリを他のリポジトリと同じように使用します。

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;
    }
}
34
Brendan Enrick

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<>から挿入し、適切なメソッドを呼び出す必要があります。

4
Alexei

キャッシングサービスの実装を確認します。
MVCアプリケーションでデータをキャッシュする方法
(ここで答えを繰り返したくありません...)
自由にコメントしてください!

2
Hrvoje Hudo