Skip to content

Commit d4c1f26

Browse files
niemyjskiCopilot
andauthored
Fixed a bug where get by id(s) with cache key would also set the id b… (#161)
* Fixed a bug where get by id(s) with cache key would also set the id based cache route when there was an include or exclude defined. Also ensure we look up by the cache key first. * Update src/Foundatio.Repositories.Elasticsearch/Repositories/ElasticReadOnlyRepositoryBase.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestion from @niemyjski --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent e1c5070 commit d4c1f26

File tree

2 files changed

+90
-3
lines changed

2 files changed

+90
-3
lines changed

src/Foundatio.Repositories.Elasticsearch/Repositories/ElasticReadOnlyRepositoryBase.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public virtual async Task<T> GetByIdAsync(Id id, ICommandOptions options = null)
9797

9898
if (IsCacheEnabled && options.ShouldReadCache())
9999
{
100-
var value = await GetCachedFindHit(id).AnyContext();
100+
var value = await GetCachedFindHit(id, options.GetCacheKey()).AnyContext();
101101

102102
if (value?.Document != null)
103103
{
@@ -144,7 +144,7 @@ public virtual async Task<IReadOnlyCollection<T>> GetByIdsAsync(Ids ids, IComman
144144

145145
var hits = new List<FindHit<T>>();
146146
if (IsCacheEnabled && options.ShouldReadCache())
147-
hits.AddRange(await GetCachedFindHit(idList).AnyContext());
147+
hits.AddRange(await GetCachedFindHit(idList, options.GetCacheKey()).AnyContext());
148148

149149
var itemsToFind = idList.Except(hits.Select(i => (Id)i.Id)).ToList();
150150
if (itemsToFind.Count == 0)
@@ -892,7 +892,6 @@ protected async Task<FindHit<T>> GetCachedFindHit(Id id, string cacheKey = null)
892892
: null;
893893

894894
_logger.LogTrace("Cache {HitOrMiss}: type={EntityType} key={CacheKey}", (result != null ? "hit" : "miss"), EntityTypeName, cacheKey ?? id);
895-
896895
return result;
897896
}
898897
catch (Exception ex)
@@ -967,8 +966,15 @@ protected Task AddDocumentsToCacheAsync(FindHit<T> findHit, ICommandOptions opti
967966
protected virtual async Task AddDocumentsToCacheAsync(ICollection<FindHit<T>> findHits, ICommandOptions options, bool isDirtyRead)
968967
{
969968
if (options.HasCacheKey())
969+
{
970970
await Cache.SetAsync(options.GetCacheKey(), findHits, options.GetExpiresIn()).AnyContext();
971971

972+
// NOTE: Custom cache keys store the complete filtered result, but ID-based caching is skipped when includes/excludes are present to avoid incomplete data.
973+
// This method also doesn't take into account any query includes or excludes but GetById(s) requests don't specify a query.
974+
if (options.GetIncludes().Count > 0 || options.GetExcludes().Count > 0)
975+
return;
976+
}
977+
972978
// don't add dirty read documents by id as they may be out of sync due to eventual consistency
973979
if (isDirtyRead)
974980
return;

tests/Foundatio.Repositories.Elasticsearch.Tests/QueryTests.cs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,45 @@ public async Task CanHandleIncludeAndExcludeOnGetById()
248248
Assert.Null(companyLog.CompanyId);
249249
}
250250

251+
252+
[Fact]
253+
public async Task CanHandleIncludeAndExcludeOnGetByIdWithCaching()
254+
{
255+
var log = await _dailyRepository.AddAsync(LogEventGenerator.Generate(companyId: "1234567890", message: "test", stuff: "stuff"), o => o.ImmediateConsistency());
256+
Assert.NotNull(log?.Id);
257+
258+
Assert.Equal(1, _cache.Misses);
259+
Assert.Equal(0, _cache.Hits);
260+
Assert.Equal(1, _cache.Writes);
261+
Assert.Collection(_cache.Items, c => Assert.StartsWith("alias:daily-logevents-", c.Key));
262+
263+
const string cacheKey = "company:1234567890";
264+
var companyLog = await _dailyRepository.GetByIdAsync(log!.Id, o => o.QueryLogLevel(LogLevel.Warning).Exclude(e => e.Date).Include(e => e.Id).Include("createdUtc").Cache(cacheKey));
265+
Assert.Equal(log.Id, companyLog.Id);
266+
Assert.Equal(log.CreatedUtc, companyLog.CreatedUtc);
267+
Assert.Equal(default, companyLog.Date);
268+
Assert.Null(companyLog.Message);
269+
Assert.Null(companyLog.CompanyId);
270+
271+
Assert.Equal(2, _cache.Misses);
272+
Assert.Equal(0, _cache.Hits);
273+
Assert.Equal(2, _cache.Writes);
274+
Assert.Collection(_cache.Items, c => Assert.StartsWith("alias:daily-logevents-", c.Key), c => Assert.Equal($"LogEvent:{cacheKey}", c.Key));
275+
276+
// Ensure cache hit by cache key.
277+
companyLog = await _dailyRepository.GetByIdAsync(log!.Id, o => o.QueryLogLevel(LogLevel.Warning).Exclude(e => e.Date).Include(e => e.Id).Include("createdUtc").Cache(cacheKey));
278+
Assert.Equal(log.Id, companyLog.Id);
279+
Assert.Equal(log.CreatedUtc, companyLog.CreatedUtc);
280+
Assert.Equal(default, companyLog.Date);
281+
Assert.Null(companyLog.Message);
282+
Assert.Null(companyLog.CompanyId);
283+
284+
Assert.Equal(2, _cache.Misses);
285+
Assert.Equal(1, _cache.Hits);
286+
Assert.Equal(2, _cache.Writes);
287+
Assert.Collection(_cache.Items, c => Assert.StartsWith("alias:daily-logevents-", c.Key), c => Assert.Equal($"LogEvent:{cacheKey}", c.Key));
288+
}
289+
251290
[Fact]
252291
public async Task CanHandleIncludeAndExcludeOnGetByIds()
253292
{
@@ -264,6 +303,48 @@ public async Task CanHandleIncludeAndExcludeOnGetByIds()
264303
Assert.Null(companyLog.CompanyId);
265304
}
266305

306+
[Fact]
307+
public async Task CanHandleIncludeAndExcludeOnGetByIdsWithCaching()
308+
{
309+
var log = await _dailyRepository.AddAsync(LogEventGenerator.Generate(companyId: "1234567890", message: "test", stuff: "stuff"), o => o.ImmediateConsistency());
310+
Assert.NotNull(log?.Id);
311+
312+
Assert.Equal(1, _cache.Misses);
313+
Assert.Equal(0, _cache.Hits);
314+
Assert.Equal(1, _cache.Writes);
315+
Assert.Collection(_cache.Items, c => Assert.StartsWith("alias:daily-logevents-", c.Key));
316+
317+
const string cacheKey = "company:1234567890";
318+
var results = await _dailyRepository.GetByIdsAsync([log!.Id], o => o.QueryLogLevel(LogLevel.Warning).Exclude(e => e.Date).Include(e => e.Id).Include("createdUtc").Cache(cacheKey));
319+
Assert.Single(results);
320+
var companyLog = results.First();
321+
Assert.Equal(log.Id, companyLog.Id);
322+
Assert.Equal(log.CreatedUtc, companyLog.CreatedUtc);
323+
Assert.Equal(default, companyLog.Date);
324+
Assert.Null(companyLog.Message);
325+
Assert.Null(companyLog.CompanyId);
326+
327+
Assert.Equal(2, _cache.Misses);
328+
Assert.Equal(0, _cache.Hits);
329+
Assert.Equal(2, _cache.Writes);
330+
Assert.Collection(_cache.Items, c => Assert.StartsWith("alias:daily-logevents-", c.Key), c => Assert.Equal($"LogEvent:{cacheKey}", c.Key));
331+
332+
// Ensure cache hit by cache key.
333+
results = await _dailyRepository.GetByIdsAsync([log!.Id], o => o.QueryLogLevel(LogLevel.Warning).Exclude(e => e.Date).Include(e => e.Id).Include("createdUtc").Cache(cacheKey));
334+
Assert.Single(results);
335+
companyLog = results.First();
336+
Assert.Equal(log.Id, companyLog.Id);
337+
Assert.Equal(log.CreatedUtc, companyLog.CreatedUtc);
338+
Assert.Equal(default, companyLog.Date);
339+
Assert.Null(companyLog.Message);
340+
Assert.Null(companyLog.CompanyId);
341+
342+
Assert.Equal(2, _cache.Misses);
343+
Assert.Equal(1, _cache.Hits);
344+
Assert.Equal(2, _cache.Writes);
345+
Assert.Collection(_cache.Items, c => Assert.StartsWith("alias:daily-logevents-", c.Key), c => Assert.Equal($"LogEvent:{cacheKey}", c.Key));
346+
}
347+
267348
[Fact]
268349
public async Task GetByCompanyWithIncludeWillOverrideDefaultExclude()
269350
{

0 commit comments

Comments
 (0)