While reading an article on Entity Framework performance, I came across this piece of information:
Secondly, the problem [SQL Server won’t reu
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)
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.