Change the value of a constant expression in a pre-existing binary expression

I have code generation expressions that need to be passed as the where clause in the database, and I'm trying to speed things up a bit.

In the example below, the where statement matches the PK of the table with the passed value:

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

The above simplification of the question - the real thing includes code that allows you to get the table for the query and find its PK, etc. This effectively does the same thing you can usually do in code:

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

This works fine, but while testing to optimize things a bit, I found it rather slow - executing above 1,000,000 times takes about 25 seconds. It’s better if I omit the last line above (but obviously then it’s useless!), So this is an Expression.Lamba expression that takes about 2/3 times, but the rest is not great either.

If all the queries happened at once, I could turn it into an IN style expression and generate it once, but unfortunately this is not possible, so I hope this saves most of the generation above and just reuse the generated expression, but passing another id value.

Please note that since this will be passed to Linq, I cannot compile the expression to have an integer parameter that I can pass on the call - it should remain as an expression tree.

Thus, the following task may be a simple version for performing synchronization exercises:

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

How can I reuse an expression just with a different id value?

+6
source share
2 answers

You cannot modify expression trees β€” they are immutable. But you can replace the constant expression by making your own visitor:

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

Using:

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

Note that this makes a copy of the existing tree. , and I have not tested this for performance. I'm not sure if it will be faster or not.

This code:

  // 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."); 

produces these results on my machine:

Expression: 00: 00: 04.1714254
Replace the constant expression: 00: 00: 02.3644953
Done.

The visitor seems to be faster than creating a new expression.

+4
source

You can use the fact that the expression tree should not contain only simple constants, it can also contain access to properties. So, what would you do is create one expression that refers to the value of a property, and each time it changes only this property, not the expression tree.

Sort of:

 class ExpressionHolder { public int Value { get; set; } public Expression<Func<Brand, bool>> Expr { get; private set; } public ExpressionHolder() { Expr = MakeWhereForPK(); } private Expression<Func<Brand, bool>> MakeWhereForPK() { var paramExp = Expression.Parameter(typeof(Brand), "b"); var leftExp = Expression.Property(paramExp, "ID"); var rightExp = Expression.Property(Expression.Constant(this), "Value"); var whereExp = Expression.Equal(leftExp, rightExp); return Expression.Lambda<Func<Brand, bool>>(whereExp, paramExp); } } 

This is about 500 Γ— faster than your code: using the Dennis measurement code, I get the following results:

 Make expression: 00:00:02.9869921 Replace constant expression: 00:00:02.3332857 Set property: 00:00:00.0056485 
+6
source

All Articles