存储库模式和本地缓存

Sim*_*mon 5 c# entity-framework repository-pattern

我有以下接口/类:

public interface IUnitOfWork : IDisposable
{
    event EventHandler<EventArgs> Saved;
    DbSet<T> Set<T>() where T : class;
    DbEntityEntry<T> Entry<T>(T entity) where T : class;
    void Commit();
}
Run Code Online (Sandbox Code Playgroud)

以及存储库的实现:

public class CachedSqlRepository<T, TKey, TContext> : ICacheRepository<T, TKey, TContext>
    where T : class
    where TContext : DbContext, IDisposable, new()
{
    //A list of the Navigation Properties to include
    private readonly Expression<Func<T, object>>[] _NavigationProperties;

    public CachedSqlRepository(params Expression<Func<T, object>>[] navigationProperties)
    {
        _NavigationProperties = navigationProperties;
        using (TContext dbContext = new TContext()) //Fetch the List of Entities
        {
            RefreshCache(dbContext);
        }
    }
    /// <summary>
    /// The Collection of Items in the database
    /// Note this is a Cache, but should replicate whats in the DB
    /// </summary>
    public IList<T> Items { get; private set; }

    public bool Any(Func<T, bool> predicate)
    {
        return Items.Any(predicate);
    }

    public void RefreshCache(DbContext context)
    {
        switch (_NavigationProperties.Length)
        {
            case 0:
                Items = context.Set<T>().ToList();
                break;
            case 1:
                Items = context.Set<T>().Include(_NavigationProperties[0]).ToList();
                break;
           //more here
        }
    }

    /// <summary>
    /// Refresh the internal cache
    /// </summary>
    public void RefreshCache()
    {
        using (TContext dbContext = new TContext())
        {
            RefreshCache(dbContext);
        }
    }

    public IEnumerable<T> FilterBy(Func<T, bool> predicate)
    {
        return Items.Where(predicate);
    }

    public T Add(T entity)
    {
        T newEntity;
        using (TContext dbContext = new TContext())
        {
            newEntity = dbContext.Set<T>().Add(entity);
            if (dbContext.SaveChanges() == 1) //1 change was made
                Items.Add(newEntity);
        }
        return newEntity;
    }

    public void Delete(TKey id)
    {
        using (TContext dbContext = new TContext())
        {
            var attachedEntry = dbContext.Set<T>().Find(id);
            if (attachedEntry == null) return; //it doesnt exist anyway!
            dbContext.Set<T>().Remove(attachedEntry);
            dbContext.SaveChanges();
            RefreshCache(dbContext);
        }
    }

    public void Update(T entity, TKey id)
    {
        if (entity == null) throw new ArgumentException("Cannot update a null entity.");

        using (TContext dbContext = new TContext())
        {
            var entry = dbContext.Entry(entity);

            if (entry.State != EntityState.Detached) return;
            T attachedEntity = dbContext.Set<T>().Find(id);

            if (attachedEntity != null)
            {
                var attachedEntry = dbContext.Entry(attachedEntity);
                attachedEntry.CurrentValues.SetValues(entity);
            }
            else
            {
                entry.State = EntityState.Modified; // This should attach entity
            }
            dbContext.SaveChanges();
            RefreshCache(dbContext);
        }
    }

    #region Transaction Methods
    public IUnitOfWork StartTransaction()
    {
        return new EFUnitOfWork(new TContext());
    }

    public T TransactionAdd(T entity, IUnitOfWork context)
    {
        context.Saved += OnSave;
        return context.Set<T>().Add(entity);
    }

    public void TransactionDelete(TKey id, IUnitOfWork context)
    {
        var attachedEntry = context.Set<T>().Find(id);
        if (attachedEntry == null) return; //it doesnt exist anyway
        context.Saved += OnSave;
        context.Set<T>().Remove(attachedEntry);
    }

    public void TransactionUpdate(T entity, TKey id, IUnitOfWork context)
    {
        if (entity == null) throw new ArgumentException("Cannot update a null entity.");

        var entry = context.Entry(entity);

        if (entry.State != EntityState.Detached) return;
        T attachedEntity = context.Set<T>().Find(id);

        if (attachedEntity != null)
        {
            var attachedEntry = context.Entry(attachedEntity);
            attachedEntry.CurrentValues.SetValues(entity);
        }
        else
        {
            entry.State = EntityState.Modified; // This should attach entity
        }
        context.Saved += OnSave;
    }

    private void OnSave(object sender, EventArgs e)
    {
        RefreshCache();
    }
    #endregion
}
Run Code Online (Sandbox Code Playgroud)

它改编自网上的各种图案.我不认为这对于具有数十万行的表有用,但对于查找表等 - 我并不总是打到数据库.

它可以工作,但有些东西不是很干净,例如我刷新缓存 - 有时我必须再次拉取所有数据(目前正在进行中).

这个声音设计?还是我在这里重新发明轮子?

phi*_*ady 3

一个有趣的问题+1。在我看来,上下文内容缓存是最好正确完成或不进行处理的缓存。并使用数据库缓存。

为什么:

  • 并行 WP 都有缓存
  • 每个WP可能有线程,上下文不是线程安全的
  • 每个线程都应该有一个缓存吗?
  • 您的缓存会话是否持久?
    • 否:您重新加载每个请求
    • 是的:您在 ASP.NET、EnterpriseLibary 缓存或类似的缓存上使用全局缓存吗?
      • 您是否正确管理缓存?
      • 你如何处理并发和变化
  • 您是否考虑过 Context 生命周期的最佳实践?一些专家建议仅缩短使用寿命
  • DB 是否位于靠近 WebServer 的 LAN 上?
  • 您是否比较过使用数据库缓冲区访问时的响应时间?

在各种环境(而不仅仅是 EF/.NET/SQL Server)中研究了这个主题后,我得出的结论是,除非数据库服务器已经成为或趋于成为 CPU 瓶颈并且无法轻松扩展,否则它是这是一种非常合理的方法,可以在构建或尝试缓存条目之前向 DB 提供内存并让它缓存 100sMB。在 WebServer 上的应用程序结中编写自己的代码之前,我宁愿将 GB 或 RAM 扔到 SQL Server 上。

当每一微秒都很重要,或者您的数据库在网络跨度上分离并存在延迟/吞吐量问题,并且您的数据是非易失性的并且不需要缓存过期/并发管理时。然后继续实施缓存。

仔细考虑内存使用、缓存构建时间和内存持久性模型。

查看一些用于缓存想法和潜在解决方案的工具。例如企业缓存块。

祝你好运。