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.
>
> 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
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