简体   繁体   English

在非基元/非结构对象上检查null后,“可空对象必须具有值”异常

[英]“Nullable object must have a value” exception after checking for null on a non-primitive/non-struct object

I'm getting Nullable object must have a value after checking for null on a regular object, after a null check. 我得到Nullable object must have a value after checking for null在一个常规对象上进行null检查后。 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? ). 我发现了各种各样的问题,主要是关于linq-to-sql,有相同的问题,但总是有可空的原始类型(如bool?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: customer类看起来像这样:

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

address property looks like this: address属性如下所示:

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. 我也不想在执行Lambda语句本身之前定义无数变量。

Please also note that above Lambda statement is part of a much bigger Lambda Where clause that contains a few more of such statements. 还请注意,上面的Lambda语句是一个更大的Lambda Where子句的一部分,它包含更多这样的语句。 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. 针对Activator类抛出的不是字符串,列表/数组或基本类型的所有内容。 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)); 我从这里得到了一个想法,并对它做了一些更改(基本上,忽略了所有不需要初始化的东西而不是Activator.CreateInstance(Type.GetType(property.PropertyType.Name));我使用了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.) 我甚至不确定原始问题中使用的版本是否有用或为什么有人想要使用它。)

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. 正如@Servy在评论中正确指出的那样,他们并没有被迫这样做,而且一般来说可能有一个技术原因没有这样做,但实际上人们倾向于在他们的查询表达式中使用这些条件并期望它们像它们在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. 我已经看到很多类似用法的问题,最后一个是LINQ to Entities条件给出了奇怪的结果 ,而“标准”评论/答案是 - 使用链接Where使用if或者某个谓词构建器。 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. 所以我最终得到了以下扩展方法,它使用ExpressionVisitor来修改查询表达式树。 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): 使用该扩展方法,您只需要在查询结束时插入.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();

The value of the expression customer.Address.Street must be evaluated to its value *before the query can be translated into SQL. 在将查询转换为SQL之前, 必须将表达式customer.Address.Street的值计算为其值*。 That expression cannot be left in the underlying SQL for the database to possibly, or possibly not, evaluate to a value. 该表达式不能留在数据库的基础SQL中,以便可能或不可能计算为值。 The query provider has to evaluate it unconditionally in order to determine what the SQL should look like. 查询提供程序必须无条件地对其进行评估,以确定SQL应该是什么样子。 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. 当然,有许多方法可以做到这一点,但是空检查逻辑确实需要在查询提供程序转换的表达式之外。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM