une arborescence d'expression lambda ne peut pas contenir d'opérateur de propagation nul


90

Question : La ligne price = co?.price ?? 0,dans le code suivant me donne l'erreur ci-dessus. mais si je retire ?de co.?cela fonctionne très bien. J'essayais de suivre cet exemple MSDN où ils utilisent ?en ligne select new { person.FirstName, PetName = subpet?.Name ?? String.Empty };Donc, il semble que j'ai besoin de comprendre quand utiliser ?avec ??et quand ne pas le faire.

Erreur :

une arborescence d'expression lambda ne peut pas contenir d'opérateur de propagation nul

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,
                  ....
                  ....
              };
    ....
    ....
 }

S'il vous plaît poster l'erreur ...
Willem Van Onsem

3
Homme, je souhaitais que C # soutienne cela!
nawfal

Réponses:


141

L'exemple que vous citiez utilise LINQ to Objects, où les expressions lambda implicites de la requête sont converties en délégués ... alors que vous utilisez EF ou similaire, avec des IQueryable<T>requêtes, où les expressions lambda sont converties en arborescences d'expression . Les arborescences d'expressions ne prennent pas en charge l'opérateur conditionnel nul (ou les tuples).

Faites-le simplement à l'ancienne:

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

(Je pense que l'opérateur de fusion nul convient parfaitement dans un arbre d'expression.)


Si vous utilisez Dynamic LINQ (System.Linq.Dynamic.Core), vous pouvez utiliser la np()méthode. Voir github.com/StefH/System.Linq.Dynamic.Core/wiki/NullPropagation
Stef Heyenrath

10

Le code auquel vous liez utilise List<T>. List<T>implémente IEnumerable<T>mais pas IQueryable<T>. Dans ce cas, la projection est exécutée en mémoire et ?.fonctionne.

Vous en utilisez IQueryable<T>, ce qui fonctionne très différemment. Pour IQueryable<T>, une représentation de la projection est créée et votre fournisseur LINQ décide quoi en faire au moment de l'exécution. Pour des raisons de compatibilité descendante, ?.ne peut pas être utilisé ici.

En fonction de votre fournisseur LINQ, vous pourrez peut-être utiliser plain .et ne pas en obtenir NullReferenceException.


@hvd Pouvez-vous expliquer pourquoi cela est nécessaire pour la compatibilité ascendante?
jag

1
@jag Tous les fournisseurs LINQ qui avaient déjà été créés avant l'introduction de ?.n'auraient pas été préparés à gérer ?.de manière raisonnable.

1
Mais le ?.est un nouvel opérateur non? Donc, l'ancien code ne serait pas utilisé ?.et donc pas cassé. Les fournisseurs Linq ne sont pas préparés à gérer beaucoup d'autres choses telles que les méthodes CLR.
jag

2
@jag Exactement, l'ancien code associé aux anciens fournisseurs LINQ ne serait pas affecté. L'ancien code ne sera pas utilisé ?.. Le nouveau code peut utiliser d'anciens fournisseurs LINQ, qui sont prêts à gérer les méthodes CLR qu'ils ne reconnaissent pas (en lançant une exception), car ceux-ci s'intègrent parfaitement dans le modèle d'objet d'arborescence d'expression existant. Les types de nœuds d'arbre d'expression complètement nouveaux ne rentrent pas dans.

3
Étant donné le nombre d'exceptions lancées par les fournisseurs LINQ déjà, cela ne semble guère un compromis valable - "nous ne prenions pas en charge, nous
préférions

1

La réponse de Jon Skeet était juste, dans mon cas, j'utilisais DateTimepour ma classe Entity. Quand j'ai essayé d'utiliser comme

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

J'ai eu l'erreur

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

J'avais donc besoin de changer DateTime?pour ma classe d'entité et

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

Il ne s'agit pas de l'opérateur de propagation nul.
Gert Arnold

J'aime la façon dont vous mentionnez que Jon Skeet avait raison, suggérant qu'il est en quelque sorte possible qu'il se trompe. Bon!
Klicker

0

Bien que l'arborescence d'expression ne prenne pas en charge la propagation Null C # 6.0, ce que nous pouvons faire est de créer un visiteur qui modifie l'arborescence d'expression pour une propagation nulle sûre, tout comme l'opérateur le fait!

Voici le mien:

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

Il passe les tests suivants:

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");
    }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.