问题
I'm getting Nullable object must have a value after checking for null
on a regular object, after a null check. I've found various questions, mostly regarding linq-to-sql, having the same problem but always with nullable primitive types (as in bool?
or DateTime?
).
The line causing the exception in my case looks like this:
myDataContext.Orders.Where(y => customer.Address == null || (string.IsNullOrEmpty(customer.Address.Street) || y.Customers.Addresses.Street == customer.Address.Street)))
customer
class looks like this:
public class Customer
{
private Address address = null;
public Address Address{get{return address;} set{address=value;}}
}
address
property looks like this:
public class Address
{
private string street = null;
public string Street{get{return street ;} set{street =value;}}
}
If I replace above code line with this:
string custStreet = null;
if (customer.Address != null)
{
custStreet = customer.Address.Street;
}
myDataContext.Orders.Where(y =>(customer.Address == null || (string.IsNullOrEmpty(custStreet) || y.Customers.Addresses.Street == custStreet)))
it runs fine. I don't undestand the reason for that. I also don't want to define countless variables before executing the Lambda statement itself.
Please also note that above Lambda statement is part of a much bigger Lambda Where
clause that contains a few more of such statements. I know I could work with Expression Trees but having coded this far, I really don't want to switch now.
edit
as the question was answered, I'm going to tell you how I worked around it: I build myself a recursive property initializer. Everything that is not a string, a list/array or a primitive type is thrown against the Activator
class. I got the idea from here and made a few changes to it (basically, ignore everything that doesn't need to be initialized and instead of Activator.CreateInstance(Type.GetType(property.PropertyType.Name));
I used Activator.CreateInstance(property.PropertyType));
I'm not even sure if the version used in the original question would work or why anyone would want to use it.)
回答1:
The value of the expression customer.Address.Street
must be evaluated to its value *before the query can be translated into SQL. That expression cannot be left in the underlying SQL for the database to possibly, or possibly not, evaluate to a value. The query provider has to evaluate it unconditionally in order to determine what the SQL should look like. So yes, you do need to perform the null check outside of the expression. There are of course any number of ways you could do so, but that null checking logic does need to be outside of the expression the query provider translates.
回答2:
Contrary to what I wrote in the comments, the problem is that query providers does not even try to reduce the predicate expressions by eliminating the constant parts. As @Servy correctly stated in the comments, they are not forced to do that, and speaking generally there might be a technical reason not doing it, but in reality people tend to use such conditions in their query expressions and expect them to work as if they are evaluated in LINQ to Objects.
I've seen many questions with similar usage, the last being LINQ to Entities conditionals give weird results, and the "standard" comment/answer is - use chained Where
with if
s or some predicate builder. Then I start thinking - ok, the providers don't do that, so why don't we do that ourselves then - after all, we are developers and can write (some) code. So I've ended up with the following extension method which uses ExpressionVisitor
to modify the query expression tree. I was thinking to post it to the linked question, but since I've get somehow involved in this thread, here you go:
public static class QueryableExtensions
{
public static IQueryable<T> ReduceConstPredicates<T>(this IQueryable<T> source)
{
var reducer = new ConstPredicateReducer();
var expression = reducer.Visit(source.Expression);
if (expression == source.Expression) return source;
return source.Provider.CreateQuery<T>(expression);
}
class ConstPredicateReducer : ExpressionVisitor
{
private int evaluateConst;
private bool EvaluateConst { get { return evaluateConst > 0; } }
private ConstantExpression TryEvaluateConst(Expression node)
{
evaluateConst++;
try { return Visit(node) as ConstantExpression; }
catch { return null; }
finally { evaluateConst--; }
}
protected override Expression VisitUnary(UnaryExpression node)
{
if (EvaluateConst || node.Type == typeof(bool))
{
var operandConst = TryEvaluateConst(node.Operand);
if (operandConst != null)
{
var result = Expression.Lambda(node.Update(operandConst)).Compile().DynamicInvoke();
return Expression.Constant(result, node.Type);
}
}
return EvaluateConst ? node : base.VisitUnary(node);
}
protected override Expression VisitBinary(BinaryExpression node)
{
if (EvaluateConst || node.Type == typeof(bool))
{
var leftConst = TryEvaluateConst(node.Left);
if (leftConst != null)
{
if (node.NodeType == ExpressionType.AndAlso)
return (bool)leftConst.Value ? Visit(node.Right) : Expression.Constant(false);
if (node.NodeType == ExpressionType.OrElse)
return !(bool)leftConst.Value ? Visit(node.Right) : Expression.Constant(true);
var rightConst = TryEvaluateConst(node.Right);
if (rightConst != null)
{
var result = Expression.Lambda(node.Update(leftConst, node.Conversion, rightConst)).Compile().DynamicInvoke();
return Expression.Constant(result, node.Type);
}
}
}
return EvaluateConst ? node : base.VisitBinary(node);
}
protected override Expression VisitConditional(ConditionalExpression node)
{
if (EvaluateConst || node.Type == typeof(bool))
{
var testConst = TryEvaluateConst(node.Test);
if (testConst != null)
return Visit((bool)testConst.Value ? node.IfTrue : node.IfFalse);
}
return EvaluateConst ? node : base.VisitConditional(node);
}
protected override Expression VisitMember(MemberExpression node)
{
if (EvaluateConst || node.Type == typeof(bool))
{
var expressionConst = node.Expression != null ? TryEvaluateConst(node.Expression) : null;
if (expressionConst != null || node.Expression == null)
{
var result = Expression.Lambda(node.Update(expressionConst)).Compile().DynamicInvoke();
return Expression.Constant(result, node.Type);
}
}
return EvaluateConst ? node : base.VisitMember(node);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (EvaluateConst || node.Type == typeof(bool))
{
var objectConst = node.Object != null ? TryEvaluateConst(node.Object) : null;
if (objectConst != null || node.Object == null)
{
var argumentsConst = new ConstantExpression[node.Arguments.Count];
int count = 0;
while (count < argumentsConst.Length && (argumentsConst[count] = TryEvaluateConst(node.Arguments[count])) != null)
count++;
if (count == argumentsConst.Length)
{
var result = Expression.Lambda(node.Update(objectConst, argumentsConst)).Compile().DynamicInvoke();
return Expression.Constant(result, node.Type);
}
}
}
return EvaluateConst ? node : base.VisitMethodCall(node);
}
}
}
With that extension method in place, all you need is to insert .ReduceConstPredicates()
at the end of your queries (before AsEnumerable()
, ToList
and similar):
var query = myDataContext.Orders
.Where(y => customer.Address == null || string.IsNullOrEmpty(customer.Address.Street) || y.Customers.Addresses.Street == customer.Address.Street)
.ReduceConstPredicates();
来源:https://stackoverflow.com/questions/36892232/nullable-object-must-have-a-value-exception-after-checking-for-null-on-a-non-p