Rewriting a LINQ Expression query to enable caching SQL Execution Plan

后端 未结 2 1091
攒了一身酷
攒了一身酷 2020-12-31 18:14

While reading an article on Entity Framework performance, I came across this piece of information:

Secondly, the problem [SQL Server won’t reu

相关标签:
2条回答
  • 2020-12-31 18:43

    After a lot of trial and error, we found you can still force Entity Framework to recognise convertedId as a parameter by slightly changing how we pass it in:

    ....
    
    var convObj = new
    {
        id = convertedId
    };
    var rightExp = Expression.Convert(Expression.Property(Expression.Constant(convObj), "id"), convertedId.GetType());
    
    var whereExpression = Expression.Lambda<Func<T, bool>>
        (
        Expression.Equal(
            Expression.Property(
                itemParameter,
                prop.Name
                ),
            rightExp
            ),
        new[] { itemParameter }
        );
    
    return Get<T>().Where(whereExpression);
    

    Which causes the generated SQL to use the same parameter (and code) for any given id:

    WHERE [Extent1].[Id] = @p__linq__0 
    

    The query in question that we were dealing with takes a long time to generate the execution plan, so we saw a significant decrease in execution time for accessing new IDs (from 3~4 seconds down to ~300 milliseconds)

    0 讨论(0)
  • 2020-12-31 18:55

    Let me recap.

    You are building Expression<Func<T, bool>> like this

    var item = Expression.Parameter(typeof(T), "item");
    var left = Expression.Property(item, idPropertyName);
    Expression right = ...;
    var body = Expression.Equal(left, right);
    var predicate = Expression.Lambda<Func<T, bool>>(body, item);
    

    and the question is what should be used for right in order to make EF not treating it as a constant.

    Apparently primitive value like

    var right = Expression.Convert(Expression.Constant(convertedId), left.Type);
    

    doesn't work, so the solution is to provide a property of some class instance. You solved it by using anonymous type, but of course there are many other ways to do that.

    For instance, using a closure (like it would have been if you were not creating the expression manually)

    Expression<Func<object>> closure = () => convertedId;
    var right = Expresion.Convert(closure.Body, left.Type);
    

    or Tuple<T> instance (a bit verbose, but eliminates Expression.Convert)

    var tuple = Activator.CreateInstance(
        typeof(Tuple<>).MakeGenericType(left.Type), convertedId);
    var right = Expression.Property(Expression.Constant(tuple), "Item1");
    

    etc.

    0 讨论(0)
提交回复
热议问题