使用变量中的常量动态生成 lambda 表达式
本文关键字:lambda 表达式 动态 常量 变量 | 更新日期: 2023-09-27 18:00:58
我一直在解决某些EF 6代码的一个异常问题,其中使用Oracle.ManagedDataAccess.Client驱动程序运行的查询有时需要几分钟才能返回结果,即使基础查询在2ms内执行也是如此。示例查询如下所示:
var result = users.Where(u => u.username == varUserName).FirstOrDefault();
此查询可能需要几分钟才能返回,但是如果我在 lambda 函数中用常量将查询替换为相同的东西,它会立即运行:
var result = users.Where(u => u.username == "testUsername").FirstOrDefault();
要解决此问题,我可以编写参数化的 SQL 查询,也可以手动生成适当的 lambda 表达式树:
var userParam = Expression.Parameter(typeof(Entity.User), "user");
var userNameField = Expression.Property(userParam, "username");
var userNameConstant = Expression.Constant(varUserName, typeof(string));
var equalUserName = Expression.Equal(userNameField, userNameConstant);
var lambda = Expression.Lambda<Func<Entity.User, bool>>(equalUserName, new ParameterExpression[] { userParam });
var result = users.Where(lambda).FirstOrDefault();
因为这有效,所以引出了一个问题:有没有办法轻松生成 lambda 表达式树,导致变量直接作为常量包含在内,而不是引用变量?
例如,像这样的东西是理想的:
var lambdaExpression = (u => u.username == varUserName).ReplaceVariablesWithConstants();
使用 ExpressionVisitor
可以相对容易地完成,它像这样评估ConstantExpression
成员:
public static class ExpressionUtils
{
public static Expression<TDelegate> ReplaceVariablesWithConstants<TDelegate>(this Expression<TDelegate> source)
{
return source.Update(
new ReplaceVariablesWithConstantsVisitor().Visit(source.Body),
source.Parameters);
}
class ReplaceVariablesWithConstantsVisitor : ExpressionVisitor
{
protected override Expression VisitMember(MemberExpression node)
{
var expression = Visit(node.Expression);
if (expression is ConstantExpression)
{
var variable = ((ConstantExpression)expression).Value;
var value = node.Member is FieldInfo ?
((FieldInfo)node.Member).GetValue(variable) :
((PropertyInfo)node.Member).GetValue(variable);
return Expression.Constant(value, node.Type);
}
return node.Update(expression);
}
}
}
这有点难。您需要使用 ExpressionsVisitor 修改表达式树。它可能会变成这样:
var lambdaExpression = ReplaceVariablesWithConstants(u => u.username == varUserName);