I generally prefer to work with EF by specifying exactly the data to fetch from the DB with regards to Navigation Properties/Lookups and the like, so we wrote this cache system to limit the number of calls to the database.
public class DBCache<TContext, TKey, T> : ICache<TKey, T> where T : class, IDbPk<T>, IAutoIncludeEntity<T>, new()
where TContext : DbContext, IQueryableRepository, IDisposable, new()
{
protected Dictionary<TKey, T> _Items;
/// <summary>
/// To assist with derivations of this system.
/// </summary>
protected DBCache()
{
}
/// <summary>
/// Generate a cache of entries specified by selector.
/// Incldue Nav Properties specified in IAutoIncludeEntity
/// </summary>
/// <param name="selector"></param>
public DBCache(Func<T, TKey> selector)
{
using (var db = new TContext())
{
_Items = db.GetDictionary(selector);
}
}
/// <summary>
/// Generate a cache of entries specified by selector.
/// Include Nav Properties specified in IAutoIncludeEntity
/// </summary>
/// <param name="context"></param>
/// <param name="selector"></param>
public DBCache(TContext context, Func<T, TKey> selector)
{
_Items = context.GetDictionary(selector);
}
/// <summary>
/// Generate a cache of entries
/// </summary>
/// <param name="context"></param>
/// <param name="selector"></param>
/// <param name="includes">Include these navigation properties</param>
public DBCache(TContext context, Func<T, TKey> selector, params Expression<Func<T, object>>[] includes)
{
_Items = context.GetDictionary(selector, includes);
}
/// <summary>
/// Get an item from the cache.
/// If it doesnt exist, try to get it from the store and if successful, add it to the cache.
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public T GetItem(TKey id)
{
if (_Items.ContainsKey(id))
return _Items[id];
using (var db = new TContext())
{
var contact = db.GetSingle<T>(new object[]{id});
//miss on cache - so add it
if (contact != null)
_Items.Add(id, contact);
return contact;
}
}
/// <summary>
/// Remove an item from the cache
/// </summary>
/// <param name="id"></param>
public void Remove(TKey id)
{
_Items.Remove(id);
}
/// <summary>
/// The Value collection (ICollection)
/// </summary>
public Dictionary<TKey, T>.ValueCollection Values { get { return _Items.Values; } }
}
Effectively I need to decorate Database first Entity Framework entities like so:
/// <summary>
/// An interface for a class to provide default Navigation Property includes
/// </summary>
/// <typeparam name="T">Type of Entity</typeparam>
public interface IAutoIncludeEntity<T> where T : class
{
Expression<Func<T, object>>[] Includes();
}
/// <summary>
/// An iterface for a class to provide the Primary Key Value
/// </summary>
public interface IDbPk<T>
{
/// <summary>
/// The Primary Key of this Entity
/// </summary>
object PrimaryKeyValue { get; }
/// <summary>
/// Expression to compare an instance via Primary Key
/// </summary>
/// <returns></returns>
Expression<Func<T, bool>> PkComparer();
}
Was the rationale behind this system OK, as I primarily work with Detached Entities in EF. One of the driving factors was to speed up performance in my application (mainly by reducing lag). I understand that I need an efficient way to determine if the cache is stale, but in our scenario we can simply refresh after an elapsed time.