дерево виразів лямбда не може містити нульовий оператор поширення


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>запити, де лямбда-вирази перетворюються на дерева виразів . Дерева виразів не підтримують нульовий умовний оператор (або кортежі).

Просто зробіть це по-старому:

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

(Я вважаю, що оператор нульового злиття чудовий у дереві виразів.)


Якщо ви використовуєте Dynamic LINQ (System.Linq.Dynamic.Core), ви можете використовувати np()метод. Дивіться github.com/StefH/System.Linq.Dynamic.Core/wiki/NullPropagation
Stef Heyenrath

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, навряд чи виглядає вартим компромісу - "ми раніше не підтримували, тому
воліли

1

Відповідь Джона Скіта була правильною, у моєму випадку я використовував DateTimeдля свого класу Entity. Коли я намагався використовувати like

(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)

Йдеться не про нульовий оператор поширення.
Герт Арнольд

Мені подобається, як ти згадуєш про те, що Джон Скіт мав рацію, припускаючи, що він якось може помилятися. Хороший!
Клікер

0

Хоча дерево виразів не підтримує розповсюдження нуля C # 6.0, ми можемо створити відвідувача, який модифікує дерево виразів для безпечного розповсюдження нуля, як це робить оператор!

Ось мій:

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.