You can allow your users to register parsers for their own query operators (MyList<T>.Contains()) by exposing the MethodCallExpressionNodeTypeRegistry.Michael
I think you were right in your initial post. As Stefan said, the idea
was to be able to translate all calls to Contains methods on
IEnumerables to a single result operator, since those usually have the
same semantics. String.Contains was already exempt (as this has a
different semantics), and IDictionary.Contains (which is really a
ContainsKey) should be exempt as well. I've created an issue here:
"https://www.re-motion.org/jira/browse/RM-3753".
The problem is, of course, inherent with our current detection of
"Contains-like" result operators. (Which was, btw, requested by
NHibernate.) The ContainsExpressionNode parser handles every method
called "Contains" that has only one argument (in addition to the
source sequence) and is declared on an IEnumerable.
IDictionary.Contains is one example of how this "fuzzy matching" can
go wrong.
> I got impatient and solved it by creating an INodeTypeProvider that
> returns false when IsRegistered is called for IDictionary.Contains.
> NHibernate is now using .93. If there are any better suggestions, I'd
> be happy to hear them.
This is similar to what I'd have suggested in this case.
If I implemented this in re-linq, I would add a
ContainsKeyResultOperator and a ContainsKeyExpressionNode parser (that
creates the ContainsKeyResultOperator). This has the advantage that it
also works at the top level of a query (whereas leaving it a
MethodCallExpression only works if the call to ContainsKey is embedded
within a query).
E.g., Orders.Items.Contains (12); // where Orders.Items is IDictionary
Without an expression node parser for the Contains method, this query
will fail to work with re-linq.
However, this might not be an issue for you if you only need those
Contains methods to work in where conditions and similar places.
(BTW, I wonder how you're detecting calls to IDictionary.Contains -
it's not easy to infer whether a MethodInfo is an implementation of a
specific interface method. So, it's difficult to get reflection code
to handle all implementations of IDictionary.Contains the same way.)
> The code for implementing the Aggregate operator is a bit more
> complicated now. I have a couple questions.
>
> 1. Are you aware of any providers that actually implement the
> Aggregate operator at the database level?
No. Not even Linq to SQL does that. (Haven't checked Linq to Entities, though.)
> 2. Is there a simpler way to perform this result operator processing?
Let me summarize what your code does:
1 - You're retransforming the accumulating Lambda back to the Func the
user specified.
2 - You build an Aggregate method call expression that mimics what the
user typed originally, but using Enumerable.Aggregate (not
Queryable.Aggregate).
3 - You add that method call expression as a "list transformer" to the HQL tree.
In other words, the first two steps effectively undo the
simplification the re-linq parser made.
I'm assuming for now that a list transformer is a LambdaExpression
that is executed on the result of an HQL query in-memory. (If not, I
wonder if undoing re-linq's simplifications really helps. After all,
the expression would have to be analyzed again in another place.)
If I'm right about the in-memory part, this is very similar to what
AggregateResultOperator.ExecuteInMemory does. Check out the code here:
"https://svn.re-motion.org/svn/Remotion/trunk/Remotion/Data/Linq/Clauses/ResultOperators/AggregateResultOperator.cs".
As you can see, the code uses the
ReverseResolvingExpressionTreeVisitor instead of the
ReplacingExpressionTreeVisitor; this simplifies step 1 a bit.
If you only execute Aggregate in-memory, there'd be another option:
replace the AggregateResultOperator with your own version that keeps
the original aggregating func.
This means:
- Add an AggregateInMemoryResultOperator class.
- Add an AggregateInMemoryExpressionNode parser class that simply
creates an instance of AggregateInMemoryResultOperator.
- Where you create your customized QueryParser, register your custom
node type (see "https://www.re-motion.org/jira/browse/RM-3721") for
the Enumerable/Queryable.Aggregate methods.
Then, you could avoid step 1 entirely.
About step 2 - building the Aggregate method call - I fear, you can't
really get rid of that. You can't reuse the expression from the
original LINQ query (it has the wrong source sequence, might use
Queryable.Aggregate, etc.). You could provide a few helper methods to
aid with generation of expressions, but I can't think of much more
simplification. (Maybe you can get rid of the Cast by calling a
different closed version of the generic Aggregate method. I.e.,
Aggregate<T> - where T is the input item type - rather than
Aggregate<object>. But that depends on how the list transformer works;
i.e., if it gives an IEnumerable<T> or only an IEnumerable.)
Regards,
Fabian