-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
Copy pathEfDbCache.cs
374 lines (309 loc) · 7.24 KB
/
EfDbCache.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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using SmartStore.Core.Caching;
using SmartStore.Core.Domain.Logging;
using SmartStore.Core.Domain.Messages;
using SmartStore.Core.Domain.Tasks;
using SmartStore.Core.Infrastructure.DependencyManagement;
namespace SmartStore.Data.Caching
{
public partial class EfDbCache : IDbCache
{
// Entity sets to be never cached or invalidated
private static readonly HashSet<string> _toxicSets = new HashSet<string>
{
typeof(ScheduleTask).Name,
typeof(Log).Name,
typeof(ActivityLog).Name,
typeof(QueuedEmail).Name
};
private const string KEYPREFIX = "efcache:";
private readonly object _lock = new object();
private bool _enabled;
private readonly ICacheManager _cache;
private readonly Work<IRequestCache> _requestCache;
public EfDbCache(ICacheManager innerCache, Work<IRequestCache> requestCache)
{
_cache = innerCache;
_requestCache = requestCache;
_enabled = true;
}
public bool Enabled
{
get
{
return _enabled;
}
set
{
if (_enabled == value)
return;
lock (_lock)
{
if (_enabled == false && value == true)
{
// When cache was disabled previously and gets enabled,
// we should clear the cache, because no invalidation has been performed
// during disabled state. We would deal with stale data otherwise.
Clear();
}
_enabled = value;
}
}
}
private bool IsToxic(IEnumerable<string> entitySets)
{
return entitySets.Any(x => _toxicSets.Contains(x));
}
#region Request Scoped
public virtual bool RequestTryGet(string key, out DbCacheEntry value)
{
value = null;
if (!Enabled)
{
return false;
}
key = HashKey(key);
value = _requestCache.Value.Get<DbCacheEntry>(key);
return value != null;
}
public virtual DbCacheEntry RequestPut(string key, object value, string[] dependentEntitySets)
{
if (!Enabled || IsToxic(dependentEntitySets))
{
return null;
}
key = HashKey(key);
var entry = new DbCacheEntry
{
Key = key,
Value = value,
EntitySets = dependentEntitySets,
CachedOnUtc = DateTime.UtcNow
};
_requestCache.Value.Put(key, entry);
foreach (var entitySet in entry.EntitySets)
{
var lookup = RequestGetLookupSet(entitySet);
lookup.Add(key);
}
return entry;
}
public virtual void RequestInvalidateSets(IEnumerable<string> entitySets)
{
Guard.NotNull(entitySets, nameof(entitySets));
if (!Enabled || !entitySets.Any() || IsToxic(entitySets))
return;
var sets = entitySets.Distinct().ToArray();
var itemsToInvalidate = new HashSet<string>();
foreach (var entitySet in sets)
{
var lookup = RequestGetLookupSet(entitySet, false);
if (lookup != null)
{
itemsToInvalidate.UnionWith(lookup);
}
}
foreach (var key in itemsToInvalidate)
{
RequestInvalidateItem(key);
}
}
public virtual void RequestInvalidateItem(string key)
{
Guard.NotEmpty(key, nameof(key));
var cache = _requestCache.Value;
var entry = cache.Get<DbCacheEntry>(key);
if (entry != null)
{
// remove item itself from cache
cache.Remove(key);
// remove this key in all lookups
foreach (var set in entry.EntitySets)
{
var lookup = RequestGetLookupSet(set, false);
if (lookup != null)
{
lookup.Remove(entry.Key);
}
}
}
}
private HashSet<string> RequestGetLookupSet(string entitySet, bool create = true)
{
var key = GetLookupKeyFor(entitySet);
if (create)
{
return _requestCache.Value.Get(key, () => new HashSet<string>());
}
else
{
return _requestCache.Value.Get<HashSet<string>>(key);
}
}
#endregion
public virtual bool TryGet(string key, out object value)
{
value = null;
if (!Enabled)
{
return false;
}
key = HashKey(key);
var now = DateTime.UtcNow;
var entry = _cache.Get<DbCacheEntry>(key);
if (entry != null)
{
if (entry.HasExpired(now))
{
lock (String.Intern(key))
{
InvalidateItemUnlocked(entry);
}
}
else
{
value = entry.Value;
return true;
}
}
return false;
}
public virtual DbCacheEntry Put(string key, object value, IEnumerable<string> dependentEntitySets, TimeSpan? duration)
{
if (!Enabled || IsToxic(dependentEntitySets))
{
return null;
}
key = HashKey(key);
lock (String.Intern(key))
{
var entitySets = dependentEntitySets.Distinct().ToArray();
var entry = new DbCacheEntry
{
Key = key,
Value = value,
EntitySets = entitySets,
CachedOnUtc = DateTime.UtcNow,
Duration = duration
};
_cache.Put(key, entry);
foreach (var entitySet in entitySets)
{
var lookup = GetLookupSet(entitySet);
lookup.Add(key);
}
return entry;
}
}
public void Clear()
{
_cache.RemoveByPattern(KEYPREFIX + "*");
_requestCache.Value.RemoveByPattern(KEYPREFIX + "*");
}
public virtual void InvalidateSets(IEnumerable<string> entitySets)
{
Guard.NotNull(entitySets, nameof(entitySets));
if (!Enabled || !entitySets.Any() || IsToxic(entitySets))
return;
var sets = entitySets.Distinct().ToArray();
lock (_lock)
{
var itemsToInvalidate = new HashSet<string>();
foreach (var entitySet in sets)
{
var lookup = GetLookupSet(entitySet, false);
if (lookup != null)
{
itemsToInvalidate.UnionWith(lookup);
}
}
foreach (var key in itemsToInvalidate)
{
InvalidateItemUnlocked(key);
}
}
}
public virtual void InvalidateItem(string key)
{
if (!Enabled)
{
return;
}
Guard.NotEmpty(key, nameof(key));
lock (String.Intern(key))
{
InvalidateItemUnlocked(key);
}
}
private void InvalidateItemUnlocked(string key)
{
if (!Enabled)
{
return;
}
if (_cache.Contains(key))
{
var entry = _cache.Get<DbCacheEntry>(key);
if (entry != null)
{
InvalidateItemUnlocked(entry);
}
}
}
protected void InvalidateItemUnlocked(DbCacheEntry entry)
{
// remove item itself from cache
_cache.Remove(entry.Key);
// remove this key in all lookups
foreach (var set in entry.EntitySets)
{
var lookup = GetLookupSet(set, false);
if (lookup != null)
{
lookup.Remove(entry.Key);
}
}
}
private ISet GetLookupSet(string entitySet, bool create = true)
{
var key = GetLookupKeyFor(entitySet);
if (create)
{
return _cache.GetHashSet(key);
}
else
{
if (_cache.Contains(key))
{
return _cache.GetHashSet(key);
}
}
return null;
}
private string GetLookupKeyFor(string entitySet)
{
return KEYPREFIX + "lookup:" + entitySet;
}
private static string HashKey(string key)
{
// Looking up large Keys can be expensive (comparing Large Strings), so if keys are large, hash them, otherwise if keys are short just use as-is
if (key.Length <= 128)
return KEYPREFIX + "data:" + key;
using (var sha = new SHA1CryptoServiceProvider())
{
try
{
return KEYPREFIX + "data:" + Convert.ToBase64String(sha.ComputeHash(Encoding.UTF8.GetBytes(key)));
}
catch
{
return KEYPREFIX + "data:" + key;
}
}
}
}
}