Last active
February 22, 2016 13:17
-
-
Save JordyLangen/7801961 to your computer and use it in GitHub Desktop.
ICachedUnitOfWork implementation that caches query results
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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<TResult></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<TEntity></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<TResult></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<TEntity></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