public User GetUser(int userId)
{
var user = _user.GetOrAdd(userId, GetUserFromDb);
if (user == null) _user.TryRemove(userId, out user);
}
You can also wrap that into an extension method:
public static TValue GetOrAddIfNotNull<TKey, TValue>(
this ConcurrentDictionary<TKey, TValue> dictionary,
TKey key,
Func<TKey, TValue> valueFactory) where TValue : class
{
var value = dictionary.GetOrAdd(key, valueFactory);
if (value == null) dictionary.TryRemove(key, out value);
return value;
}
Then your code will look like:
public User GetUser(int userId)
{
var user = _user.GetOrAddIfNotNull(userId, GetUserFromDb)
}
UPDATE
As per @usr comment, there might be a case when:
- Thread 1 executes
GetOrAdd
, adds null
to the dictionary and pauses.
- User is added to the database.
- Thread 2 executes
GetOrAdd
and retrieves null
from the dictionary instead of hitting the database.
- Thread 1 and Thread 2 execute
TryRemove
and remove record from the dictionary.
With this timing, Thread 2 will get null
instead of hitting the database and getting the user record. If this edge case matters to you and you still want to use ConcurrentDictionary
, then you can use lock
in the extension method:
public static class ConcurrentDictionaryExtensions
{
private static readonly object myLock = new object();
public static TValue GetOrAddIfNotNull<TKey, TValue>(
this ConcurrentDictionary<TKey, TValue> dictionary,
TKey key,
Func<TKey, TValue> valueFactory) where TValue : class
{
lock (myLock)
{
var value = dictionary.GetOrAdd(key, valueFactory);
if (value == null) dictionary.TryRemove(key, out value);
return value;
}
}
}