Skip to content

Instantly share code, notes, and snippets.

@JordyLangen
Last active February 22, 2016 13:17
Show Gist options
  • Save JordyLangen/7801961 to your computer and use it in GitHub Desktop.
Save JordyLangen/7801961 to your computer and use it in GitHub Desktop.
ICachedUnitOfWork implementation that caches query results
public interface ICachedUnitOfWork : IUnitOfWork
{
/// <summary>
/// Executes the specified Query or return a cached result.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <param name="query">The query.</param>
/// <returns></returns>
TResult Query<TEntity, TResult>(Expression<Func<IQueryable<TEntity>, TResult>> query) where TEntity : class;
/// <summary>
/// Executes the specified Query or return a cached result.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <typeparam name="TResult">The type of the result. The method will return IEnumerable&lt;TResult&gt;</typeparam>
/// <param name="query">The query.</param>
/// <returns></returns>
IEnumerable<TResult> Query<TEntity, TResult>(Expression<Func<IQueryable<TEntity>, IEnumerable<TResult>>> query) where TEntity : class;
/// <summary>
/// Executes the specified Query or return a cached result.
/// </summary>
/// <typeparam name="TEntity">The type of the entity. The method will return IEnumerable&lt;TEntity&gt;</typeparam>
/// <param name="query">The query.</param>
/// <returns></returns>
IEnumerable<TEntity> Query<TEntity>(Expression<Func<IQueryable<TEntity>, IEnumerable<TEntity>>> query) where TEntity : class;
}
public class CachedQueryResult
{
/// <summary>
/// Gets or sets the query.
/// </summary>
/// <value>
/// The query.
/// </value>
public Expression Query { get; set; }
/// <summary>
/// Gets or sets the query result.
/// </summary>
/// <value>
/// The query result.
/// </value>
public object QueryResult { get; set; }
/// <summary>
/// Gets or sets the queried on.
/// </summary>
/// <value>
/// The queried on.
/// </value>
public DateTime QueriedOn { get; set; }
/// <summary>
/// Determines whether the specified life span is valid.
/// </summary>
/// <param name="lifeSpan">The life span.</param>
/// <returns>
/// <c>true</c> if the specified life span is valid; otherwise, <c>false</c>.
/// </returns>
public bool IsValid(TimeSpan lifeSpan)
{
var timeSinceLastQuery = DateTime.Now - QueriedOn;
return lifeSpan > timeSinceLastQuery;
}
}
public abstract class CachedUnitOfWork : DbContext, ICachedUnitOfWork
{
/// <summary>
/// The cached query results
/// </summary>
private static readonly HashSet<CachedQueryResult> CachedQueryResults = new HashSet<CachedQueryResult>();
/// <summary>
/// The life span
/// </summary>
private static readonly TimeSpan LifeSpan = TimeSpan.FromMinutes(5); // 5 minutes
/// <summary>
/// The cached query results lock
/// </summary>
private static readonly object CachedQueryResultsLock = new object();
/// <summary>
/// Executes the specified Query or return a cached result.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <param name="query">The query.</param>
/// <returns></returns>
public TResult Query<TEntity, TResult>(Expression<Func<IQueryable<TEntity>, TResult>> query) where TEntity : class
{
lock (CachedQueryResultsLock)
{
query = query.EvaluateValues();
var cachedQueryResult = CachedQueryResults.SingleOrDefault(result => result.Query.ToString().Equals(query.ToString()));
if (cachedQueryResult != null && cachedQueryResult.IsValid(LifeSpan))
{
ApplicationLogger.Info("Returning cached query result. Query: {0}", cachedQueryResult.Query.ToString());
return (TResult)cachedQueryResult.QueryResult;
}
if (cachedQueryResult != null && !cachedQueryResult.IsValid(LifeSpan))
{
ApplicationLogger.Info("Deleting cached query result. Query: {0}", cachedQueryResult.Query.ToString());
CachedQueryResults.Remove(cachedQueryResult);
return ExecuteQuery(query);
}
return ExecuteQuery(query);
}
}
/// <summary>
/// Executes the specified Query or return a cached result.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <typeparam name="TResult">The type of the result. The method will return IEnumerable&lt;TResult&gt;</typeparam>
/// <param name="query">The query.</param>
/// <returns></returns>
public IEnumerable<TResult> Query<TEntity, TResult>(Expression<Func<IQueryable<TEntity>, IEnumerable<TResult>>> query) where TEntity : class
{
return Query<TEntity, IEnumerable<TResult>>(query);
}
/// <summary>
/// Executes the specified Query or return a cached result.
/// </summary>
/// <typeparam name="TEntity">The type of the entity. The method will return IEnumerable&lt;TEntity&gt;</typeparam>
/// <param name="query">The query.</param>
/// <returns></returns>
public IEnumerable<TEntity> Query<TEntity>(Expression<Func<IQueryable<TEntity>, IEnumerable<TEntity>>> query) where TEntity : class
{
return Query<TEntity, TEntity>(query);
}
/// <summary>
/// Executes the query.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <param name="query">The query.</param>
/// <returns></returns>
private TResult ExecuteQuery<TEntity, TResult>(Expression<Func<IQueryable<TEntity>, TResult>> query) where TEntity : class
{
var compiledQuery = query.Compile();
var result = compiledQuery(All<TEntity>());
var cachedQueryResult = new CachedQueryResult
{
QueriedOn = DateTime.Now,
Query = query,
QueryResult = result
};
var enumerableResult = result as IEnumerable<TEntity>;
if (enumerableResult != null)
{
cachedQueryResult.QueryResult = enumerableResult.ToList();
}
CachedQueryResults.Add(cachedQueryResult);
ApplicationLogger.Info("Added cached query result. Query: {0}", cachedQueryResult.Query.ToString());
return result;
}
/// <summary>
/// Gets this instance.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public IQueryable<T> All<T>() where T : class
{
return Set<T>();
}
/// <summary>
/// Finds the specified keys.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="keys">The keys.</param>
/// <returns></returns>
public T Find<T>(params object[] keys) where T : class
{
return Set<T>().Find(keys);
}
/// <summary>
/// Adds the specified entry.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="entry">The entry.</param>
/// <returns></returns>
public T Add<T>(T entry) where T : class
{
return Set<T>().Add(entry);
}
/// <summary>
/// Removes the specified entry.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="entry">The entry.</param>
/// <returns></returns>
public T Remove<T>(T entry) where T : class
{
return Set<T>().Remove(entry);
}
/// <summary>
/// Attaches the specified entry.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="entry">The entry.</param>
/// <returns></returns>
public T Attach<T>(T entry) where T : class
{
return Set<T>().Attach(entry);
}
/// <summary>
/// Saves the changes.
/// </summary>
public new void SaveChanges()
{
base.SaveChanges();
}
/// <summary>
/// Indicates if this instance is disposed
/// </summary>
private bool _disposed;
/// <summary>
/// Finalizes an instance of the <see cref="UrsContext" /> class.
/// </summary>
~CachedUnitOfWork()
{
Dispose(false);
}
/// <summary>
/// Disposes the context. The underlying <see cref="T:System.Data.Objects.ObjectContext" /> is also disposed if it was created
/// is by this context or ownership was passed to this context when this context was created.
/// The connection to the database (<see cref="T:System.Data.Common.DbConnection" /> object) is also disposed if it was created
/// is by this context or ownership was passed to this context when this context was created.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected override void Dispose(bool disposing)
{
if (!_disposed)
{
base.Dispose(disposing);
_disposed = true;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment