forked from Avanade/Beef
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathReferenceDataCache.cs
146 lines (130 loc) · 5.68 KB
/
ReferenceDataCache.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/Beef
using Beef.Caching;
using Beef.Caching.Policy;
using Beef.Entities;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Beef.RefData.Caching
{
/// <summary>
/// Provides the standard <see cref="ReferenceDataManager"/> instance cache management.
/// </summary>
/// <typeparam name="TColl">The <see cref="ReferenceDataCollectionBase{TItem}"/> <see cref="Type"/> of the the collection.</typeparam>
/// <typeparam name="TItem">The <see cref="ReferenceDataBase"/> <see cref="Type"/> for the collection.</typeparam>
public class ReferenceDataCache<TColl, TItem> : CacheCoreBase, IReferenceDataCache<TColl, TItem>
where TColl : ReferenceDataCollectionBase<TItem>, IReferenceDataCollection, new()
where TItem : ReferenceDataBase, new()
{
private readonly object _lock = new();
private TColl? _coll;
private readonly Func<Task<TColl>> _loadCollection;
private readonly ReferenceDataCacheLoader _loader = ReferenceDataCacheLoader.Create();
/// <summary>
/// Initializes a new instance of the <see cref="ReferenceDataCache{TColl, TItem}"/> class.
/// </summary>
/// <param name="loadCollection">The function that loads the collection from the data repository (<c>null</c> indicates manually updated).</param>
public ReferenceDataCache(Func<Task<TColl>> loadCollection) : base(typeof(TItem).FullName)
{
_loadCollection = loadCollection;
}
/// <summary>
/// Gets the count of items in the cache (both expired and non-expired may co-exist until flushed).
/// </summary>
public override long Count => _coll == null ? 0 : _coll.AllList.Count;
/// <summary>
/// Gets the cached <see cref="IReferenceDataCollection"/>.
/// </summary>
/// <returns>The <see cref="IReferenceDataCollection"/>.</returns>
IReferenceDataCollection IReferenceDataCache.GetCollection()
{
return GetCollection();
}
/// <summary>
/// Gets the cached <see cref="ReferenceDataCollectionBase{TItem}"/>.
/// </summary>
/// <returns>The resultant <see cref="ReferenceDataCollectionBase{TItem}"/>.</returns>
public TColl GetCollection()
{
lock (_lock)
{
// Refresh ref data when cache has expired or no data exists.
ICachePolicy policy = GetPolicy();
if (_coll != null && !policy.HasExpired())
return _coll;
if (!(policy is NoCachePolicy))
{
_coll = GetCollectionInternal();
policy.Reset();
return _coll;
}
}
// The 'NoCachePolicy' should execute outside of the thread lock - allow concurrency.
return GetCollectionInternal();
}
/// <summary>
/// Gets/fills the collection whilst also making the cached data read only.
/// </summary>
private TColl GetCollectionInternal()
{
TColl coll;
if (_loadCollection == null)
coll = new TColl();
else
coll = _loader.LoadAsync(this, _loadCollection).GetAwaiter().GetResult() ?? new TColl();
coll.ETag = ETagGenerator.Generate(coll);
return coll;
}
/// <summary>
/// Sets the cached <see cref="ReferenceDataCollectionBase{TItem}"/> with the contents of the passed collection.
/// </summary>
/// <param name="items">The source collection.</param>
/// <remarks>This provides a means to override the loading of the collection on as needed basis. Also, resets
/// the cache expiry (<see cref="ICachePolicy.Reset"/>).</remarks>
void IReferenceDataCache.SetCollection(IEnumerable<ReferenceDataBase> items)
{
SetCollection((IEnumerable<TItem>)items);
}
/// <summary>
/// Sets the cached <see cref="ReferenceDataCollectionBase{TItem}"/> with the contents of the passed collection.
/// </summary>
/// <param name="items">The source collection.</param>
/// <remarks>This provides a means to override the loading of the collection on as needed basis. Also, resets
/// the cache expiry (<see cref="ICachePolicy.Reset"/>).</remarks>
public void SetCollection(IEnumerable<TItem> items)
{
lock (_lock)
{
if (_coll == null)
_coll = new TColl();
_coll.Clear();
foreach (var item in Check.NotNull(items, nameof(items)))
{
item.MakeReadOnly();
_coll.Add(item);
}
_coll.ETag = ETagGenerator.Generate(_coll);
GetPolicy().Reset();
}
}
/// <summary>
/// Flush the cache.
/// </summary>
/// <param name="ignoreExpiry"><c>true</c> indicates to flush immediately; otherwise, <c>false</c> to only flush when the <see cref="ICachePolicy"/> is <see cref="ICachePolicy.IsExpired"/> (default).</param>
protected override void OnFlushCache(bool ignoreExpiry)
{
lock (_lock)
{
if (_coll != null)
_coll = null;
}
}
/// <summary>
/// Indicates whether the cache has expired and must be refreshed.
/// </summary>
public bool IsExpired
{
get => (_coll == null) || GetPolicy().IsExpired;
}
}
}