Я получаю Nullable object must have a value after checking for null для обычного объекта после нулевой проверки. Я встречал различные вопросы, в основном касающиеся linq-to-sql, с той же проблемой, но всегда с примитивными типами, допускающими значение NULL (как в bool? или DateTime?).

Строка, вызывающая исключение в моем случае, выглядит так:

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));, я даже не уверен, подойдет ли версия, использованная в исходном вопросе, и почему кто-то захочет ее использовать.)

1
Steffen Winkler 27 Апр 2016 в 17:06

2 ответа

Лучший ответ

Значение выражения customer.Address.Street должно быть оценено как его значение *, прежде чем запрос можно будет преобразовать в SQL. Это выражение нельзя оставить в базовом SQL, чтобы база данных могла или, возможно, не оценила значение. Поставщик запроса должен безоговорочно оценить его, чтобы определить, как должен выглядеть SQL. Итак, да, вам действительно нужно выполнить нулевую проверку вне выражения. Конечно, есть несколько способов сделать это, но логика проверки на null действительно должна находиться за пределами выражения, которое преобразует поставщик запроса.

1
Servy 27 Апр 2016 в 14:28

Вопреки тому, что я написал в комментариях, проблема в том, что поставщики запросов не даже пытаются уменьшить количество выражений предиката, удаляя постоянные части. Как правильно указал @Servy в комментариях, они не обязаны это делать, и, вообще говоря, может быть техническая причина не делать этого, но на самом деле люди склонны использовать такие условия в своих выражениях запросов и ожидают, что они будут работать как < strong> если они оцениваются в LINQ to Objects.

Я видел много вопросов с похожим использованием, последний из которых LINQ to Entities дает странные результаты, и "стандартный" комментарий / ответ - используйте цепочку Where с if или каким-нибудь построителем предикатов. Тогда я начинаю думать - хорошо, провайдеры этого не делают, так почему бы нам тогда не сделать это самим - в конце концов, мы разработчики и можем писать (некоторый) код. Итак, я получил следующий метод расширения, который использует ExpressionVisitor для изменения дерева выражения запроса. Я думал опубликовать его в связанном вопросе, но, поскольку я каким-то образом участвовал в этой теме, вот и все:

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();
2
Community 23 Май 2017 в 12:23