Dans mon article précédent nous avons vu comment mettre en place le Pattern Repository. Aujourd’hui nous allons voir comment améliorer les performances et implémenter une surcouche de mise en Cache au dessus de cette Repository Generique.

Pour cela nous allons commencer par modifier l’interface IRepository<T> en rajoutant quelques méthodes qui nous permettrons d’obtenir les informations d’état de l’entité.
public interface IRepository<T> : IDisposable where T : class
{
IQueryable<T> Fetch();
IEnumerable<T> GetAll();
IEnumerable<T> Find(Func<T, bool> predicate);
T Single(Func<T, bool> predicate);
T First(Func<T, bool> predicate);
void Add(T entity);
void Delete(T entity);
void Attach(T entity);
void SaveChanges();
void SaveChanges(SaveOptions options);
/// <summary>
/// Get Object State Entries
/// </summary>
/// <param name="state"></param>
/// <returns></returns>
IEnumerable<ObjectStateEntry> GetObjectStateEntries(EntityState state);
/// <summary>
/// Get Object State Entry
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
ObjectStateEntry GetObjectStateEntry(T entity);
}
Nous allons ensuite implémenter ces méthodes dans notre Repository Generique en utilisant l’ObjectStateManager.
public IEnumerable<ObjectStateEntry> GetObjectStateEntries(EntityState state)
{
return _context.ObjectStateManager.GetObjectStateEntries(state);
}
public ObjectStateEntry GetObjectStateEntry(T entity)
{
return _context.ObjectStateManager.GetObjectStateEntry(entity);
}
Pour l’implémentation du Cache nous allons utiliser une interface ICache qui nous permettra ensuite d’implémenter nos providers de cache.
public interface ICache
{
int Count { get; }
void Clear();
bool Contains(string key);
T Get<T>(string key);
void Set<T>(string key, T value);
void Remove(string key);
IEnumerable<T> GetAll<T>();
}
Pour les besoins de l’article j’ai crée un provider de cache SimpleCache qui stock juste les objets dans un dictionnaire. Par la suite vous pourrez implémenter votre propre provider de cache pour utiliser System.Web.Caching (ASP.NET), Enterprise Library Caching Block ou autres.
public class SimpleCache : ICache
{
private static IDictionary<string, object> CachedObjects { get; set; }
public SimpleCache()
{
CachedObjects = new Dictionary<string, object>();
}
#region ICache Members
public void Clear()
{
CachedObjects.Clear();
}
public bool Contains(string key)
{
return CachedObjects.ContainsKey(key);
}
public T Get<T>(string key)
{
object o;
CachedObjects.TryGetValue(key, out o);
return (T)o;
}
public void Set<T>(string key, T value)
{
if (Contains(key))
Remove(key);
CachedObjects.Add(key, value);
}
public void Remove(string key)
{
if (!Contains(key))
return;
CachedObjects.Remove(key);
}
public IEnumerable<T> GetAll<T>()
{
return CachedObjects.Select(p => p.Value).Cast<T>().AsEnumerable<T>();
}
public int Count
{
get { return CachedObjects.Count; }
}
#endregion
}
Maintenant passons aux choses sérieuses :) L’implémentation de notre surcouche a Repository<T> utilisant le Cache.
Pour ce faire nous allons créer une CachingRepository<T> qui implémente IRepository<T>, cette classe prendra en paramètre de constructeur une instance de Repository<T> afin de bénéficier de sa logique de traitement interne ainsi qu’un ICache qui sera notre instance de provider de Cache.
public class CachingRepository<T> : IRepository<T> where T : class
{
private IRepository<T> _innerRepository;
private static ICache _cache;
public CachingRepository(IRepository<T> innerRepository)
: this(innerRepository, new SimpleCache())
{
}
public CachingRepository(IRepository<T> innerRepository, ICache cache)
{
_innerRepository = innerRepository;
_cache = cache;
}
}
L’idée maintenant est de maintenir le cache a jours lors des appels aux méthodes GetAll(), First, Single mais également lors des Add(), Delete() et Updates.
Toute la logique de mise en cache va être fais lors de l’appel a SaveChanges, puisque c’est cette méthode qui va persister les données sur la base de données. C’est donc ici que nous allons faire appel a nos méthodes GetObjectStateEntries que nous avons déclaré un peu plus haut dans l’interface IRepository afin de récuperer l’état de nos entités (Added, Modified, Deleted, Unchanged) via l’énumeration EntityState.
public void SaveChanges()
{
SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
}
public void SaveChanges(SaveOptions options)
{
// Get Entities State
var addedEntries = GetObjectStateEntries(EntityState.Added);
var modifiedEntries = GetObjectStateEntries(EntityState.Modified);
var deletedEntries = GetObjectStateEntries(EntityState.Deleted);
// We have to get cache item before SaveChanges applies to database for Deleted Entities since
// there status will not be available after this.
var cacheItemsToDelete = GetCacheItems(deletedEntries);
// Persist to Database
_innerRepository.SaveChanges();
// Perform Cache Updates
var cacheItemsToAdd = GetCacheItems(addedEntries);
var cacheItemsToUpdate = GetCacheItems(modifiedEntries);
CacheItems(cacheItemsToAdd, EntityState.Added);
CacheItems(cacheItemsToUpdate, EntityState.Modified);
CacheItems(cacheItemsToDelete, EntityState.Deleted);
}
#region Caching Methods
/// <summary>
/// Get Unique Cache Key based on EntityKey
/// </summary>
/// <param name="entityKey"></param>
/// <returns></returns>
private string GetCacheKey(EntityKey entityKey)
{
if (entityKey == null)
throw new ArgumentNullException("Entity cannot be null");
if (entityKey.EntityKeyValues == null)
return string.Empty;
var cacheKey = new StringBuilder();
cacheKey.AppendFormat("{0}:", typeof(T).Name);
foreach (var keyValue in entityKey.EntityKeyValues)
{
cacheKey.AppendFormat("{0}", keyValue);
}
return cacheKey.ToString();
}
/// <summary>
/// Cache Entities
/// </summary>
/// <param name="entities"></param>
private void CacheEntities(IEnumerable<T> entities)
{
var entries = new List<ObjectStateEntry>();
foreach (var entity in entities)
{
var entry = GetObjectStateEntry(entity);
entries.Add(entry);
}
var cacheItems = GetCacheItems(entries);
CacheItems(cacheItems, EntityState.Unchanged);
}
/// <summary>
/// Perform Cache Update
/// </summary>
/// <param name="key"></param>
/// <param name="entity"></param>
/// <param name="state"></param>
private void CacheItem(string key, T entity, EntityState state)
{
switch (state)
{
case EntityState.Added:
_cache.Set(key, entity);
break;
case EntityState.Modified:
_cache.Set(key, entity);
break;
case EntityState.Deleted:
_cache.Remove(key);
break;
default:
_cache.Set(key, entity);
break;
}
}
/// <summary>
/// Perform Cache Updates
/// </summary>
/// <param name="items"></param>
/// <param name="state"></param>
private void CacheItems(IDictionary<string, T> items, EntityState state)
{
foreach (var item in items)
{
CacheItem(item.Key, item.Value, state);
}
}
/// <summary>
/// Get Cache Items
/// </summary>
/// <param name="entries"></param>
/// <param name="state"></param>
/// <returns></returns>
private IDictionary<string, T> GetCacheItems(IEnumerable<ObjectStateEntry> entries)
{
var items = new Dictionary<string, T>();
foreach (var entry in entries)
{
var cacheKey = GetCacheKey(entry.EntityKey);
items.Add(cacheKey, (T)entry.Entity);
}
return items;
}
#endregion
Et voila maintenant nous pouvons continuer a utiliser notre repository normalement sans avoir a ce soucier de la façon dont les données sont mises en cache.
Ce n’est qu’un début d’implémentation de cache avec Entity Framework 4, qui peut très certainement être amélioré mais c’est un début.
Code source de l’article :
db5c221f-bd80-472b-8c39-28920c2dcc11|2|5.0
C#, Visual Studio 2010, Entity Framework
entity framework 4, c#, visual studio 2010