лямбда-выражение дерева не может содержать нулевой оператор распространения

Вопрос: строка price = co?.price ?? 0, в следующем коде дает мне указанную выше ошибку. но если я удалю ? из co.?, он будет работать нормально. Я пытался следовать этому примеру MSDN где они используют ? в строке select new { person.FirstName, PetName = subpet?.Name ?? String.Empty }; Итак, похоже, мне нужно понять, когда использовать ? с ??, а когда нет.

Ошибка:

лямбда-выражение дерева не может содержать нулевой оператор распространения

public class CustomerOrdersModelView
{
    public string CustomerID { get; set; }
    public int FY { get; set; }
    public float? price { get; set; }
    ....
    ....
}
public async Task<IActionResult> ProductAnnualReport(string rpt)
{
    var qry = from c in _context.Customers
              join ord in _context.Orders
                on c.CustomerID equals ord.CustomerID into co
              from m in co.DefaultIfEmpty()
              select new CustomerOrdersModelView
              {
                  CustomerID = c.CustomerID,
                  FY = c.FY,
                  price = co?.price ?? 0,
                  ....
                  ....
              };
    ....
    ....
 }

person nam    schedule 21.06.2017    source источник
comment
Пожалуйста, опубликуйте ошибку ...   -  person Willem Van Onsem    schedule 21.06.2017
comment
Чувак, я бы хотел, чтобы C # поддерживал это!   -  person nawfal    schedule 14.02.2018


Ответы (4)


В приведенном вами примере используется LINQ to Objects, где неявные лямбда-выражения в запросе преобразуются в делегаты ... тогда как вы используете EF или аналогичный, с IQueryable<T> запросами, где лямбда-выражения преобразуются в деревья выражений. Деревья выражений не поддерживают условный оператор null (или кортежи).

Просто сделайте это по-старому:

price = co == null ? 0 : (co.price ?? 0)

(Я считаю, что оператор объединения с нулевым значением подходит для дерева выражений.)

person Jon Skeet    schedule 21.06.2017
comment
Если вы используете Dynamic LINQ (System.Linq.Dynamic.Core), вы можете использовать метод np(). См. github.com/StefH/System.Linq.Dynamic.Core/wiki / NullPropagation - person Stef Heyenrath; 04.08.2019
comment
@Jon Может ли LINQ преобразовать это в SQL или это приведет к тому, что фильтрация будет происходить в памяти? - person Dani Mazahreh; 18.07.2021
comment
@DaniMazahreh: Как я уже сказал, деревья выражений не поддерживают условный оператор null, поэтому я ожидал, что вы получите ошибку компиляции. - person Jon Skeet; 18.07.2021

В коде, на который вы ссылаетесь, используется List<T>. List<T> реализует IEnumerable<T>, но не IQueryable<T>. В этом случае проекция выполняется в памяти и ?. работает.

Вы используете какой-то IQueryable<T>, который работает совсем по-другому. Для IQueryable<T> создается представление проекции, и ваш поставщик LINQ решает, что с ним делать во время выполнения. По причинам обратной совместимости здесь нельзя использовать ?..

В зависимости от вашего поставщика LINQ вы можете использовать обычный . и по-прежнему не получать никаких NullReferenceException.

person Community    schedule 21.06.2017
comment
@hvd Не могли бы вы объяснить, почему это требуется для обратной совместимости? - person jag; 24.11.2017
comment
@jag Все поставщики LINQ, которые уже были созданы до введения ?., не были бы готовы обрабатывать ?. каким-либо разумным способом. - person ; 24.11.2017
comment
Но ?. это новый оператор нет? Таким образом, старый код не будет использовать ?. и, следовательно, не будет сломан. Поставщики Linq не готовы обрабатывать многие другие вещи, такие как методы CLR. - person jag; 09.12.2017
comment
@jag Верно, старый код в сочетании со старыми поставщиками LINQ не пострадает. Старый код не будет использовать ?.. Новый код может использовать старые поставщики LINQ, которые готовы обрабатывать методы CLR, которые они не распознают (путем создания исключения), поскольку они хорошо вписываются в существующую объектную модель дерева выражений. Абсолютно новые типы узлов дерева выражений не подходят. - person ; 09.12.2017
comment
Учитывая количество исключений, уже созданных поставщиками LINQ, вряд ли это будет стоящий компромисс - мы раньше не поддерживали, поэтому мы бы никогда не могли - person NetMage; 04.04.2019

Хотя дерево выражений не поддерживает распространение NULL в C # 6.0, мы можем создать посетителя, который изменяет дерево выражений для безопасного распространения NULL, как это делает оператор!

Вот мой:

public class NullPropagationVisitor : ExpressionVisitor
{
    private readonly bool _recursive;

    public NullPropagationVisitor(bool recursive)
    {
        _recursive = recursive;
    }

    protected override Expression VisitUnary(UnaryExpression propertyAccess)
    {
        if (propertyAccess.Operand is MemberExpression mem)
            return VisitMember(mem);

        if (propertyAccess.Operand is MethodCallExpression met)
            return VisitMethodCall(met);

        if (propertyAccess.Operand is ConditionalExpression cond)
            return Expression.Condition(
                    test: cond.Test,
                    ifTrue: MakeNullable(Visit(cond.IfTrue)),
                    ifFalse: MakeNullable(Visit(cond.IfFalse)));

        return base.VisitUnary(propertyAccess);
    }

    protected override Expression VisitMember(MemberExpression propertyAccess)
    {
        return Common(propertyAccess.Expression, propertyAccess);
    }

    protected override Expression VisitMethodCall(MethodCallExpression propertyAccess)
    {
        if (propertyAccess.Object == null)
            return base.VisitMethodCall(propertyAccess);

        return Common(propertyAccess.Object, propertyAccess);
    }

    private BlockExpression Common(Expression instance, Expression propertyAccess)
    {
        var safe = _recursive ? base.Visit(instance) : instance;
        var caller = Expression.Variable(safe.Type, "caller");
        var assign = Expression.Assign(caller, safe);
        var acess = MakeNullable(new ExpressionReplacer(instance,
            IsNullableStruct(instance) ? caller : RemoveNullable(caller)).Visit(propertyAccess));
        var ternary = Expression.Condition(
                    test: Expression.Equal(caller, Expression.Constant(null)),
                    ifTrue: Expression.Constant(null, acess.Type),
                    ifFalse: acess);

        return Expression.Block(
            type: acess.Type,
            variables: new[]
            {
                caller,
            },
            expressions: new Expression[]
            {
                assign,
                ternary,
            });
    }

    private static Expression MakeNullable(Expression ex)
    {
        if (IsNullable(ex))
            return ex;

        return Expression.Convert(ex, typeof(Nullable<>).MakeGenericType(ex.Type));
    }

    private static bool IsNullable(Expression ex)
    {
        return !ex.Type.IsValueType || (Nullable.GetUnderlyingType(ex.Type) != null);
    }

    private static bool IsNullableStruct(Expression ex)
    {
        return ex.Type.IsValueType && (Nullable.GetUnderlyingType(ex.Type) != null);
    }

    private static Expression RemoveNullable(Expression ex)
    {
        if (IsNullableStruct(ex))
            return Expression.Convert(ex, ex.Type.GenericTypeArguments[0]);

        return ex;
    }

    private class ExpressionReplacer : ExpressionVisitor
    {
        private readonly Expression _oldEx;
        private readonly Expression _newEx;

        internal ExpressionReplacer(Expression oldEx, Expression newEx)
        {
            _oldEx = oldEx;
            _newEx = newEx;
        }

        public override Expression Visit(Expression node)
        {
            if (node == _oldEx)
                return _newEx;

            return base.Visit(node);
        }
    }
}

Он проходит следующие тесты:

private static string Foo(string s) => s;

static void Main(string[] _)
{
    var visitor = new NullPropagationVisitor(recursive: true);

    Test1();
    Test2();
    Test3();

    void Test1()
    {
        Expression<Func<string, char?>> f = s => s == "foo" ? 'X' : Foo(s).Length.ToString()[0];

        var fBody = (Expression<Func<string, char?>>)visitor.Visit(f);

        var fFunc = fBody.Compile();

        Debug.Assert(fFunc(null) == null);
        Debug.Assert(fFunc("bar") == '3');
        Debug.Assert(fFunc("foo") == 'X');
    }

    void Test2()
    {
        Expression<Func<string, int>> y = s => s.Length;

        var yBody = visitor.Visit(y.Body);
        var yFunc = Expression.Lambda<Func<string, int?>>(
                                    body: yBody,
                                    parameters: y.Parameters)
                            .Compile();

        Debug.Assert(yFunc(null) == null);
        Debug.Assert(yFunc("bar") == 3);
    }

    void Test3()
    {
        Expression<Func<char?, string>> y = s => s.Value.ToString()[0].ToString();

        var yBody = visitor.Visit(y.Body);
        var yFunc = Expression.Lambda<Func<char?, string>>(
                                    body: yBody,
                                    parameters: y.Parameters)
                            .Compile();

        Debug.Assert(yFunc(null) == null);
        Debug.Assert(yFunc('A') == "A");
    }
}
person leandromoh    schedule 08.08.2020

Ответ Джона Скита был правильным, в моем случае я использовал DateTime для своего класса Entity. Когда я пытался использовать лайк

(a.DateProperty == null ? default : a.DateProperty.Date)

У меня была ошибка

Property 'System.DateTime Date' is not defined for type 'System.Nullable`1[System.DateTime]' (Parameter 'property')

Поэтому мне нужно было изменить DateTime? для моего класса сущности и

(a.DateProperty == null ? default : a.DateProperty.Value.Date)
person Ugur Ozturk    schedule 02.08.2020
comment
Речь идет не об операторе нулевого распространения. - person Gert Arnold; 03.08.2020
comment
Мне нравится, как вы упоминаете, что Джон Скит был прав, предполагая, что он каким-то образом может ошибаться. Неплохо! - person Klicker; 06.08.2020