Why ordinary laws in evaluating boolean expression does not fit into LINQ?
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 (insuranceNumbe开发者_StackOverflowrSearch == 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: The || (or) Operator in Linq with C#
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?
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:
- 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).
- You'll get an error that the expression can't be represented in NHibernate syntax.
- Only a limited portion of the query will be converted to NHibernate syntax; the rest will be evaluated by LINQ to Objects.
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);
精彩评论