Skip to content

Commit 1970268

Browse files
author
Jeff Treuting
committed
Added timeout and standard caching strategies
Needed to make a StandardCompoundKeyCachingStrategy because there was already a version with a 3rd generic for the Partition. We may want to create a StandardCachingStrategyWithPartition instead so it's obvious what it is and then we can have a StandardCachingStrategy<T, TKey, TKey2>. I'll probably do that later but it will be a breaking change so need to think it thry fully first.
1 parent e97e3a7 commit 1970268

7 files changed

Lines changed: 762 additions & 1 deletion
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq.Expressions;
4+
using SharpRepository.Repository.Queries;
5+
using SharpRepository.Repository.Specifications;
6+
7+
namespace SharpRepository.Repository.Caching
8+
{
9+
public abstract class CompoundKeyCachingStrategyBase<T, TKey, TKey2> : ICompoundKeyCachingStrategy<T, TKey, TKey2> where T : class
10+
{
11+
private ICachingProvider _cachingProvider;
12+
public string CachePrefix { get; set; }
13+
protected string TypeFullName { get; set; }
14+
15+
internal CompoundKeyCachingStrategyBase()
16+
{
17+
}
18+
19+
internal CompoundKeyCachingStrategyBase(ICachingProvider cachingProvider)
20+
{
21+
CachePrefix = "#Repo";
22+
CachingProvider = cachingProvider;
23+
24+
TypeFullName = typeof(T).FullName ?? typeof(T).Name; // sometimes FullName returns null in certain derived type situations, so I added the check to use the Name property if FullName is null
25+
}
26+
27+
public ICachingProvider CachingProvider
28+
{
29+
get { return _cachingProvider; }
30+
set { _cachingProvider = value ?? new InMemoryCachingProvider(); }
31+
}
32+
33+
public virtual bool TryGetResult<TResult>(TKey key, TKey2 key2, Expression<Func<T, TResult>> selector, out TResult result)
34+
{
35+
return IsInCache(GetWriteThroughCacheKey(key, key2, selector), out result);
36+
}
37+
38+
public virtual void SaveGetResult<TResult>(TKey key, TKey2 key2, Expression<Func<T, TResult>> selector, TResult result)
39+
{
40+
SetCache(GetWriteThroughCacheKey(key, key2, selector), result);
41+
}
42+
43+
public virtual bool TryGetAllResult<TResult>(IQueryOptions<T> queryOptions, Expression<Func<T, TResult>> selector, out IEnumerable<TResult> result)
44+
{
45+
var cacheKey = GetAllCacheKey(queryOptions, selector);
46+
if (!IsInCache(cacheKey, out result))
47+
return false;
48+
49+
// if there are no query options then we don't need to check for the cache for data to update them with
50+
return queryOptions == null || SetCachedQueryOptions(cacheKey, queryOptions);
51+
}
52+
53+
public virtual void SaveGetAllResult<TResult>(IQueryOptions<T> queryOptions, Expression<Func<T, TResult>> selector, IEnumerable<TResult> result)
54+
{
55+
SetCache(GetAllCacheKey(queryOptions, selector), result, queryOptions);
56+
}
57+
58+
public virtual bool TryFindAllResult<TResult>(ISpecification<T> criteria, IQueryOptions<T> queryOptions, Expression<Func<T, TResult>> selector, out IEnumerable<TResult> result)
59+
{
60+
var cacheKey = FindAllCacheKey(criteria, queryOptions, selector);
61+
if (!IsInCache(cacheKey, out result))
62+
return false;
63+
64+
// if there are no query options then we don't need to check for the cache for data to update them with
65+
return queryOptions == null || SetCachedQueryOptions(cacheKey, queryOptions);
66+
}
67+
68+
public virtual void SaveFindAllResult<TResult>(ISpecification<T> criteria, IQueryOptions<T> queryOptions, Expression<Func<T, TResult>> selector, IEnumerable<TResult> result)
69+
{
70+
SetCache(FindAllCacheKey(criteria, queryOptions, selector), result, queryOptions);
71+
}
72+
73+
public virtual bool TryFindResult<TResult>(ISpecification<T> criteria, IQueryOptions<T> queryOptions, Expression<Func<T, TResult>> selector, out TResult result)
74+
{
75+
return IsInCache(FindCacheKey(criteria, queryOptions, selector), out result);
76+
}
77+
78+
public virtual void SaveFindResult<TResult>(ISpecification<T> criteria, IQueryOptions<T> queryOptions, Expression<Func<T, TResult>> selector, TResult result)
79+
{
80+
SetCache(FindCacheKey(criteria, queryOptions, selector), result);
81+
}
82+
83+
public abstract void Add(TKey key, TKey2 key2, T result);
84+
85+
public abstract void Update(TKey key, TKey2 key2, T result);
86+
87+
public abstract void Delete(TKey key, TKey2 key2, T result);
88+
89+
public abstract void Save();
90+
91+
protected bool IsInCache<TCacheItem>(string cacheKey, out TCacheItem result)
92+
{
93+
result = default(TCacheItem);
94+
95+
try
96+
{
97+
if (CachingProvider.Get(cacheKey, out result))
98+
{
99+
return true;
100+
}
101+
}
102+
catch (Exception)
103+
{
104+
// don't let caching errors cause problems for the Repository
105+
}
106+
107+
return false;
108+
}
109+
110+
protected bool IsPagingTotalInCache(string cacheKey, out int totalItems)
111+
{
112+
totalItems = 0;
113+
try
114+
{
115+
if (CachingProvider.Get(cacheKey + "=>pagingTotal", out totalItems))
116+
{
117+
//Trace.WriteLine(String.Format("Got item from cache: {0} - {1}", cacheKey, typeof(TCacheItem).Name));
118+
return true;
119+
}
120+
}
121+
catch (Exception)
122+
{
123+
// don't let caching errors cause problems for the Repository
124+
}
125+
126+
return false;
127+
}
128+
129+
/// <summary>
130+
/// This will repopualte the PagingOptions.TotalItems from the value stored in cache. This should only be called if the results were already found in cache.
131+
/// </summary>
132+
/// <param name="cacheKey"></param>
133+
/// <param name="queryOptions"></param>
134+
/// <returns>True if it is not a PagingOptions query or if it is and the TotalItems is stored in cache as well</returns>
135+
protected bool SetCachedQueryOptions(string cacheKey, IQueryOptions<T> queryOptions)
136+
{
137+
// TODO: see if there is a better way that doesn't rely on checking for PagingOptions specifically
138+
// originally was thinking doing a ref arg for queryOptions and setting it via cache but ran into an issue in QueryManager using a ref in a lamda expression
139+
140+
// we only need to do this for PagingOptions because it has a TotalItems property that we need
141+
if (!(queryOptions is PagingOptions<T>))
142+
return true;
143+
144+
int totalItems;
145+
// there is a PagingOptions passed in so we want to make sure that both the results and the queryOptions are in cache
146+
// this is a safety in case the caching provider kicked one of them out
147+
if (IsPagingTotalInCache(cacheKey, out totalItems))
148+
{
149+
((PagingOptions<T>)queryOptions).TotalItems = totalItems;
150+
return true;
151+
}
152+
153+
// this was a PagingOptions query but the value wasn't in cache, so return false which will make the entire query be ran again so the results and TotalItems will get cached
154+
return false;
155+
}
156+
157+
protected void ClearCache(string cacheKey)
158+
{
159+
try
160+
{
161+
CachingProvider.Clear(cacheKey);
162+
}
163+
catch (Exception)
164+
{
165+
// don't let caching errors mess with the repository
166+
}
167+
}
168+
169+
protected void SetCache<TCacheItem>(string cacheKey, TCacheItem result, IQueryOptions<T> queryOptions = null)
170+
{
171+
try
172+
{
173+
CachingProvider.Set(cacheKey, result);
174+
175+
if (queryOptions is PagingOptions<T>)
176+
{
177+
CachingProvider.Set(cacheKey + "=>pagingTotal", ((PagingOptions<T>)queryOptions).TotalItems);
178+
}
179+
//Trace.WriteLine(String.Format("Write item to cache: {0} - {1}", cacheKey, typeof(TCacheItem).Name));
180+
}
181+
catch (Exception)
182+
{
183+
// don't let caching errors mess with the repository
184+
}
185+
}
186+
187+
protected string GetWriteThroughCacheKey<TResult>(TKey key, TKey2 key2, Expression<Func<T, TResult>> selector)
188+
{
189+
return String.Format("{0}/{1}/{2}/{3}::{4}", CachePrefix, TypeFullName, key, key2, (selector != null ? selector.ToString() : "null"));
190+
}
191+
192+
protected abstract string GetAllCacheKey<TResult>(IQueryOptions<T> queryOptions, Expression<Func<T, TResult>> selector);
193+
194+
protected abstract string FindAllCacheKey<TResult>(ISpecification<T> criteria, IQueryOptions<T> queryOptions, Expression<Func<T, TResult>> selector);
195+
196+
protected abstract string FindCacheKey<TResult>(ISpecification<T> criteria, IQueryOptions<T> queryOptions, Expression<Func<T, TResult>> selector);
197+
}
198+
}

SharpRepository.Repository/Caching/NoCachingStrategy.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
/// Implements no caching within the repository.
55
/// </summary>
66
/// <typeparam name="T">The type of the repository entity.</typeparam>
7-
/// <typeparam name="TKey">The type of the primary key.</typeparam>
7+
/// <typeparam name="TKey">The type of the first part of the compound primary key.</typeparam>
8+
/// <typeparam name="TKey2">The type of the second part of the compound primary key.</typeparam>
89
public class NoCachingStrategy<T, TKey, TKey2> : NoCompoundKeyCachingStrategyBase<T, TKey, TKey2>
910
{
1011

SharpRepository.Repository/Caching/StandardCachingStrategy.cs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,88 @@ public StandardCachingStrategy(ICachingProvider cachingProvider, Expression<Func
129129
Partition = partition;
130130
}
131131
}
132+
133+
/// <summary>
134+
/// Implements Write-Through caching for all CRUD operations (writing to the database and cache at the same time), and Generational caching for all queries (FindAll, GetAll, Find) with the option to partition the Generational Cache based on a specific entity property for better performance in certain situations.
135+
/// </summary>
136+
/// <typeparam name="T">Type of the entity the corresponding repository queries against.</typeparam>
137+
/// <typeparam name="TKey">The primary key type of the entity</typeparam>
138+
/// <typeparam name="TPartition">The type of the column that the Generational Cache will be partitioned on.</typeparam>
139+
public class StandardCompoundKeyCachingStrategy<T, TKey, TKey2> : StandardCompoundKeyCachingStrategyBase<T, TKey, TKey2, int> where T : class
140+
{
141+
/// <summary>
142+
/// Initializes a new instance of the <see cref="StandardCachingStrategy&lt;T, TKey&gt;"/> class.
143+
/// </summary>
144+
public StandardCompoundKeyCachingStrategy()
145+
: base()
146+
{
147+
}
148+
149+
/// <summary>
150+
/// Initializes a new instance of the <see cref="StandardCachingStrategy&lt;T, TKey&gt;"/> class.
151+
/// </summary>
152+
/// <param name="cachingProvider">The caching provider to use (e.g. <see cref="InMemoryCachingProvider"/>, <see cref="MemcachedCachingProvider"/>, etc.). Defaults to <see cref="InMemoryCachingProvider"/>.</param>
153+
public StandardCompoundKeyCachingStrategy(ICachingProvider cachingProvider)
154+
: base(cachingProvider)
155+
{
156+
Partition = null;
157+
}
158+
159+
/// <summary>
160+
/// Initializes a new instance of the <see cref="StandardCachingStrategy&lt;T, TKey&gt;"/> class.
161+
/// </summary>
162+
/// <param name="cachingProvider">The caching provider to use (e.g. <see cref="InMemoryCachingProvider"/>, <see cref="MemcachedCachingProvider"/>, etc.). Defaults to <see cref="InMemoryCachingProvider"/>.</param>
163+
/// <param name="partition">The property that should be used for partitioning.</param>
164+
public StandardCompoundKeyCachingStrategy(ICachingProvider cachingProvider, Expression<Func<T, int>> partition)
165+
: base(cachingProvider)
166+
{
167+
Partition = partition;
168+
}
169+
170+
/// <summary>
171+
/// Initializes a new instance of the <see cref="StandardCachingStrategy&lt;T, TKey&gt;"/> class.
172+
/// </summary>
173+
/// <param name="partition">The property that should be used for partitioning.</param>
174+
public StandardCompoundKeyCachingStrategy(Expression<Func<T, int>> partition)
175+
{
176+
Partition = partition;
177+
}
178+
}
179+
180+
/// <summary>
181+
/// Implements Write-Through caching for all CRUD operations (writing to the database and cache at the same time), and Generational caching for all queries (FindAll, GetAll, Find) with the option to partition the Generational Cache based on a specific entity property for better performance in certain situations.
182+
/// </summary>
183+
/// <typeparam name="T">Type of the entity the corresponding repository queries against.</typeparam>
184+
/// <typeparam name="TKey">The primary key type of the entity</typeparam>
185+
/// <typeparam name="TPartition">The type of the column that the Generational Cache will be partitioned on.</typeparam>
186+
public class StandardCompoundKeyCachingStrategy<T, TKey, TKey2, TPartition> : StandardCompoundKeyCachingStrategyBase<T, TKey, TKey2, TPartition> where T : class
187+
{
188+
/// <summary>
189+
/// Initializes a new instance of the <see cref="StandardCachingStrategy&lt;T, TKey, TPartition&gt;"/> class.
190+
/// </summary>
191+
public StandardCompoundKeyCachingStrategy()
192+
: base()
193+
{
194+
}
195+
196+
/// <summary>
197+
/// Initializes a new instance of the <see cref="StandardCachingStrategy&lt;T, TKey, TPartition&gt;"/> class.
198+
/// </summary>
199+
/// <param name="partition">The property that should be used for partitioning.</param>
200+
public StandardCompoundKeyCachingStrategy(Expression<Func<T, TPartition>> partition)
201+
{
202+
Partition = partition;
203+
}
204+
205+
/// <summary>
206+
/// Initializes a new instance of the <see cref="StandardCachingStrategy&lt;T, TKey, TPartition&gt;"/> class.
207+
/// </summary>
208+
/// <param name="cachingProvider">The caching provider to use (e.g. <see cref="InMemoryCachingProvider"/>, <see cref="MemcachedCachingProvider"/>, etc.). Defaults to <see cref="InMemoryCachingProvider"/>.</param>
209+
/// <param name="partition">The property that should be used for partitioning.</param>
210+
public StandardCompoundKeyCachingStrategy(ICachingProvider cachingProvider, Expression<Func<T, TPartition>> partition)
211+
: base(cachingProvider)
212+
{
213+
Partition = partition;
214+
}
215+
}
132216
}

0 commit comments

Comments
 (0)