Locally Evaluatable Method Call in Linq Query

74 views
Skip to first unread message

Darren Kopp

unread,
Mar 29, 2011, 11:37:35 PM3/29/11
to nhibernate-...@googlegroups.com
Ok, I know this is dev group, but this really is a dev question.

So I have linq query like so:

.Where(p => p.ID == hh2.InternalUser.CurrentUser.Options.GetOptionValue(206).ToInt())

which is giving me the error: System.NotSupportedException: Int32 ToInt(System.String

This is expected behavior since ToInt() is an extension method not registered with nhibernate. Although i shouldn't need to add a custom method generator for this as all of the data exists in memory and can be calculated in memory (this also worked in old linq provider) i tried adding a generator to handle the ToInt() and then it just complained about the GetOptionValue call.

Now, I know what's happening: when the linq query is being parsed, it's not correctly identifying that piece of code as being able to be evaluated locally and replacing the call in the linq query with the value, the problem is i don't know why. and to make things worse, i've spent about 3 hours unsuccessfully trying to make a failing test. 

Now in NHibernate, there seems to be 2 places where this is done, before the query is ever parsed using the re-motion nominator (PartialEvaluatingExpressionTreeVisitor) and in the select clause visitor with the nominator. NHibernate's nominator is very similar to the one from matt warren's linq tutorial though it is different, so it could be a problem with nhibernate's nominator, but at the same time this is in the where clause, not the select clause. I would guess that it would be re-motion's nominator that's missing it, but i don't know what their's looks like.

So, anyone have a guess as to what might be causing this or where it might be happening? Maybe Fabian can chime in on the re-motion side. I know how to fix my code, but at the same time this is something that shouldn't be happening in the provider.

Darren Kopp

unread,
Mar 29, 2011, 11:40:42 PM3/29/11
to nhibernate-...@googlegroups.com
oh yeah, i should mention

InternalUser == type
CurrentUser = static property (singleton)
GetOptionValue == instance method on InternalUser
ToInt == extension method

Fabian Schmied

unread,
Mar 30, 2011, 5:21:25 AM3/30/11
to nhibernate-...@googlegroups.com
Hi Darren,
 
Since you've asked me specifically: can you please post the whole query? I specifically need to know what "hh2" is.
 
I've just run the following query within re-linq's test suite:
var hh2 = new C1();
var query = QuerySource
.Where (p => p.ID == hh2.InternalUser.CurrentUser.Options.GetOptionValue (206).ToInt());

var parsed = QueryParser.GetParsedQuery (query.Expression);

Console.WriteLine (parsed);
(For the declarations I used, see below.)
 
In this test, re-linq correctly evaluates the expression to the right of the == sign in-memory.
 
re-linq can and should partially evaluate the expression if all data is available in memory. What you describe should only happen if hh2 is a parameter, e.g., if the Where clause is part of a subquery nested within an outer query.
If hh2 is actually an in-memory object, this might be a bug in re-linq. In that case, please follow up with us at http://groups.google.com/group/re-motion-users.
 
Regards,
Fabian
class C1
{
  public User InternalUser = new User ();
}

class User
{
  public CurrentUser CurrentUser = new CurrentUser ();
}

class CurrentUser
{
  public Options Options = new Options ();
}

class Options
{
  public Option GetOptionValue (int i)
  {
    return new Option ();
  }
}

class Option
{
}

static class OptionExt
{
  public static int ToInt (this Option o)
  {
    return 4711;
  }
}

Darren Kopp

unread,
Mar 30, 2011, 4:45:59 PM3/30/11
to nhibernate-...@googlegroups.com
hh2 is just a namespace.
query is session.Query<DatabaseFile>().Where(p => p.ID == hh2.InternalUser.CurrentUser.Options.GetOptionValue(206).ToInt()).Select(p => p.ExternalId).FirstOrDefault()

Everything is definitely in memory and this class is from the "old" system and not mapped in nhibernate or anything. CurrentUser gets the object from the HttpContext. I'm not sure how it's happening, but ultimately the expression tree is getting to nhibernate and they are looking for a registered method, which there isn't one, but it should have been replaced by the value earlier i'm guessing. The only thing i'm curious about is the web page this is on is running .net 4.0, but the InternalUser is a 3.5 dll, but again it works in the old linq provider, so i'm guessing it's just not being detected as locally evaluatable. I also couldn't get any tests to fail even by recreating the structure.

What i'm going to try to do today is build nhibernate from source and attach a debugger and look at the expression tree, anything that might be helpful that i could get you fabian?

example code for InternalUser would be like this.

class InternalUser {
    public UserOptions Options { get; set; }
    public static InternalUser CurrentUser { get { return Cache<InternalUser>["key"]; } }
}

class UserOptions
{
    IDictionary<int,string> Settings;
    public string GetOptionSetting(int option)
    {
        return Settings[option];
    }
}

static class StringExtensions
{
    public static int ToInt(this string value) {
        return int.Parse(value);
    }
}

Fabian Schmied

unread,
Apr 4, 2011, 4:33:33 AM4/4/11
to nhibernate-development
Hi Darren,

I've again tested this scenario using your sample declarations
(adapted a little to make it run), and in the re-linq test suite, the
method call is perfectly inlined.

This is the query:
QuerySource.Where (p => p.ID ==
hh2.InternalUser.CurrentUser.Options.GetOptionValue
(206).ToInt()).Select (p => p.ID).FirstOrDefault();

This is what re-linq makes of it:
ExecuteSingle<Int32> (from Cook p in TestQueryable<Cook>() where
([p].ID = 206) select [p].ID => FirstOrDefault(), True)

So, I really can't reproduce the issue. Maybe something in NHibernate
circumvents the partial evaluator?

> What i'm going to try to do today is build nhibernate from source and attach
> a debugger and look at the expression tree, anything that might be helpful
> that i could get you fabian?

Check the following items:

- Is
PartialEvaluatingExpressionTreeVisitor.EvaluateIndependentSubtrees
called on the where expression?
-- If yes, what's the result? If the result still holds the call to
ToInt (ie, it was not evaluated), this would probably be a bug in re-
linq. In that case, please follow up with us on "http://www.google.com/
url?sa=D&q=http://groups.google.com/group/re-motion-users" in order to
let us reproduce and fix the issue.
-- If not, check the QueryParser used by NHibernate.
QueryParser.ExpressionTreeParser.Processor should be a
CompoundExpressionTreeProcessor containing an instance of
PartialEvaluatingExpressionTreeProcessor. If it doesn't, this would be
an issue in NHibernate.

Regards,
Fabian

Darren Kopp

unread,
Apr 5, 2011, 1:40:22 PM4/5/11
to nhibernate-...@googlegroups.com
Reply all
Reply to author
Forward
0 new messages