Search code examples
c#performancelinqnhibernateexpression-trees

How do I cache compiled LINQ query (not results)?


It looks like each time I query something with LINQ NHibernate builds that query from scratch:

dotTrace report

The code looks like

session.Query<User>().Where(x => ids.Contains(x.Id)).ToFuture();

Is it possible to avoid recompiling it?

Also the same question about caching QueryOver/Criteria queries (not so critical but it may still fit the scope).


Solution

  • Particularly this case was caused by that the access to ids (int[]) here

    session.Query<User>().Where(x => ids.Contains(x.Id)).ToFuture();
    

    was transformed into MemberAccessExpression (not ConstantExpression) and NHibernate had to evaluate it. Though ids was never changed it still was captured into a closure generated class (like DisplayClass<>.ids).

    I optimized this case by making my own version of PartialEvaluatingExpressionTreeVisitor:

        protected Expression EvaluateSubtree(Expression subtree)
        {
            ArgumentUtility.CheckNotNull(nameof(subtree), subtree);
            var memberExpression = subtree as MemberExpression;
            if (memberExpression != null)
            {
                Expression constant;
                if (TryEvaluateMember(memberExpression, out constant)) return constant;
            }
    
            if (subtree.NodeType != ExpressionType.Constant)
                throw new NHibernateExpressionOptimizerException(subtree);
            ConstantExpression constantExpression = (ConstantExpression)subtree;
            IQueryable queryable = constantExpression.Value as IQueryable;
            if (queryable != null && queryable.Expression != constantExpression)
                return queryable.Expression;
            return constantExpression;
        }
    
        bool TryEvaluateMember(MemberExpression memberExpression, out Expression constant)
        {
            constant = null;
            ConstantExpression c = memberExpression.Expression == null ? Expression.Constant(null) : EvaluateSubtree(memberExpression.Expression) as ConstantExpression;
            if (c == null) return false;
            var fieldInfo = memberExpression.Member as FieldInfo;
            if (fieldInfo != null)
            {
                constant = Expression.Constant(ReflectorReadFieldDelegate(fieldInfo, c.Value));
                return true;
            }
    
            var propertyInfo = memberExpression.Member as PropertyInfo;
            if (propertyInfo != null)
            {
                constant = Expression.Constant(ReflectorReadPropertyDelegate(propertyInfo, c.Value));
                return true;
            }
            return false;
        }
    

    The reflector delegates use a kind of cached reflection.emit magic.