title: Repository Pattern and Local Caching
body: <p>I have the following interfaces/class:</p>
  
  <pre><code>public interface IUnitOfWork : IDisposable
  {
      event EventHandler&lt;EventArgs&gt; Saved;
      DbSet&lt;T&gt; Set&lt;T&gt;() where T : class;
      DbEntityEntry&lt;T&gt; Entry&lt;T&gt;(T entity) where T : class;
      void Commit();
  }
  </code></pre>
  
  <p>And an implementation of a repository:</p>
  
  <pre><code>public class CachedSqlRepository&lt;T, TKey, TContext&gt; : ICacheRepository&lt;T, TKey, TContext&gt;
      where T : class
      where TContext : DbContext, IDisposable, new()
  {
      //A list of the Navigation Properties to include
      private readonly Expression&lt;Func&lt;T, object&gt;&gt;[] _NavigationProperties;
  
      public CachedSqlRepository(params Expression&lt;Func&lt;T, object&gt;&gt;[] navigationProperties)
      {
          _NavigationProperties = navigationProperties;
          using (TContext dbContext = new TContext()) //Fetch the List of Entities
          {
              RefreshCache(dbContext);
          }
      }
      /// &lt;summary&gt;
      /// The Collection of Items in the database
      /// Note this is a Cache, but should replicate whats in the DB
      /// &lt;/summary&gt;
      public IList&lt;T&gt; Items { get; private set; }
  
      public bool Any(Func&lt;T, bool&gt; predicate)
      {
          return Items.Any(predicate);
      }
  
      public void RefreshCache(DbContext context)
      {
          switch (_NavigationProperties.Length)
          {
              case 0:
                  Items = context.Set&lt;T&gt;().ToList();
                  break;
              case 1:
                  Items = context.Set&lt;T&gt;().Include(_NavigationProperties[0]).ToList();
                  break;
             //more here
          }
      }
  
      /// &lt;summary&gt;
      /// Refresh the internal cache
      /// &lt;/summary&gt;
      public void RefreshCache()
      {
          using (TContext dbContext = new TContext())
          {
              RefreshCache(dbContext);
          }
      }
  
      public IEnumerable&lt;T&gt; FilterBy(Func&lt;T, bool&gt; predicate)
      {
          return Items.Where(predicate);
      }
  
      public T Add(T entity)
      {
          T newEntity;
          using (TContext dbContext = new TContext())
          {
              newEntity = dbContext.Set&lt;T&gt;().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&lt;T&gt;().Find(id);
              if (attachedEntry == null) return; //it doesnt exist anyway!
              dbContext.Set&lt;T&gt;().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&lt;T&gt;().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&lt;T&gt;().Add(entity);
      }
  
      public void TransactionDelete(TKey id, IUnitOfWork context)
      {
          var attachedEntry = context.Set&lt;T&gt;().Find(id);
          if (attachedEntry == null) return; //it doesnt exist anyway
          context.Saved += OnSave;
          context.Set&lt;T&gt;().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&lt;T&gt;().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
  }
  </code></pre>
  
  <p>It is adapted from various patterns on the net. I don't expect this to be useful for tables with hundreds of thousands of rows, but for lookup tables etc - I'm not always hitting the DB.</p>
  
  <p>It works, but some things aren't super clean, for example where I refresh the cache - sometimes I have to pull all the data again (currently a work in progress).</p>
  
  <p>Is this sound design? Or am I reinventing the wheel here?</p>