re: Locally Evaluatable Method Call in Linq Query

50 views
Skip to first unread message

Darren Kopp

unread,
Apr 5, 2011, 1:39:48 PM4/5/11
to re-moti...@googlegroups.com
as continued from here: https://groups.google.com/d/topic/nhibernate-development/PRTsDeVW7g4/discussion

Ok. In NHibernate's code they are calling PartialEvaluatingExpressionTreeVisitor.EvaluateIndependentSubtrees. Here's what i got from the debugger

Query Expression before evaluation
value(NHibernate.Linq.NhQueryable`1[DBI.Media.DatabaseFile]).Where(p => (p.ID == InternalUser.CurrentUser.Options.GetOptionValue(206).ToInt())).Select(p => p.ExternalID).FirstOrDefault()

Expression that was returned from evaluation:
value(NHibernate.Linq.NhQueryable`1[DBI.Media.DatabaseFile]).Where(p => (p.ID == value(hh2.SystemOptionSettingList).GetOptionValue(206).ToInt())).Select(p => p.ExternalID).FirstOrDefault()

I don't think the expression is processed any further because the query then always ends up failing when it tries to find something to handle the ToInt method.

I also checked and NHibernate's query processor is the CompoundExpressionTreeProcessor and contains the PartialEvaluatingExpressionTreeProcessor

Fabian Schmied

unread,
Apr 6, 2011, 5:07:26 AM4/6/11
to re-moti...@googlegroups.com, Darren Kopp
Hi Darren,

Your analysis shows that the evaluator is indeed called, and that it
does evaluate "InternalUser.CurrentUser.Options", but stops at the
call to "GetOptionValue(206)".
So, we have to investigate why it stops there.

One reason I can see for that behavior is the SystemOptionSettingList
class implementing IQueryable. re-linq's partial evaluator does not
evaluate method calls applied to IQueryable instances because such
instances normally represent (sub-)queries and thus cannot be executed
in-memory.

So, does that class implement IQueryable, by chance?

If not, I can only ask you to step into the expression visitors
(especially EvaluatableTreeFindingExpressionTreeVisitor) using the
debugger in order to find out why the partial evaluator decides not to
evaluate that MethodCallExpression.

Regards,
Fabian

> --
> You received this message because you are subscribed to the Google Groups
> "re-motion Users" group.
> To post to this group, send email to re-moti...@googlegroups.com.
> To unsubscribe from this group, send email to
> re-motion-use...@googlegroups.com.
> For more options, visit this group at
> http://groups.google.com/group/re-motion-users?hl=en.
>

Darren Kopp

unread,
Apr 6, 2011, 1:45:29 PM4/6/11
to re-moti...@googlegroups.com
Nice call Fabian, I didn't even think about if the collection implemented IQueryable. SystemOptionSettingList derives from a class in 3rd part framework that implements IQueryable. Now, the old nhibernate linq provider I know would error if you had multiple IQueryable because it didn't support it, but this query did work without throwing an error as it was able to evaluate locally. How is re-linq checking for the IQueryable? I could go look at the old linq provider and tell you exactly what they are doing, and perhaps we could enhance re-linq to support some situations where something may technically be IQueryable, it is an in memory object and can be evaluated?

Regardless, thank you very much for your help in getting to the bottom of this situation. I'm more than willing to help in any way to enhance re-linq to support this scenario if you are interested. If i recall re-linq is open source, so I can do the investigation work if you would like.

Fabian Schmied

unread,
Apr 11, 2011, 8:37:59 AM4/11/11
to re-moti...@googlegroups.com
Hi Darren,

> Nice call Fabian, I didn't even think about if the collection implemented
> IQueryable. SystemOptionSettingList derives from a class in 3rd part
> framework that implements IQueryable. Now, the old nhibernate linq provider
> I know would error if you had multiple IQueryable because it didn't support
> it, but this query did work without throwing an error as it was able to
> evaluate locally. How is re-linq checking for the IQueryable? I could go
> look at the old linq provider and tell you exactly what they are doing, and
> perhaps we could enhance re-linq to support some situations where something
> may technically be IQueryable, it is an in memory object and can be
> evaluated?

Let's discuss the problem with an example:

var queryable1 = session.Query<X>();
var queryable2 = session.Query<Y>();

from x in queryable1
from y in queryable2
select new { x, y }

This query results in an expression tree with two IQueryables, and in
this case, we want the second queryable to be a data source in the
same query as the first queryable:

SELECT x.*, y.*
FROM X x, Y y

When re-linq sees an IQueryable in the middle of an expression tree,
it always assumes that you want to treat that IQueryable as an
additional data source. And therefore, it does not partially evaluate
expressions using that IQueryable in-memory.

The question is, how to detect situations where you wouldn't want that behavior?

> Regardless, thank you very much for your help in getting to the bottom of
> this situation. I'm more than willing to help in any way to enhance re-linq
> to support this scenario if you are interested. If i recall re-linq is open
> source, so I can do the investigation work if you would like.

Sure, if you want to, take a look at the
EvaluatableTreeFindingExpressionTreeVisitor and the
PartialEvaluatingExpressionTreeVisitor. Those two visitors detect what
parts of an expression tree can be evaluated. If you have a good idea
of how to improve partial evaluation while still enabling multiple
data sources, we can discuss it here on the list.

Regards,
Fabian

Fabian Schmied

unread,
Apr 11, 2011, 8:39:08 AM4/11/11
to re-moti...@googlegroups.com
Oh, and, here's our svn:
"https://svn.re-motion.org/svn/Remotion/trunk/" (re-linq is in the
Relinq folder).

Darren Kopp

unread,
Apr 12, 2011, 12:53:16 PM4/12/11
to re-moti...@googlegroups.com
I haven't had time to look into this yet, but I have had it sitting around in my head, and I'm thinking that this actually couldn't be done in re-linq, but it could be done in nhibernate. I would like your opinion on this as I would put my understanding of linq providers in the median level, where yours would be in the high to expert level. 

So here's my thinking. When you are evaluating an expression tree and looking for subqueries (other IQueryable items), you really only care about ones that you can actually handle. This would mean basically only things that are NHQueryable or whatever they had. Anything else could be evaluated in memory. I'm not sure how re-linq parses the query tree, and I don't think re-linq would have enough information on it's own to know what type of IQueryable is one it can handle and what it can't. NHibernate, or any other implementor on the other hand, could.

Think that would work, or is even a good way to approach this? 

-Darren

Fabian Schmied

unread,
Apr 18, 2011, 3:17:05 AM4/18/11
to re-moti...@googlegroups.com

Yeah, I was thinking along similar terms. re-linq can't really decide
whether an IQueryable should be handled by the calling provider or be
executed in memory. The provider might be able to decide, for example
via a type check (though this is only a heuristic, really; if someone
uses a variable of type IQueryable that holds an instance of
NHQueryable, a type check wouldn't be able to detect that).

To enable this, re-linq would need to allow the provider to customize
what expressions to evaluate and what not. If you look at the code,
the decision in question is currently made in
EvaluatableTreeFindingExpressionTreeVisitor.VisitMethodCallExpression
and VisitMemberExpression. The visitor assumes that if a method call
(or member access) has a target object or argument of type IQueryable,
then that method call (member access) must not be evaluated in-memory.
(And this is transitive, i.e., expressions containing non-evaluable
expressions cannot be evaluated themselves.)

Now, that check for IQueryable would have to be made customizable so
that NHibernate could check for their own IQueryable implementation.
For example, one could move the
EvaluatableTreeFindingExpressionTreeVisitor.IsQueryableExpression
method to a strategy that is injected into
EvaluatableTreeFindingExpressionTreeVisitor via
PartialEvaluatingExpressionTreeVisitor,
PartialEvaluatingExpressionTreeProcessor, and
ExpressionTreeParser.CreateDefaultProcessor. A default implementation
of the strategy would perform the type check for IQueryable.
NHibernate could replace it with a type check for NHQueryable instead.
(With the problem that variables of type IQueryable would also be
executed in-memory, then, even if they actually held NHQueryable.)

There's another problem, though:
EvaluatableTreeFindingExpressionTreeVisitor currently assumes that any
ParameterExpression (and, therefore, any other expression nesting a
ParameterExpression), cannot be evaluated in memory. If we want to
allow executing queryables in-memory, this becomes a problem. Example:

from x in session.Query<X>()
where x.ID == myQueryable.First (y => y.ID)
select x

In this sample, "myQueryable.First (y => y.ID)" should probably be
evaluated in memory, assuming "myQueryable" is not an NHQueryable. It
contains a ParameterExpression, though, so the current implementation
would reject it.
To make this work, EvaluatableTreeFindingExpressionTreeVisitor would
also have to be changed to implement scoping of ParameterExpressions.
So, while a ParameterExpression on its own is not evaluable in memory,
the whole MethodCallExpression is evaluable (unless it hold parameters
from the outside).

I'd have to think about how to change
EvaluatableTreeFindingExpressionTreeVisitor to make this work, maybe
also take a look at how Matt Warren's partial evaluator does it. (We
can't just take his code since the licenses aren't compatible; but we
can take a look at the ideas it implements.)

If you yourself have a good idea, please let me know.

Regards,
Fabian

Reply all
Reply to author
Forward
0 new messages