基于方法参数生成唯一的缓存键

本文关键字:唯一 缓存 参数 于方法 方法 | 更新日期: 2023-09-27 17:57:55

我有一个基本的存储库框架,它最终执行查询并将结果映射回一个对象:

例如:

    public SomeEntity Get(id)
    {
        return base.GetItem<SomeEntity>
                   ("select * from SomeEntities where id = @idParam",
                    new { idParam = id}); 
    }

如果这看起来像Dapper,那是因为在引擎盖下GetItem正在包装Dapper。

我想在GetItem中添加自动缓存,我有两个参数:

  • 包含查询的字符串
  • 包含任何参数的匿名字典

我担心对这些参数进行简单的素数散列会导致缓存密钥冲突,并且当您从缓存中提取数据时,冲突可能非常非常严重(例如,泄漏敏感信息)。

那么,我有什么技术可以生成大小合理的缓存密钥,同时保证基于查询和参数输入的唯一性呢?

基于方法参数生成唯一的缓存键

我使用以下扩展方法来制作委托的缓存版本:

    public static Func<T, TResult> AsCached<T, TResult>(this Func<T, TResult> function)
    {
        var cachedResults = new Dictionary<T, TResult>();
        return (argument) =>
        {
            TResult result;
            lock (cachedResults)
            {
                if (!cachedResults.TryGetValue(argument, out result))
                {
                    result = function(argument);
                    cachedResults.Add(argument, result);
                }
            }
            return result;
        };
    }
    public static Func<T1, T2, TResult> AsCached<T1, T2, TResult>(this Func<T1, T2, TResult> function)
    {
        var cachedResults = new Dictionary<Tuple<T1, T2>, TResult>();
        return (value1, value2) =>
        {
            TResult result;
            var paramsTuple = new Tuple<T1, T2>(value1, value2);
            lock(cachedResults)
            {
                if (!cachedResults.TryGetValue(paramsTuple, out result))
                {
                    result = function(value1, value2);
                    cachedResults.Add(paramsTuple, result);
                }
            }
            return result;
        };
    }
    public static Func<T1, T2, T3, TResult> AsCached<T1, T2, T3, TResult>(this Func<T1, T2, T3, TResult> function)
    {
        var cachedResults = new Dictionary<Tuple<T1, T2, T3>, TResult>();
        return (value1, value2, value3) =>
        {
            TResult result;
            var paramsTuple = new Tuple<T1, T2, T3>(value1, value2, value3);
            lock(cachedResults)
            {
                if (!cachedResults.TryGetValue(paramsTuple, out result))
                {
                    result = function(value1, value2, value3);
                    cachedResults.Add(paramsTuple, result);
                }
            }
            return result;
        };
    }

对于N个参数,依此类推。。。

如果代码中不清楚,我会创建一个带有参数的元组,并将该元组用作字典的键,该字典保存每组参数的返回值。请注意,每次调用AsCached时,都会创建一个单独的缓存。

您可以使用以下方法:

private Func<int, SomeEntity> _getCached;
public SomeEntity Get(int id)
{
    if (_getCached == null)
    {
        Func<int, SomeEntity> func = GetImpl;
        _getCached = func.AsCached();
    }
    return _getCached(id);
}
private SomeEntity GetImpl(int id)
{
    return base.GetItem<SomeEntity>
               ("select * from SomeEntities where id = @idParam",
                new { idParam = id}); 
}

我看到了一些选项

  1. 将数据打包到一个类中,使用BinaryFormatter序列化该类,并对序列化后的数据执行SHA1哈希,以获得哈希键。

  2. 将数据打包到一个类中,实现IEqualityComparer,然后将其存储在Dictionary中。通过实现IEqualityComparer,您将控制哈希的生成以及在发生冲突时为识别唯一数据而执行的数据比较。