Skip to content

Commit 54352b8

Browse files
author
Jeff Treuting
committed
Fixes #47
Created failing Unit tests for standard caching and pagination. When caching is on and PagingOptions are used on GetAll or FindAll, now it caches the TotalItems property so it can retrieve it and set the TotalItems property on the PagingOptions that were passed in and this fixes the issue.
1 parent 8dc92cb commit 54352b8

3 files changed

Lines changed: 211 additions & 10 deletions

File tree

SharpRepository.Repository/Caching/StandardCachingStrategyBase.cs

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,26 +64,42 @@ public bool TryGetAllResult<TResult>(IQueryOptions<T> queryOptions, Expression<F
6464
{
6565
result = null;
6666

67-
return GenerationalCachingEnabled && IsInCache(GetAllCacheKey(queryOptions, selector), out result);
67+
if (!GenerationalCachingEnabled)
68+
return false;
69+
70+
var cacheKey = GetAllCacheKey(queryOptions, selector);
71+
if (!IsInCache(cacheKey, out result))
72+
return false;
73+
74+
// if there are no query options then we don't need to check for the cache for data to update them with
75+
return queryOptions == null || SetCachedQueryOptions(cacheKey, queryOptions);
6876
}
6977

7078
public void SaveGetAllResult<TResult>(IQueryOptions<T> queryOptions, Expression<Func<T, TResult>> selector, IEnumerable<TResult> result)
7179
{
7280
if (GenerationalCachingEnabled)
73-
SetCache(GetAllCacheKey(queryOptions, selector), result);
81+
SetCache(GetAllCacheKey(queryOptions, selector), result, queryOptions);
7482
}
7583

7684
public bool TryFindAllResult<TResult>(ISpecification<T> criteria, IQueryOptions<T> queryOptions, Expression<Func<T, TResult>> selector, out IEnumerable<TResult> result)
7785
{
7886
result = null;
7987

80-
return GenerationalCachingEnabled && IsInCache(FindAllCacheKey(criteria, queryOptions, selector), out result);
88+
if (!GenerationalCachingEnabled)
89+
return false;
90+
91+
var cacheKey = FindAllCacheKey(criteria, queryOptions, selector);
92+
if (!IsInCache(cacheKey, out result))
93+
return false;
94+
95+
// if there are no query options then we don't need to check for the cache for data to update them with
96+
return queryOptions == null || SetCachedQueryOptions(cacheKey, queryOptions);
8197
}
8298

8399
public void SaveFindAllResult<TResult>(ISpecification<T> criteria, IQueryOptions<T> queryOptions, Expression<Func<T, TResult>> selector, IEnumerable<TResult> result)
84100
{
85101
if (GenerationalCachingEnabled)
86-
SetCache(FindAllCacheKey(criteria, queryOptions, selector), result);
102+
SetCache(FindAllCacheKey(criteria, queryOptions, selector), result, queryOptions);
87103
}
88104

89105
public bool TryFindResult<TResult>(ISpecification<T> criteria, IQueryOptions<T> queryOptions, Expression<Func<T, TResult>> selector, out TResult result)
@@ -99,6 +115,34 @@ public void SaveFindResult<TResult>(ISpecification<T> criteria, IQueryOptions<T>
99115
SetCache(FindCacheKey(criteria, queryOptions, selector), result);
100116
}
101117

118+
/// <summary>
119+
/// 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.
120+
/// </summary>
121+
/// <param name="cacheKey"></param>
122+
/// <param name="queryOptions"></param>
123+
/// <returns>True if it is not a PagingOptions query or if it is and the TotalItems is stored in cache as well</returns>
124+
private bool SetCachedQueryOptions(string cacheKey, IQueryOptions<T> queryOptions)
125+
{
126+
// TODO: see if there is a better way that doesn't rely on checking for PagingOptions specifically
127+
// 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
128+
129+
// we only need to do this for PagingOptions because it has a TotalItems property that we need
130+
if (!(queryOptions is PagingOptions<T>))
131+
return true;
132+
133+
int totalItems;
134+
// there is a PagingOptions passed in so we want to make sure that both the results and the queryOptions are in cache
135+
// this is a safety in case the caching provider kicked one of them out
136+
if (IsPagingTotalInCache(cacheKey, out totalItems))
137+
{
138+
((PagingOptions<T>) queryOptions).TotalItems = totalItems;
139+
return true;
140+
}
141+
142+
// 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
143+
return false;
144+
}
145+
102146
public void Add(TKey key, T result)
103147
{
104148
if (WriteThroughCachingEnabled)
@@ -335,11 +379,35 @@ protected bool IsInCache<TCacheItem>(string cacheKey, out TCacheItem result)
335379
return false;
336380
}
337381

338-
protected void SetCache<TCacheItem>(string cacheKey, TCacheItem result)
382+
protected bool IsPagingTotalInCache(string cacheKey, out int totalItems)
383+
{
384+
totalItems = 0;
385+
try
386+
{
387+
if (CachingProvider.Get(cacheKey + "=>pagingTotal", out totalItems))
388+
{
389+
//Trace.WriteLine(String.Format("Got item from cache: {0} - {1}", cacheKey, typeof(TCacheItem).Name));
390+
return true;
391+
}
392+
}
393+
catch (Exception)
394+
{
395+
// don't let caching errors cause problems for the Repository
396+
}
397+
398+
return false;
399+
}
400+
401+
protected void SetCache<TCacheItem>(string cacheKey, TCacheItem result, IQueryOptions<T> queryOptions = null )
339402
{
340403
try
341404
{
342405
CachingProvider.Set(cacheKey, result);
406+
407+
if (queryOptions is PagingOptions<T>)
408+
{
409+
CachingProvider.Set(cacheKey + "=>pagingTotal", ((PagingOptions<T>)queryOptions).TotalItems);
410+
}
343411
//Trace.WriteLine(String.Format("Write item to cache: {0} - {1}", cacheKey, typeof(TCacheItem).Name));
344412
}
345413
catch (Exception)

SharpRepository.Repository/Caching/TimeoutCachingStrategyBase.cs

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,22 +44,32 @@ public void SaveGetResult<TResult>(TKey key, Expression<Func<T, TResult>> select
4444

4545
public bool TryGetAllResult<TResult>(IQueryOptions<T> queryOptions, Expression<Func<T, TResult>> selector, out IEnumerable<TResult> result)
4646
{
47-
return IsInCache(GetAllCacheKey(queryOptions, selector), out result);
47+
var cacheKey = GetAllCacheKey(queryOptions, selector);
48+
if (!IsInCache(cacheKey, out result))
49+
return false;
50+
51+
// if there are no query options then we don't need to check for the cache for data to update them with
52+
return queryOptions == null || SetCachedQueryOptions(cacheKey, queryOptions);
4853
}
4954

5055
public void SaveGetAllResult<TResult>(IQueryOptions<T> queryOptions, Expression<Func<T, TResult>> selector, IEnumerable<TResult> result)
5156
{
52-
SetCache(GetAllCacheKey(queryOptions, selector), result);
57+
SetCache(GetAllCacheKey(queryOptions, selector), result, queryOptions);
5358
}
5459

5560
public bool TryFindAllResult<TResult>(ISpecification<T> criteria, IQueryOptions<T> queryOptions, Expression<Func<T, TResult>> selector, out IEnumerable<TResult> result)
5661
{
57-
return IsInCache(FindAllCacheKey(criteria, queryOptions, selector), out result);
62+
var cacheKey = FindAllCacheKey(criteria, queryOptions, selector);
63+
if (!IsInCache(cacheKey, out result))
64+
return false;
65+
66+
// if there are no query options then we don't need to check for the cache for data to update them with
67+
return queryOptions == null || SetCachedQueryOptions(cacheKey, queryOptions);
5868
}
5969

6070
public void SaveFindAllResult<TResult>(ISpecification<T> criteria, IQueryOptions<T> queryOptions, Expression<Func<T, TResult>> selector, IEnumerable<TResult> result)
6171
{
62-
SetCache(FindAllCacheKey(criteria, queryOptions, selector), result);
72+
SetCache(FindAllCacheKey(criteria, queryOptions, selector), result, queryOptions);
6373
}
6474

6575
public bool TryFindResult<TResult>(ISpecification<T> criteria, IQueryOptions<T> queryOptions, Expression<Func<T, TResult>> selector, out TResult result)
@@ -72,6 +82,34 @@ public void SaveFindResult<TResult>(ISpecification<T> criteria, IQueryOptions<T>
7282
SetCache(FindCacheKey(criteria, queryOptions, selector), result);
7383
}
7484

85+
/// <summary>
86+
/// 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.
87+
/// </summary>
88+
/// <param name="cacheKey"></param>
89+
/// <param name="queryOptions"></param>
90+
/// <returns>True if it is not a PagingOptions query or if it is and the TotalItems is stored in cache as well</returns>
91+
private bool SetCachedQueryOptions(string cacheKey, IQueryOptions<T> queryOptions)
92+
{
93+
// TODO: see if there is a better way that doesn't rely on checking for PagingOptions specifically
94+
// 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
95+
96+
// we only need to do this for PagingOptions because it has a TotalItems property that we need
97+
if (!(queryOptions is PagingOptions<T>))
98+
return true;
99+
100+
int totalItems;
101+
// there is a PagingOptions passed in so we want to make sure that both the results and the queryOptions are in cache
102+
// this is a safety in case the caching provider kicked one of them out
103+
if (IsPagingTotalInCache(cacheKey, out totalItems))
104+
{
105+
((PagingOptions<T>)queryOptions).TotalItems = totalItems;
106+
return true;
107+
}
108+
109+
// 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
110+
return false;
111+
}
112+
75113
public void Add(TKey key, T result)
76114
{
77115
// nothing to do
@@ -113,11 +151,35 @@ private bool IsInCache<TCacheItem>(string cacheKey, out TCacheItem result)
113151
return false;
114152
}
115153

116-
private void SetCache<TCacheItem>(string cacheKey, TCacheItem result)
154+
protected bool IsPagingTotalInCache(string cacheKey, out int totalItems)
155+
{
156+
totalItems = 0;
157+
try
158+
{
159+
if (CachingProvider.Get(cacheKey + "=>pagingTotal", out totalItems))
160+
{
161+
//Trace.WriteLine(String.Format("Got item from cache: {0} - {1}", cacheKey, typeof(TCacheItem).Name));
162+
return true;
163+
}
164+
}
165+
catch (Exception)
166+
{
167+
// don't let caching errors cause problems for the Repository
168+
}
169+
170+
return false;
171+
}
172+
173+
private void SetCache<TCacheItem>(string cacheKey, TCacheItem result, IQueryOptions<T> queryOptions = null)
117174
{
118175
try
119176
{
120177
CachingProvider.Set(cacheKey, result, CacheItemPriority.Default, TimeoutInSeconds);
178+
179+
if (queryOptions is PagingOptions<T>)
180+
{
181+
CachingProvider.Set(cacheKey + "=>pagingTotal", ((PagingOptions<T>)queryOptions).TotalItems, CacheItemPriority.Default, TimeoutInSeconds);
182+
}
121183
}
122184
catch (Exception)
123185
{

SharpRepository.Tests/Caching/InMemoryCachingTests.cs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System.Linq;
2+
using System.Runtime.Caching;
23
using SharpRepository.InMemoryRepository;
34
using NUnit.Framework;
45
using SharpRepository.Repository.Caching;
6+
using SharpRepository.Repository.Queries;
57
using SharpRepository.Tests.TestObjects;
68
using Should;
79

@@ -10,6 +12,23 @@ namespace SharpRepository.Tests.Caching
1012
[TestFixture]
1113
public class InMemoryCachingTests : TestBase
1214
{
15+
[SetUp]
16+
public void Setup()
17+
{
18+
// need to clear out the InMemory cache before each test is run so that each is independent and won't effect the next one
19+
var cache = MemoryCache.Default;
20+
foreach (var item in cache)
21+
{
22+
cache.Remove(item.Key);
23+
}
24+
}
25+
26+
[TearDown]
27+
public void Teardown()
28+
{
29+
30+
}
31+
1332
[Test]
1433
public void ExecuteGetAll_With_Selector_Should_Use_Cache_After_First_Call()
1534
{
@@ -141,5 +160,57 @@ public void ExecuteGet_Should_Use_Cache_After_First_Call()
141160
repos.CacheUsed.ShouldBeTrue();
142161
item.ShouldNotBeNull();
143162
}
163+
164+
[Test]
165+
public void ExecuteFindAll_With_Paging_Should_Save_TotalItems_In_Cache()
166+
{
167+
var repos = new InMemoryRepository<Contact>(new StandardCachingStrategy<Contact>());
168+
169+
repos.Add(new Contact { ContactId = 1, Name = "Test1" });
170+
repos.Add(new Contact { ContactId = 2, Name = "Test2" });
171+
repos.Add(new Contact { ContactId = 3, Name = "Test3" });
172+
repos.Add(new Contact { ContactId = 4, Name = "Test4" });
173+
174+
var pagingOptions = new PagingOptions<Contact>(1, 1, "Name");
175+
176+
var items = repos.FindAll(x => x.ContactId >= 2, x => x.Name, pagingOptions);
177+
repos.CacheUsed.ShouldBeFalse();
178+
items.Count().ShouldEqual(1);
179+
pagingOptions.TotalItems.ShouldEqual(3);
180+
181+
// reset paging options so the TotalItems is default
182+
pagingOptions = new PagingOptions<Contact>(1, 1, "Name");
183+
184+
items = repos.FindAll(x => x.ContactId >= 2, x => x.Name, pagingOptions);
185+
repos.CacheUsed.ShouldBeTrue();
186+
items.Count().ShouldEqual(1);
187+
pagingOptions.TotalItems.ShouldEqual(3);
188+
}
189+
190+
[Test]
191+
public void ExecuteGetAll_With_Paging_Should_Save_TotalItems_In_Cache()
192+
{
193+
var repos = new InMemoryRepository<Contact>(new StandardCachingStrategy<Contact>());
194+
195+
repos.Add(new Contact { ContactId = 1, Name = "Test1" });
196+
repos.Add(new Contact { ContactId = 2, Name = "Test2" });
197+
repos.Add(new Contact { ContactId = 3, Name = "Test3" });
198+
repos.Add(new Contact { ContactId = 4, Name = "Test4" });
199+
200+
var pagingOptions = new PagingOptions<Contact>(1, 1, "Name");
201+
202+
var items = repos.GetAll(x => x.Name, pagingOptions);
203+
repos.CacheUsed.ShouldBeFalse();
204+
items.Count().ShouldEqual(1);
205+
pagingOptions.TotalItems.ShouldEqual(4);
206+
207+
// reset paging options so the TotalItems is default
208+
pagingOptions = new PagingOptions<Contact>(1, 1, "Name");
209+
210+
items = repos.GetAll(x => x.Name, pagingOptions);
211+
repos.CacheUsed.ShouldBeTrue();
212+
items.Count().ShouldEqual(1);
213+
pagingOptions.TotalItems.ShouldEqual(4);
214+
}
144215
}
145216
}

0 commit comments

Comments
 (0)