식 트리 람다는 null 전파 연산자를 포함 할 수 없습니다.


90

질문 : price = co?.price ?? 0,다음 코드 의 줄 은 위의 오류를 제공합니다. 내가 제거하면되지만 ?에서 co.?그것을 잘 작동합니다. 나는 그들이 온라인에서 사용 하는 이 MSDN 예제 를 따르려고 노력하고 있었 으므로 언제 사용 하고 사용 하지 않아야하는지 이해해야 할 것 같습니다 .?select new { person.FirstName, PetName = subpet?.Name ?? String.Empty };???

오류 :

식 트리 람다는 null 전파 연산자를 포함 할 수 없습니다.

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

... 오류를 게시하시기 바랍니다
윌렘 반 Onsem

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
스테프 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 공급자와 결합 된 이전 코드는 영향을받지 않습니다. 이전 코드는 ?.. 새 코드는 기존 식 트리 개체 모델에 잘 맞기 때문에 인식 할 수없는 CLR 메서드 (예외 발생)를 처리 할 준비 된 이전 LINQ 공급자를 사용할 수 있습니다 . 완전히 새로운 식 트리 노드 유형에 적합하지 않습니다.

3
이미 LINQ 공급자에 의해 발생 된 예외의 수를 감안할 때 "우리는 지원하지 않았기 때문에 지원할 수 없었습니다."
NetMage 2019

1

Jon Skeet의 대답은 옳았습니다. 제 경우에는 DateTimeEntity 클래스 에 사용 했습니다. 내가 사용하려고 할 때

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

이것은 널 전파 연산자에 관한 것이 아닙니다.
Gert Arnold

나는 당신이 Jon Skeet이 옳았다는 것을 언급하는 방식이 마음에 들며, 그가 틀릴 가능성이 있다고 제안합니다. 잘 했어!
Klicker

0

식 트리는 C # 6.0 null 전파를 지원하지 않지만 연산자처럼 안전한 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");
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.