在写一个功能时,为避免从数据库中反复读取各种对象,于是写一个简单的仓库用于缓存。仓库定义如下:
public abstract class ReadOnlyRepository<TKey, TValue> where TKey : notnull
{
protected ConcurrentDictionary<TKey, TValue> Items { get; set; } = new ConcurrentDictionary<TKey, TValue>();
public TValue this[TKey key]
{
//根据 TKey ,返回 TValue。
}
public Dictionary<TKey, TValue> GetBatch(HashSet<TKey> keys)
{
//根据一组 TKey ,返回一组 TValue。
}
public bool TryGetValue(TKey key, out TValue value)
{
//根据 TKey ,返回 TValue,返回 bool 值指示是否找到了值
}
public bool TryGetBatch(HashSet<TKey> keys, out Dictionary<TKey, TValue> keyValuePairs)
{
//根据一组 TKey ,返回一组 TValue,返回 bool 值指示是否所有的Tkey都找到了值
}
protected abstract bool TryGetValueFromDb(TKey key, [MaybeNullWhen(false)] out TValue value);
protected abstract bool TryGetBatchFromDb(HashSet<TKey> keys, out Dictionary<TKey, TValue> keyValuePairs);
protected abstract string TKeyName { get; }
protected abstract string TValueName { get; }
}
上述定义中,4个公共函数均会先从缓存 Item 中查询,如果不存在,再尝试查询数据库。
另外两个抽象函数
protected abstract bool TryGetValueFromDb 和 protected abstract bool TryGetBatchFromDb 用于执行具体的查询数据库和批量查询数据库的逻辑留给继承的子类去实现。
还有两个抽象属性 TKeyName 和 TValueName 用于给出 key 类型和 value 类型的友好名称,也由子类实现。
完整代码如下:
public abstract class ReadOnlyRepository<TKey, TValue> where TKey : notnull
{
protected ConcurrentDictionary<TKey, TValue> Items { get; set; } = new ConcurrentDictionary<TKey, TValue>();
public TValue this[TKey key]
{
get
{
TValue? value;
if (!TryGetValue(key, out value))
{
throw new System.NullReferenceException($"Cannot find {TValueName} by {TKeyName} \"{key}\"");
}
else
{
return value;
}
}
}
public Dictionary<TKey, TValue> GetBatch(HashSet<TKey> keys)
{
bool allFound;
Dictionary<TKey, TValue> results;
allFound = TryGetBatch(keys, out results);
if (!allFound)
{
HashSet<TKey> notFound = keys.Except(results.Keys).ToHashSet();
if (notFound.Any())
{
throw new System.NullReferenceException(
$"Cannot find {TValueName} by {TKeyName} \"{string.Join(';', notFound.Select(x => x.ToString()))}\"");
}
}
return results;
}
/// <summary>
/// If found, return true; else return false
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <returns></returns>
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
{
bool found;
if (Items.ContainsKey(key))
{
found = true;
value = Items[key];
}
else
{
found = TryGetValueFromDb(key, out value);
if (value != null) { Items.TryAdd(key, value); }
}
return found;
}
/// <summary>
/// If found ALL, return true; else return false
/// </summary>
/// <param name="keys"></param>
/// <param name="keyValuePairs"></param>
/// <returns></returns>
public bool TryGetBatch(HashSet<TKey> keys, out Dictionary<TKey, TValue> keyValuePairs)
{
bool allFound;
HashSet<TKey> notBuffered = keys.Except(Items.Keys).ToHashSet();
if (notBuffered.Count == 0)
{
allFound = true;
}
else
{
Dictionary<TKey, TValue> pulled;
allFound = TryGetBatchFromDb(notBuffered, out pulled);
foreach (var item in pulled) { Items.TryAdd(item.Key, item.Value); }
}
keyValuePairs = new Dictionary<TKey, TValue>();
foreach (var key in keys.Intersect(Items.Keys)) { keyValuePairs.Add(key, Items[key]); }
return allFound;
}
/// <summary>
/// If found, return true; else return false
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <returns></returns>
protected abstract bool TryGetValueFromDb(TKey key, [MaybeNullWhen(false)] out TValue value);
/// <summary>
/// If found ALL, return true; else return false
/// </summary>
/// <param name="keys"></param>
/// <param name="keyValuePairs"></param>
/// <returns>Dictionary that contains zero or more KVPs</returns>
protected abstract bool TryGetBatchFromDb(HashSet<TKey> keys, out Dictionary<TKey, TValue> keyValuePairs);
protected abstract string TKeyName { get; }
protected abstract string TValueName { get; }
}