C# · 12月 22, 2021

c# – 在预先存在的BinaryExpression中更改ConstantExpression的值

我有一些代码生成表达式作为数据库读取中的“where”语句传递,我正在尝试加快速度.

下面的示例使where语句与传入值的表的PK相匹配:

private Expression MakeWhereForPK(int id){ var paramExp = Expression.Parameter(typeof(Brand),”b”); //Expression to get value from the entity var leftExp = Expression.Property(paramExp,”ID”); //Expression to state the value to match (from the passed in variable) var rightExp = Expression.Constant(id,typeof(int)); //Expression to compare the two var whereExp = Expression.Equal(leftExp,rightExp); return Expression.Lambda<Func<Brand,bool>>(whereExp,paramExp);}

以上是对问题的简化 – 真实的东西包括使表格查询并找到其PK等的代码.它实际上是通常在代码中执行相同的操作:

ctx.Brands.Where(b => b.ID = id);

这样做没问题,但是,在进行测试以优化某些事情时,我发现它相当慢 – 做上述1000000次需要大约25秒.如果我省略上面的最后一行(但很明显它没用!),它会更好,所以看起来像Expression.Lamba需要大约2/3的时间,但其余的也不是很好.

如果所有的查询都会立即发生,我可以把它变成一个IN风格的表达式并生成一次,但遗憾的是这是不可能的,所以我希望的是保存上面的大多数代,并且只是重用生成表达式,但传入不同的id值.

请注意,由于这将传递给Linq,我无法编译表达式以获得可以在调用时传入的整数参数 – 它必须保留为表达式树.

因此,以下可能是一个简单的版本,用于进行计时练习:

Expression<Func<Brand,bool>> savedExp;private Expression MakeWhereForPKWithCache(int id){ if (savedExp == null) { savedExp = MakeWhereForPK(id); } else { var body = (BinaryExpression)savedExp.Body; var rightExp = (ConstantExpression)body.Right; //At this point,value is readonly,so is there some otherway to “inject” id,//and save on compilation? rightExp.Value = id; } return savedExp;}

如何使用不同的id值重新使用表达式?

解决方法 你无法改变表达式树 – 它们是不可变的.但您可以通过制作自己的访问者来替换常量表达式: class MyVisitor : ExpressionVisitor{ private readonly ConstantExpression newIdExpression; public MyVisitor(int newId) { this.newIdExpression = Expression.Constant(newId); } public Expression ReplaceId(Expression sourceExpression) { return Visit(sourceExpression); } protected override Expression VisitConstant(ConstantExpression node) { return newIdExpression; }}

用法:

var expr = MakeWhereForPK(0); // p => p.ID == 0 var visitor = new MyVisitor(1); var newExpr = visitor.ReplaceId(expr); p => p.ID == 1

请注意,这是现有树的副本.我没有测试它的性能.我不确定,这会更快或更快.

这段代码:

// warming up var visitor = new MyVisitor(1); var expr = MakeWhereForPK(0); visitor.ReplaceId(MakeWhereForPK(0)); var sw = new System.Diagnostics.Stopwatch(); sw.Start(); for (var i = 0; i < 1000000; i++) { MakeWhereForPK(i); } sw.Stop(); Console.WriteLine(“Make expression: {0}”,sw.Elapsed); sw.Restart(); for (var i = 0; i < 1000000; i++) { visitor.Visit(expr); } sw.Stop(); Console.WriteLine(“Replace constant expression: {0}”,sw.Elapsed); Console.WriteLine(“Done.”);

在我的机器上生成这些结果:

Make expression: 00:00:04.1714254
Replace constant expression: 00:00:02.3644953
Done.

看起来访问者比创建新表达式更快.