Reputation: 47417
I've got the following extension
Public Module CacheExtensions
Sub New()
End Sub
Private sync As New Object()
Public Const DefaultCacheExpiration As Integer = 1200 ''# 20 minutes
<Extension()>
Public Function GetOrStore(Of T)(ByVal cache As Cache, ByVal key As String, ByVal generator As Func(Of T)) As T
Return cache.GetOrStore(key, If(generator IsNot Nothing, generator(), Nothing), DefaultCacheExpiration)
End Function
<Extension()>
Public Function GetOrStore(Of T)(ByVal cache As Cache, ByVal key As String, ByVal generator As Func(Of T), ByVal expireInSeconds As Double) As T
Return cache.GetOrStore(key, If(generator IsNot Nothing, generator(), Nothing), expireInSeconds)
End Function
<Extension()>
Public Function GetOrStore(Of T)(ByVal cache As Cache, ByVal key As String, ByVal obj As T) As T
Return cache.GetOrStore(key, obj, DefaultCacheExpiration)
End Function
<Extension()>
Public Function GetOrStore(Of T)(ByVal cache As Cache, ByVal key As String, ByVal obj As T, ByVal expireInSeconds As Double) As T
Dim result = cache(key)
If result Is Nothing Then
SyncLock sync
If result Is Nothing Then
result = If(obj IsNot Nothing, obj, Nothing)
cache.Insert(key, result, Nothing, DateTime.Now.AddSeconds(expireInSeconds), cache.NoSlidingExpiration)
End If
End SyncLock
End If
Return DirectCast(result, T)
End Function
End Module
From here, I'm using the extension is a TagService to get a list of tags
Public Function GetTagNames() As List(Of String) Implements Domain.ITagService.GetTags
''# We're not using a dynamic Cache key because the list of TagNames
''# will persist across all users in all regions.
Return HttpRuntime.Cache.GetOrStore(Of List(Of String))("TagNamesOnly",
Function() _TagRepository.Read().Select(Function(t) t.Name).OrderBy(Function(t) t).ToList())
End Function
All of this is pretty much straight forward except when I put a breakpoint on _TagRepository.Read()
. The problem is that it is getting called on every request, when I thought that it is only to be called when Result Is Nothing
Am I missing something here?
EDIT: and for you c# guys, here's the C# equivalent
public static class CacheExtensions
{
private static object sync = new object();
public const int DefaultCacheExpiration = 20;
public static T GetOrStore<T>( this Cache cache, string key, Func<T> generator ) {
return cache.GetOrStore( key, generator != null ? generator() : default( T ), DefaultCacheExpiration );
}
public static T GetOrStore<T>( this Cache cache, string key, Func<T> generator, double expireInMinutes ) {
return cache.GetOrStore( key, generator != null ? generator() : default( T ), expireInMinutes );
}
public static T GetOrStore<T>( this Cache cache, string key, T obj ) {
return cache.GetOrStore( key, obj, DefaultCacheExpiration );
}
public static T GetOrStore<T>( this Cache cache, string key, T obj, double expireInMinutes ) {
var result = cache[key];
if ( result == null ) {
lock ( sync ) {
if ( result == null ) {
result = obj != null ? obj : default( T );
cache.Insert( key, result, null, DateTime.Now.AddMinutes( expireInMinutes ), Cache.NoSlidingExpiration );
}
}
}
return (T)result;
}
}
and the call
return HttpRuntime.Cache.GetOrStore<List<string>>("TagNamesOnly", () => _TagRepository.Read().Select(t => t.Name).OrderBy(t => t).ToList());
Upvotes: 0
Views: 505
Reputation: 55457
The problem is that you're calling an overloaded method that's invoking your method and as @Simon Svensson pointed out the method that you're calling isn't the method that you posted. This is the C# version of the method that you're actually calling:
return cache.GetOrStore( key, generator != null ? generator() : default( T ), DefaultCacheExpiration )
Right away you should see that the second argument calls generator()
if its passed a non-null value.
UPDATED
The problem isn't anything to do with caching, its that the overloaded method that you're calling is executing your lambda method. If you're really set on calling the method as you outlined you'll have to change it to something like this:
''# Fix StackOverflow Code Coloring Bug
<Extension()>
Public Function GetOrStore(Of T)(ByVal cache As Cache, ByVal key As String, ByVal generator As Func(Of T)) As T
''# Null value to pass to first call. We can not use Nothing because VB can not infer the overload type
Dim NV As Object = Nothing
''# Call the primary method passing a null value
Dim Ret = cache.GetOrStore(key, NV, DefaultCacheExpiration)
''# If that call returns nothing call our generator() method and then re-call the main method
If (Ret Is Nothing) AndAlso (generator IsNot Nothing) Then
Ret = cache.GetOrStore(key, generator(), DefaultCacheExpiration)
End If
Return Ret
End Function
I haven't tested this but it or something very close should get you what you want. This is also why I'm not a big fan of lambdas. The idea is great, but people tend to lump so many together and create code that's not really readable. I'd rather have 10 lines that I can read than 1 line that does the same thing.
Upvotes: 1
Reputation: 19791
The signature of GetOrStore, and it's implementation, contains no evaluation of the Func you're sending in. I dont really know what's happening at the moment (or if it's really working). It seems you're adding a Func into your cache.
public const int DefaultCacheExpiration = 20;
private static readonly Object SyncRoot = new Object();
public static T GetOrStore<T>(this Cache cache, String key, Func<T> itemGenerator, Double expireInSeconds = DefaultCacheExpiration) {
var item = cache[key];
if (item != null)
return (T)item;
lock (SyncRoot) {
// Fetch a second time to check if anyone have
// added it while we blocked waiting for the lock.
item = cache[key];
if (item != null)
return (T)item;
// Invoke the almighty itemGenerator to execute,
// and generate, the item that should be inserted
// into the cache.
item = itemGenerator.Invoke();
cache.Insert(key, item, null, DateTime.Now.AddSeconds(expireInSeconds), Cache.NoSlidingExpiration);
return (T)item;
}
}
public static T GetOrStore<T>(this Cache cache, String key, T newItem, Double expireInSeconds = DefaultCacheExpiration) {
return cache.GetOrStore(key, () => newItem, expireInSeconds);
}
If this doesn't do it, look into your asp.net caching settings and amount of free memory on the server. The cache wont work in low-memory conditions.
Upvotes: 1