fabspro fabspro - 6 months ago 36
C# Question

Dynamically generate lambda expression with constants from variables

I've been troubleshooting an unusual issue with some EF 6 code where queries running with the Oracle.ManagedDataAccess.Client driver are sometimes taking several minutes to return results even when the underlying query executes within 2ms. An example query would be as follows:

var result = users.Where(u => u.username == varUserName).FirstOrDefault();

This query might take several minutes to return, however if I replace the query with the same thing with a constant in the lambda function, it runs instantaneously:

var result = users.Where(u => u.username == "testUsername").FirstOrDefault();

To work around this issue, I can either write parameterised SQL queries, or I can manually generate an appropriate lambda expression tree:

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

Because this works, it begs the question: is there a way to easily generate lambda expression trees which result in variables being directly included as constants, instead of references to variables?

For example, something like this would be ideal:

var lambdaExpression = (u => u.username == varUserName).ReplaceVariablesWithConstants();


It can be done relatively easy with ExpressionVisitor which evaluates the ConstantExpression members like this:

public static class ExpressionUtils
    public static Expression<TDelegate> ReplaceVariablesWithConstants<TDelegate>(this Expression<TDelegate> source)
        return source.Update(
            new ReplaceVariablesWithConstantsVisitor().Visit(source.Body), 

    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) :
                return Expression.Constant(value, node.Type);
            return node.Update(expression);