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

时光总嘲笑我的痴心妄想 提交于 2020-01-19 06:14:28

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!