[英]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肿的缓存。
我知道这并不能直接回答您的问题,但是我想提出一些有关此方法的问题,这些问题一开始可能很诱人:
我可以看到这种方法背后的原因。 零维护性能层。 但是,如果不考虑上述几点,则该方法将首先破坏性能,然后导致进行很多维护尝试,然后证明是完全无法维护的。
我一直走那条路。 最终浪费了很多时间,放弃了。
我发现了一种更好的方法,即当结果来自后端时分别缓存每个结果实体,并分别为每种类型或通过公共接口使用扩展方法。
然后,您可以为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
实例,然后使用您的表达式调用其Visit
或VisitAndConvert
方法即可。
请注意,还将对您的复杂类型调用Expression.ToString
方法,因此可以覆盖其ToString
方法或在Evaluate
方法中为其编写自定义逻辑。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.