Is it possible to use a custom extension method in a subquery with OOB ExpressionTreeParser?

218 views
Skip to first unread message

Alex Norcliffe

unread,
Dec 18, 2011, 3:21:30 PM12/18/11
to re-moti...@googlegroups.com
Hi there. I'm using Relinq build 1.13.122.1.

Here's an example query that currently parses fine into a SubQueryExpression:

items.Where(item => item.ChildrenProperty.Any())

ChildrenProperty implements IEnumerable.
However, in an effort to avoid having a property on my model that infers "children" which would force my model to have references back to the services that load those children, I've got a static extension method. So my query is actually more like:

items.Where(item => item.ChildrenExtensionMethod().Any())

However, that latter query throws an exception when being parsed by Relinq - because GetQueryOperatorExpression in Relinq's default ExpressionTreeParser always returns method call expressions unmodified whereas members that aren't operators are treated differently, so when it hits my method the parser continues up the expression tree in the subquery until it gets to "item" and I get the following error:

Remotion.Linq.Parsing.ParserException : Cannot parse expression 'item' as it has an unsupported type. Only query sources (that is, expressions that implement IEnumerable) and query operators can be parsed.
----> Remotion.Linq.Utilities.ArgumentTypeException : Expected a type implementing IEnumerable<T>, but found 'Umbraco.Cms.Web.Model.Content'.
Parameter name: expression

I've thought of an approach to get around this, which is to have my own ExpressionTreeParser (which I did anyway replicating the existing source to debug this) and have some kind of registry that says my "ChildrenExtensionMethod" is treated differently.

To summarise:
  • In the first example, I end up with "item.ChildrenCollection" as the MainFromClause of the sub query - which is fine.
  • In the second example, I want to end up with "item.ChildrenExtensionMethod" as the MainFromClause of a sub query, but at the moment the default providers ignore it and moan about "item" not being IEnumerable
I'd prefer not to maintain my own ExpressionTreeParser implementation, is there a way of doing this with OOB stuff?

Many thanks!
Alex

Alex Norcliffe
Lead Architect, Umbraco 5
@alex_norcliffe on Twitter, blogging at boxbinary.com

Gordon Watts

unread,
Dec 18, 2011, 11:48:21 PM12/18/11
to re-moti...@googlegroups.com
Hi Alex,
  What is the signature for your ChildrenExtensionMethod extension method? Is it IEnumerable?
 
  And to avoid loading would it be more efficient to have a ChildrenEmpty method that returns true/false – and that way avoid having to load everything?
 
  Finally, my guess is that tree parser doesn’t do well with extension methods. That might be code that has to be added. However, Fabian or someone else will have to answer this as you are past my detailed knowledge of re-linq on this question.
 
  That said, there is a plug-in model that allows you to run multiple expression tree parsers during the main parsing phase. In these you can manipulate the expression tree as you please. I’ve not used this facility myself, but you might be able to use that to get around this issue – though you’d have to replace ChildrenProperty with something else, I suppose, which won’t work.
 
  I’m impressed you can get away w/out having to implement your own ExpressionTreeParser. I use a custom one (actually, several) to translate the query into C++ code – and so very much have to have a custom ExpressionTreeParser. I guess you don’t need to do that level of translation?
 
    Cheers,
        Gordon.
--
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.

Fabian Schmied

unread,
Dec 19, 2011, 9:48:45 AM12/19/11
to re-moti...@googlegroups.com
Hi everyone,

I'll have to dig into the code before I can make a qualified statement
about this issue.
I should be able to take some time to investigate it tomorrow.

Cheers,
Fabian

Fabian Schmied

unread,
Dec 20, 2011, 3:41:04 AM12/20/11
to re-moti...@googlegroups.com
Hi again,

[...]

> items.Where(item => item.ChildrenExtensionMethod().Any())
>
> However, that latter query throws an exception when being parsed by Relinq -
> because GetQueryOperatorExpression in Relinq's default ExpressionTreeParser
> always returns method call expressions unmodified whereas members that
> aren't operators are treated differently, so when it hits my method the
> parser continues up the expression tree in the subquery until it gets to
> "item" and I get the following error:

[...]

Yes, you're right. re-linq currently assumes that all methods
occurring in a query operator call chain should be treated like query
operators (Where, Select, etc.). In your case this means that re-linq
regards "item" as the start of the query operator chain and, since
item doesn't implement IEnumerable<T>, throws an exception. Even if
item implemented IEnumerable<T>, an exception would be thrown that
ChildrenExtensionMethod() "is currently not supported", unless you've
registered a custom node parser for that method.

To support this out of the box, re-linq's query operator detection
semantics would have to be changed somehow. I'll discuss this in a
separate thread (and/or blog post).

Now, back to your scenario. Yes, you can tweak re-linq to support this
without implementing a custom ExpressionTreeParser. You just need to
replace the method call with a different expression before the
ExpressionTreeParser tries to parse it. The easiest way to do so is to
implement a light-weight expression transformer (implementing
IExpressionTransformer<MethodCallExpression>) that you register when
you create your QueryParser. See here for an explanation on how to do
that: "https://www.re-motion.org/blogs/mix/2011/04/29/re-linq-customizability-explained/".
The transformer should detect calls to "ChildrenExtensionMethod()" and
replace those with a different expression. I'd probably define a
custom expression type derived from ExtensionExpression for this
("RelationAccessExpression" or something like that).

Gordon: You wrote you were surprised that Alex didn't need to
implement his own ExpressionTreeParser, since you were using several.
I think you meant Expression _visitors_, right? Of these I guess Alex
also has a few :)

Regards,
Fabian

Gordon Watts

unread,
Dec 20, 2011, 11:35:55 AM12/20/11
to re-moti...@googlegroups.com
Hi,
Shoot - yes, thanks, that is exactly what I meant. My code has been stable
for the last few months so I'm already starting to forget bits of it!

Cheers,
Gordon.

-----Original Message-----
From: Fabian Schmied
Sent: Tuesday, December 20, 2011 2:41 AM
To: re-moti...@googlegroups.com
Subject: Re: [re-motion-users] Is it possible to use a custom extension
method in a subquery with OOB ExpressionTreeParser?

Hi again,

[...]

[...]

Regards,
Fabian

--

Fabian Schmied

unread,
Dec 20, 2011, 1:48:17 PM12/20/11
to re-motion Users
> To support this out of the box, re-linq's query operator detection
> semantics would have to be changed somehow. I'll discuss this in a
> separate thread (and/or blog post).

Here's the blog post: "https://www.re-motion.org/blogs/mix/2011/12/20/
re-linq-how-to-recognize-if-a-method-is-a-query-operator/", and here's
the discussion: "http://groups.google.com/group/re-motion-dev/
browse_thread/thread/f9f6198bbbecd796".

I'd be very interested in hearing your opinions as LINQ provider
implementers.

Fabian

Reply all
Reply to author
Forward
0 new messages