繁体   English   中英

将lambda表达式转换为唯一的缓存键

[英]Converting a lambda expression into a unique key for caching

我看过其他与此问题类似的问题,但找不到任何可行的答案。

我一直在使用以下代码来生成唯一键,以将linq查询的结果存储到缓存中。

    string key = ((LambdaExpression)expression).Body.ToString();

    foreach (ParameterExpression param in expression.Parameters)
    {
        string name = param.Name;
        string typeName = param.Type.Name;

        key = key.Replace(name + ".", typeName + ".");
    }

    return key;

对于包含整数或布尔值的简单查询,它似乎工作正常,但是当我的查询包含嵌套常量表达式时,例如

// Get all the crops on a farm where the slug matches the given slug.
(x => x.Crops.Any(y => slug == y.Slug) && x.Deleted == false)

因此,返回的密钥为:

(真实AndAlso(Farm.Crops.Any(y =>(value(OzFarmGuide.Controllers.FarmController + <> c__DisplayClassd).slug == y.Slug))AndAlso(Farm.Deleted == False))

如您所见,我通过的任何农作物名称都会给出相同的关键结果。 有没有一种方法可以提取给定参数的值,以便区分查询?

也可以将y转换为正确的类型名称,.....

正如Polity和Marc在评论中所说,您需要的是LINQ表达式的部分评估器。 您可以在Matt Warren的LINQ:构建IQueryable提供程序-第III部分中阅读如何使用ExpressionVisitor进行操作。 Pete Montgomery (链接到Polity) 对LINQ查询的结果进行缓存的文章描述了有关这种缓存的更多详细信息,例如,如何在查询中表示集合。

另外,我不确定是否会像这样依赖ToString() 我认为这主要是出于调试目的,并且将来可能会改变。 另一种方法是创建您自己的IEqualityComparer<Expression> ,它可以为任何表达式创建哈希码,并且可以比较两个表达式是否相等。 我可能也可以使用ExpressionVisitor进行此操作,但是这样做会很繁琐。

我一直在尝试找出一种方案,在这种情况下,这种方法可能有用而又不会导致难以维护的in肿的缓存。

我知道这并不能直接回答您的问题,但是我想提出一些有关此方法的问题,这些问题一开始可能很诱人:

  • 您打算如何管理参数排序? 就是 (x => x.blah ==“ slug” &&!x.Deleted)缓存键应等于(x =>!x.Deleted && x.blah ==“ slug”)缓存键。
  • 您如何计划避免在缓存中重复对象? 就是 根据设计,来自多个查询的同一服务器场将与每个查询分别缓存。 说,对于场中出现的每个子弹,我们都有场的单独副本。
  • 用更多参数(例如包裹,农夫等)扩展上述内容将导致更多匹配查询,每个查询都具有缓存的场的单独副本。 相同的适用于您可能查询的每种类型,并且参数的顺序可能不同
  • 现在,如果更新服务器场会怎样? 不知道哪些缓存查询将包含您的服务器场,您将被迫杀死整个缓存。 哪种方法适得其反。

我可以看到这种方法背后的原因。 零维护性能层。 但是,如果不考虑上述几点,则该方法将首先破坏性能,然后导致进行很多维护尝试,然后证明是完全无法维护的。

我一直走那条路。 最终浪费了很多时间,放弃了。

我发现了一种更好的方法,即当结果来自后端时分别缓存每个结果实体,并分别为每种类型或通过公共接口使用扩展方法。

然后,您可以为lambda表达式构建扩展方法,以便在访问数据库之前先尝试缓存。

var query = (x => x.Crops.Any(y => slug == y.Slug) && x.Deleted == false);
var results = query.FromCache();
if (!results.Any()) {
    results = query.FromDatabase();
    results.ForEach(x = x.ToCache());
}

当然,您仍然需要跟踪哪些查询实际命中了数据库,以避免查询A从数据库返回满足查询B的3个服务器场,并从缓存中返回一个匹配场,而数据库实际上将有20个匹配场可用。 因此,每个查询至少需要访问数据库一次。

而且,您需要跟踪返回0结果的查询,以避免它们因此无济于事。

但总而言之,您可以省去更少的代码,并且,作为奖励,当您更新服务器场时,您可以

var farm = (f => f.farmId == farmId).FromCache().First();
farm.Name = "My Test Farm";
var updatedFarm = farm.ToDatabase();
updatedFarm.ToCache();

怎么样:

var call = expression.Body as MethodCallExpression;

if (call != null)
{

    List<object> list = new List<object>();

    foreach (Expression argument in call.Arguments)
    {

        object o = Expression.Lambda(argument, expression.Parameters).Compile().DynamicInvoke();

        list.Add(o);

    }

    StringBuilder keyValue = new StringBuilder();

    keyValue.Append(expression.Body.ToString());

    list.ForEach(e => keyValue.Append(String.Format("_{0}", e.ToString())));

    string key = keyValue.ToString();

}

那这个呢?

public class KeyGeneratorVisitor : ExpressionVisitor
{
    protected override Expression VisitParameter(ParameterExpression node)
    {
        return Expression.Parameter(node.Type, node.Type.Name);
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        if (CanBeEvaluated(node))
        {
            return Expression.Constant(Evaluate(node));
        }
        else
        {
            return base.VisitMember(node);
        }
    }

    private static bool CanBeEvaluated(MemberExpression exp)
    {
        while (exp.Expression.NodeType == ExpressionType.MemberAccess)
        {
            exp = (MemberExpression) exp.Expression;
        }

        return (exp.Expression.NodeType == ExpressionType.Constant);
    }

    private static object Evaluate(Expression exp)
    {
        if (exp.NodeType == ExpressionType.Constant)
        {
            return ((ConstantExpression) exp).Value;
        }
        else
        {
            MemberExpression mexp = (MemberExpression) exp;
            object value = Evaluate(mexp.Expression);

            FieldInfo field = mexp.Member as FieldInfo;
            if (field != null)
            {
                return field.GetValue(value);
            }
            else
            {
                PropertyInfo property = (PropertyInfo) mexp.Member;
                return property.GetValue(value, null);
            }
        }
    }
}

这会将复杂的常量表达式替换为其原始值,并将参数名称替换为其类型名称。 因此,只需创建一个新的KeyGeneratorVisitor实例,然后使用您的表达式调用其VisitVisitAndConvert方法即可。

请注意,还将对您的复杂类型调用Expression.ToString方法,因此可以覆盖其ToString方法或在Evaluate方法中为其编写自定义逻辑。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM