"Nullable object must have a value" 在 non-primitive/non-struct 对象上检查 null 后出现异常

"Nullable object must have a value" exception after checking for null on a non-primitive/non-struct object

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

在我的案例中导致异常的行如下所示:

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

customer class 看起来像这样:

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 子句的一部分,该子句包含更多此类语句。我知道我可以使用表达式树,但是编码到这里为止,我现在真的不想切换。

编辑

随着问题的回答,我将告诉您我是如何解决这个问题的:我为自己构建了一个递归 属性 初始化器。不是字符串、list/array 或原始类型的所有内容都会针对 Activator class 抛出。我从 here 得到了这个想法并对它做了一些改变(基本上,忽略所有不需要初始化的东西而不是 Activator.CreateInstance(Type.GetType(property.PropertyType.Name)); 我用 Activator.CreateInstance(property.PropertyType)); 我什至没有确定原始问题中使用的版本是否有效,或者为什么有人想要使用它。)

表达式 customer.Address.Street 的值必须 计算为它的值 * 才能将查询转换为 SQL。该表达式不能留在基础 SQL 中,以便数据库可能或可能不会计算出一个值。查询提供者必须无条件地评估它以确定 SQL 应该是什么样子。所以是的,您确实需要在表达式的外部 执行空值检查。当然,您可以通过多种方式做到这一点,但是空值检查逻辑 确实 需要在查询提供程序翻译的表达式之外。

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

我见过很多类似用法的问题,最后一个是 ,"standard" comment/answer 是 - use chained Where with ifs 或一些谓词生成器。然后我开始思考 - 好吧,供应商不这样做,那我们为什么不自己做 - 毕竟,我们是开发人员并且可以编写(一些)代码。所以我最终得到了以下扩展方法,它使用 ExpressionVisitor 来修改查询表达式树。我正在考虑 post 它到链接的问题,但由于我以某种方式参与了这个线程,所以你去:

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

使用该扩展方法,您只需在查询末尾插入 .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();