2013-07-25 18 views
5

mam następujących interfejsów/klasa:Repository Wzór i lokalne buforowanie

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

i wdrożenia repozytorium:

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 
} 

jest przystosowany z różnych wzorów w internecie. Nie oczekuję, że będzie to użyteczne dla tabel z setkami tysięcy wierszy, ale dla tabel odnośników itp. - nie zawsze trafiam do DB.

Działa, ale niektóre rzeczy nie są super czyste, na przykład gdy odświeżam pamięć podręczną - czasami muszę ponownie pobrać wszystkie dane (obecnie trwają prace).

Czy to jest dźwięk? A może wymyślam tutaj koło?

+2

Entity Framework już w pełni implementuje schematy repozytorium i jednostki pracy. Zobacz odpowiedź na to pytanie: http://stackoverflow.com/questions/5488313/organizationally-where-ould--put-common-queries-when-using-entity- frame (również na to pytanie: http: // stackoverflow.com/questions/5762846/is-unitofwork-and-genericrepository-pattern-redundant-in-ef-4-1-code-first). Wykorzystaj wiązania Linq EF do tego, do czego są przeznaczone. Zawijanie ich to strata czasu, IMO :) –

+1

(Ta rada oczywiście nie dotyczy buforowania, ale moim zdaniem jest to, że każdy mechanizm buforowania, który utworzysz powinien działać z istniejącym interfejsem EF, zamiast ukrywać go za kiepskim). –

+0

Dzięki Merlyn, chcieliśmy po prostu uogólnić metody CRUD, które pierwotnie mieliśmy, więc nie używamy go w prawdziwym znaczeniu repozytorium. dobre linki choć – Simon

Odpowiedz

3

Interesujące pytanie +1. Moim zdaniem buforowanie zawartości kontekstu jest najlepiej zrobione poprawnie lub pozostawione w spokoju. I użyj buforowania bazy danych.

Dlaczego:

  • Równoległe WP wszystkie mają cache
  • Każdy WP mogą mieć wątki, kontekst nie jest bezpieczeństwo wątków
  • Gdyby każdy wątek posiada pamięć podręczną?
  • Czy sesja pamięci podręcznej jest trwała?
    • Nie: przeładować każdy wniosek
    • Tak: użyć buforowania globalnej pamięci podręcznej na ASP.NET, EnterpriseLibary lub podobne?
      • Czy zarządzasz pamięcią podręczną poprawnie?
      • Jak radzić sobie z współbieżności i zmienia
  • Czy za najlepsze praktyki wokół życia kontekście? Niektórzy eksperci sugerują krótką żywotność tylko
  • Czy DB znajduje się w sieci LAN w pobliżu WebServer?
  • Czy porównywałeś czasy reakcji podczas korzystania z dostępu do bufora DB?

Po przejrzeniu tego tematu w różnych środowiskach, nie tylko w systemie EF/.NET/SQL Server, doszedłem do wniosku, że dopóki serwer DB nie stanie się lub nie będzie wąskim gardłem procesora i nie może Łatwo skalowalne, jest to bardzo rozsądne podejście do dostarczania pamięci do DB i niech buforuje 100sMB przed budowaniem lub próbowaniem wpisów w pamięci podręcznej. Wolałbym rzucić GB lub RAM na SQL Server przed kodowaniem się w węzłach aplikacji na WebServer.

Kiedy liczy się każda mikrosekunda, lub twój DB jest rozdzielony w zakresie sieci z problemami z opóźnieniem/przepustowością, a twoje dane nie są niestabilne i nie wymagają zarządzania wygaśnięciem/współbieżnością pamięci podręcznej. Następnie należy wdrożyć buforowanie.

Uważnie przeanalizuj użycie pamięci, czas budowy pamięci podręcznej i model trwałości pamięci.

Zobacz niektóre narzędzia do buforowania pomysłów i potencjalnych rozwiązań. na przykład Blok buforowania korporacyjnego.

powodzenia.

+0

Dzięki, dobre informacje. Kiedy to zaimplementowałem, moja aplikacja wydawała się bardziej responsywna, ale masz rację - sprzęt, którego używamy, nie jest najnowocześniejszy. – Simon

+0

Przy super ogólnej Bazie Respository jest nadal dobry wzór. To tylko pseudo-cache, które wygląda trochę podejrzanie. –

+0

Tak, zgadzam się, jest to w połowie wykonane. Zobaczę, dokąd mnie to zaprowadzi, zastanawiałem się tylko, czy są jakieś gotowe implementacje – Simon