【问题标题】:Generating a unique cache key based on method arguments根据方法参数生成唯一的缓存键
【发布时间】:2011-09-05 00:47:22
【问题描述】:

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

例如:

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

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

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

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

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

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

【问题讨论】:

  • 对我来说,您似乎在描述某种身份映射。在这种情况下,您的密钥可以简单地是与 id 结合的类型句柄。身份映射的问题是您要确保它们绑定到某种上下文,全局身份映射非常难以无效

标签: c# .net asp.net caching


【解决方案1】:

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

    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}); 
}

【讨论】:

    【解决方案2】:

    我看到了一些选项

    1. 将数据打包到一个类中,使用 BinaryFormatter 对类进行序列化,并对序列化数据执行 SHA1 哈希以提供哈希键。

    2. 将数据打包到一个类中,实现 IEqualityComparer,然后可以将其存储在字典中。通过实现 IEqualityComparer,您将控制 Hash 的生成和执行的数据比较,以在发生冲突时识别唯一数据。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-05-22
      • 2018-01-29
      • 1970-01-01
      • 1970-01-01
      • 2014-03-16
      • 1970-01-01
      • 2021-08-27
      • 1970-01-01
      相关资源
      最近更新 更多