문제

데이터 액세스를 위해 저장소/서비스 패턴을 사용하는 MVC 기반 사이트가 있습니다.서비스는 대부분의 애플리케이션(콘솔, winform 및 웹)에서 사용하도록 작성되었습니다.현재 컨트롤러는 서비스와 직접 통신합니다.이로 인해 적절한 캐싱을 적용하는 기능이 제한되었습니다.

내 옵션은 다음과 같습니다.

  • 캐싱을 수행하는 IWhatEverService를 구현하는 웹 앱용 래퍼를 작성합니다.
  • 각 작업에 대한 ViewData를 캐시하여 각 컨트롤러에 캐싱을 적용합니다.
  • 데이터 캐싱에 대해 걱정하지 말고 각 작업에 대해 OutputCaching을 구현하세요.

각각의 장단점을 볼 수 있어요.리포지토리/서비스를 사용한 캐싱에 대한 모범 사례는 무엇입니까?

도움이 되었습니까?

해결책

가장 쉬운 방법은 저장소 공급자에서 캐싱을 처리하는 것입니다.이렇게 하면 앱의 나머지 부분에서 코드를 변경할 필요가 없습니다.데이터가 저장소가 아닌 캐시에서 제공되었다는 사실을 인식하지 못할 것입니다.

따라서 컨트롤러가 백엔드와 통신하는 데 사용하는 인터페이스를 만들고 이를 구현하면서 캐싱 논리를 추가했습니다.DI를 사용하여 모든 것을 멋지게 마무리하면 앱을 쉽게 테스트할 수 있도록 설정됩니다.

다른 팁

Steve Smith는 CachedRepository 패턴을 사용하여 원하는 결과를 얻는 방법을 보여주는 두 개의 훌륭한 블로그 게시물을 작성했습니다.

CachedRepository 패턴 소개

전략 패턴을 통해 CachedRepository 구축

이 두 게시물에서 그는 이 패턴을 설정하는 방법과 이것이 유용한 이유를 설명합니다.이 패턴을 사용하면 기존 코드에서 캐싱 논리를 확인하지 않고도 캐싱을 수행할 수 있습니다.기본적으로 캐시된 저장소를 다른 저장소인 것처럼 사용합니다.

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.인터페이스

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

}

삼.일반 캐시 저장소 캐시되지 않은 것을 기반으로 합니다.

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<> 그리고 적절한 메서드를 호출합니다.

캐싱 서비스 구현을 확인하십시오.
MVC 애플리케이션에서 데이터를 캐시하는 방법
(여기서 답변을 반복하고 싶지 않습니다...)
자유롭게 댓글을 달아주세요!

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top