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


90

Вопрос : строка 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,
                  ....
                  ....
              };
    ....
    ....
 }

Пожалуйста,
опубликуйте

3
Чувак, я бы хотел, чтобы C # поддерживал это!
nawfal

Ответы:


141

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

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

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

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


Если вы используете Dynamic LINQ (System.Linq.Dynamic.Core), вы можете использовать этот np()метод. См. Github.com/StefH/System.Linq.Dynamic.Core/wiki/NullPropagation
Стеф Хейенрат

10

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

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

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


@hvd Не могли бы вы объяснить, почему это требуется для обратной совместимости?
jag

1
@jag Все поставщики LINQ, которые уже были созданы до введения ?., не были готовы к обработке ?.каким-либо разумным способом.

1
Но ?.разве нового оператора нет? Таким образом, старый код не будет использоваться ?.и, следовательно, не будет сломан. Поставщики Linq не готовы обрабатывать многие другие вещи, такие как методы CLR.
jag

2
@jag Верно, старый код в сочетании со старыми поставщиками LINQ не пострадает. Старый код использовать не будет ?.. Новый код может быть использование старых поставщиков LINQ, которые будут подготовлены для обработки методов CLR они не признают (бросая исключение), так как те , вписывались в существующем объекте модель выражения дерева. Абсолютно новые типы узлов дерева выражений не подходят.

3
Учитывая количество исключений, уже созданных поставщиками LINQ, вряд ли стоит ожидать компромисса - «раньше мы не поддерживали, поэтому лучше никогда не сможем»
NetMage

1

Ответ Джона Скита был правильным, в моем случае я использовал 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)

Речь идет не об операторе нулевого распространения.
Герт Арнольд

Мне нравится, как вы упоминаете, что Джон Скит был прав, предполагая, что он каким-то образом может ошибаться. Хороший!
Klicker,

0

Хотя дерево выражений не поддерживает распространение 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");
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.