views:

340

answers:

4

In such a code:

if (insuranceNumberSearch == null 
     ? true  
     : ei.InsuranceNumber.Contains(insuranceNumberSearch.Trim())) 
   doSomething();

where insuranceNumberSearch is null, remaining expression is not null while in following code:

var q = from ei in session.Linq<EmployeeInsurance>()
        where insuranceNumberSearch == null 
                ? true 
                : ei.InsuranceNumber.Contains(insuranceNumberSearch.Trim())
        select ei;

all section of expression is evaluated regardless of insuranceNumberSearch is null or is not null.

I'm using LINQ to NHibernate

UPDATE:

Unfortunately I have put the first snippet wrong. The correct is:

if (insuranceNumberSearch == null || (insuranceNumberSearch != null && ei.InsuranceNumber.Contains(insuranceNumberSearch.Trim()))
doSomething();

or

bool b1 = insuranceNumberSearch == null ? true : ei.InsuranceNumber.Contains(insuranceNumberSearch.Trim());
if (b1)
doSomething();

In both of above when insuranceNumberSearch is null, remaining expressions are not evaluated any more. If such a behavior does not exists, insuranceNumberSearch.Trim() will cause a reference object is null exception. Sadly LINQ (or maybe LINQ-to-NHibernate) does not obey such a nice behavior and evaluate all of expression even when insuranceNumberSearch is null and results in error.

UPDATE 2: I found a similar question: http://stackoverflow.com/questions/772261/the-or-operator-in-linq-with-c

+5  A: 

Beat me, but wth wouldn't you use

if (
     (insuranceNumberSearch == null) ||
     ei.InsuranceNumber.Contains(insuranceNumberSearch.Trim()))
  doSomething();

in your statement, be it in the LINQ expression or not?

Benjamin Podszun
My guess is that, if NHibernate doesn't support shortcut operators, this expression is going to have the same problem, and the poster was trying to force the intended shortcut behavior to be more apparent by using a conditional expression instead.
BlueMonkMN
Quite possible, although I have to admit that I'm ignorant how both variants of the expression would be translated - and I'd expect better support for the normal/sane approach using plain OR above. You did get a +1 from me though for your ideas and the sample. :)
Benjamin Podszun
plz see my UPDATE.
afsharm
A: 

Why you wrote a

insuranceNumberSearch == null 
            ? true 
            : ei.InsuranceNumber.Contains(insuranceNumberSearch.Trim())

but not

insuranceNumberSearch == null || ei.InsuranceNumber.Contains(insuranceNumberSearch.Trim())
Dennis Cheung
Because when `insuranceNumberSearch` is null, `insuranceNumberSearch.Trim()` will cause `object reference is null` error.
afsharm
OR are lazy, if first part is true, the second part will not be executed.
Dennis Cheung
+3  A: 

As demonstrated by this code, it's not LINQ's problem. This code is similar to yours, but it does not evaluate both sides of the condition in the LINQ expression:

class Program
{
  class MyClass
  {
     public string value;
     public MyClass(string value) { this.value = value; }
     public bool Contains(char elem)
     {
        Console.WriteLine("Checking if {0} contains {1}", value, elem);
        return value.Contains(elem);
     }
  }

  static void Main(string[] args)
  {
     var mc = new MyClass[2];
     mc[0] = new MyClass("One");
     mc[1] = new MyClass(null);
     var q = from i in mc where i.value == null ? true : i.Contains('O') select i;
     foreach (MyClass c in q)
        Console.WriteLine(c.value == null ? "null" : c.value);
  }
}

It's possible the expression evaluator for LINQ to NHibernate does not perform shotcut conditional operations the way LINQ to Objects does.

The output of the program is:

Checking if One contains O
One
null

Keep in mind that LINQ is a way of representing arbitrary expressions for conversion into other syntaxes. As I understand it, LINQ itself would not evaluate the expression, NHibernate would (whatever that is). So LINQ just converts the expression you provide into an expression compatible with NHibernate. If NHibernate doesn't have a means of representing shortcut conditional operations, I can imagine one of 3 things occurring:

  1. NHibernate will evaluate the expression its own way (just like LINQ to SQL will always shortcut AND operations even if you use the non-shortcutting AND operator from VB.NET).
  2. You'll get an error that the expression can't be represented in NHibernate syntax.
  3. Only a limited portion of the query will be converted to NHibernate syntax; the rest will be evaluated by LINQ to Objects.
BlueMonkMN
But I can not use any type other than System.String
afsharm
I'm not suggesting that you write the code differently (to solve the problem), just trying to explain why it worked the way it did (answering the question).
BlueMonkMN
My suggestion for a solution would be similar to the solution for the other question you have linked to: Split the collection into items where insuranceNumberSearch == null and other items where it is not null and only use LINQ to NHibernate on the second collection. Is that possible?
BlueMonkMN
+1  A: 

It seems that the problem is in the NHibernate provider for LINQ - for LINQ to objects (which does just a simple syntactic transformation of query to method calls) the law would hold as expected. The problem is that when working with expression trees, the provider can do any modification to your code.

What may be even worse - the target execution environment may not support the exact semantics of some C# operations. For example, it may not have the same implementation of floating point arithmetics.

In your example, it seems that NHibernate doesn't support short-circuiting behavior. It's not clear to me why that would be a problem - it can evaluate the second part of the expression, but the result should be the same.

Anyway, if short-circuiting operators cause problems to the provider, you'll probably need to split the query into two:

var q = 
  insuranceNumberSearch == null 
    ? session.Linq<EmployeeInsurance>() 
    : (from ei in session.Linq<EmployeeInsurance>() 
       where ei.InsuranceNumber.Contains(insuranceNumberSearch.Trim()) 
       select ei); 
Tomas Petricek
See my comment on the question in response to yours: The outcome is not the same if you try to use `insuranceNumberSearch.Trim()` for `insuranceNumberSearch == null`.
Benjamin Podszun
@Tomas, your solution will work but my real use of this query requires more than one parameter (insuranceNumberSearch). What about when I have 5 parameter? Then I must create 32 queries based on being null of each of 5 parameters! Indeed I can use HQL instead but I started to use NHibernate-to-LINQ because of its simplicity.
afsharm