在非原始/非结构对象上检查null之后,“可空对象必须具有值”exception

我得到Nullable object must have a value after checking for null在常规对象上进行null检查后返回值。 我发现了各种各样的问题,主要是关于linq-to-sql,有相同的问题,但总是有可空的原始类型(如bool?DateTime? )。

在我的情况下导致exception的行看起来像这样:

 myDataContext.Orders.Where(y => customer.Address == null || (string.IsNullOrEmpty(customer.Address.Street) || y.Customers.Addresses.Street == customer.Address.Street))) 

customer类看起来像这样:

 public class Customer { private Address address = null; public Address Address{get{return address;} set{address=value;}} } 

address属性如下所示:

 public class Address { private string street = null; public string Street{get{return street ;} set{street =value;}} } 

如果我用以下代码替换上面的代码行:

 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))) 

它运行正常。 我不明白这个原因。 在执行Lambda语句本身之前,我也不想定义无数变量。

还请注意,上面的Lambda语句是一个更大的Lambda Where子句的一部分,它包含更多这样的语句。 我知道我可以使用表达式树,但编码了这么远,我真的不想现在切换。

编辑

当问题得到解答时,我将告诉你我是如何解决它的:我自己构建了一个递归属性初始化器。 对Activator类抛出的不是字符串,列表/数组或基本类型的所有内容。 我从这里得到了一个想法,并对它做了一些更改(基本上,忽略了所有不需要初始化的东西,而不是Activator.CreateInstance(Type.GetType(property.PropertyType.Name));我使用了Activator.CreateInstance(property.PropertyType)); 我甚至不确定原始问题中使用的版本是否可行或者为什么有人想要使用它。)

在将查询转换为SQL之前, 必须将表达式customer.Address.Street的值计算为其值*。 该表达式不能留在数据库的基础SQL中,可能或可能不会计算为值。 查询提供程序必须无条件地对其进行评估,以确定SQL应该是什么样子。 所以是的,你需要在表达式之外执行空值检查。 当然,有许多方法可以做到这一点,但是空检查逻辑确实需要在查询提供程序转换的表达式之外。

与我在评论中写的相反,问题是查询提供程序甚至没有尝试通过消除常量部分来减少谓词表达式。 正如@Servy在评论中正确指出的那样,他们并没有被迫这样做,并且通常说可能有技术原因没有这样做,但实际上人们倾向于在他们的查询表达式中使用这样的条件并期望它们像它们在LINQ to Objects中进行评估。

我已经看到很多类似用法的问题,最后一个是LINQ to Entities条件给出了奇怪的结果 ,而“标准”注释/答案是 – 使用链接Where使用if或者某个谓词构建器。 然后我开始思考 – 好吧,提供商不这样做,那么为什么我们不这样做呢 – 毕竟,我们是开发人员,可以写(某些)代码。 所以我最终得到了以下扩展方法,它使用ExpressionVisitor来修改查询表达式树。 我想把它发布到链接的问题,但是因为我已经以某种方式参与了这个主题,所以你走了:

 public static class QueryableExtensions { public static IQueryable ReduceConstPredicates(this IQueryable source) { var reducer = new ConstPredicateReducer(); var expression = reducer.Visit(source.Expression); if (expression == source.Expression) return source; return source.Provider.CreateQuery(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); } } } 

使用该扩展方法,您只需要在查询结束时插入.ReduceConstPredicates() (在AsEnumerable()ToList和类似之前):

 var query = myDataContext.Orders .Where(y => customer.Address == null || string.IsNullOrEmpty(customer.Address.Street) || y.Customers.Addresses.Street == customer.Address.Street) .ReduceConstPredicates();