ChatGPT解决这个技术问题 Extra ChatGPT

An expression tree lambda may not contain a null propagating operator

The line price = co?.price ?? 0, in the following code gives me the above error, but if I remove ? from co.? it works fine.

I was trying to follow this MSDN example where they are using ? on line select new { person.FirstName, PetName = subpet?.Name ?? String.Empty }; So, it seems I need to understand when to use ? with ?? and when not to.

Error:

an expression tree lambda may not contain a null propagating operator

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,
                  ....
                  ....
              };
    ....
    ....
 }
Man I wished C# supported this!

J
Jon Skeet

The example you were quoting from uses LINQ to Objects, where the implicit lambda expressions in the query are converted into delegates... whereas you're using EF or similar, with IQueryable<T> queryies, where the lambda expressions are converted into expression trees. Expression trees don't support the null conditional operator (or tuples).

Just do it the old way:

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

(I believe the null-coalescing operator is fine in an expression tree.)


In case you are using Dynamic LINQ (System.Linq.Dynamic.Core), you can use the np() method. See github.com/StefH/System.Linq.Dynamic.Core/wiki/NullPropagation
@Jon Is LINQ able to translate this to SQL or would it cause the filtering to happen in memory?
@DaniMazahreh: As I said before, expression trees don't support the null conditional operator - so I'd expect that you'd get a compilation error.
Oh, I get it. From the coding side, it looks like a lambda expression either way, but only the delegate/Func supports the operator usage. Also, I wonder if there's a reason the error says "null-propagating" as opposed to "null-conditional", which is what the official docs call it?
@Calculuswhiz: It is a lambda expression either way. But not all lambda expressions actually support conversion into expression trees. I believe null-propagating was an earlier term before the team settled on null-conditional.
佚名

The code you link to uses List<T>. List<T> implements IEnumerable<T> but not IQueryable<T>. In that case, the projection is executed in memory and ?. works.

You're using some IQueryable<T>, which works very differently. For IQueryable<T>, a representation of the projection is created, and your LINQ provider decides what to do with it at runtime. For backwards compatibility reasons, ?. cannot be used here.

Depending on your LINQ provider, you may be able to use plain . and still not get any NullReferenceException.


@hvd Could you explain why this is required for backwards compatibility?
@jag All LINQ providers that had already been created before the introduction of ?. would not have been prepared to handle ?. in any reasonable way.
But the ?. is a new operator no? So old code would not use ?. and thus not be broken. Linq Providers are not prepared to handle many other things such as CLR methods.
@jag Right, old code in combination with old LINQ providers would not be affected. Old code won't be using ?.. New code may be using old LINQ providers, which are prepared to handle CLR methods they don't recognise (by throwing an exception), since those fit nicely into the existing expression tree object model. Completely new expression tree node types don't fit in.
Given the number of exceptions thrown by LINQ providers already, hardly seems a worthwhile trade-off - "we used to not support, so we'd rather never can"
f
fcdt

Jon Skeet's answer was right, in my case I was using DateTime for my Entity class. When I tried to use like

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

I had the error

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

So I needed to change DateTime? for my entity class and

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

This is not about the null propagation operator.
I like how you mention that Jon Skeet was right, suggesting that it's somehow possible for him to be wrong. Good one!
Yeah, this isn't the same error as the OP.
l
leandromoh

While expression tree does not support the C# 6.0 null propagating, what we can do is create a visitor that modify expression tree for safe null propagation, just like the operator does!

Here is mine:

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);
        }
    }
}

It passes on the following tests:

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");
    }
}