pohon ekspresi lambda tidak boleh berisi operator penyebaran null

90

Pertanyaan : Baris price = co?.price ?? 0,dalam kode berikut memberi saya kesalahan di atas. tapi jika saya menghapus ?dari co.?itu bekerja dengan baik. Saya mencoba mengikuti contoh MSDN ini di mana mereka menggunakan secara ?online. select new { person.FirstName, PetName = subpet?.Name ?? String.Empty };Jadi, sepertinya saya perlu memahami kapan harus menggunakan ?dengan ??dan kapan tidak.

Kesalahan :

pohon ekspresi lambda tidak boleh berisi operator penyebaran 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,
                  ....
                  ....
              };
    ....
    ....
 }
nam
sumber
Silakan posting kesalahan ...
Willem Van Onsem
3
Sobat, aku berharap C # mendukung ini!
nawfal

Jawaban:

141

Contoh yang Anda kutip dari menggunakan LINQ ke Objek, di mana ekspresi lambda implisit dalam kueri diubah menjadi delegasi ... saat Anda menggunakan EF atau serupa, dengan IQueryable<T>kueri, di mana ekspresi lambda diubah menjadi pohon ekspresi . Pohon ekspresi tidak mendukung operator bersyarat null (atau tupel).

Lakukan saja dengan cara lama:

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

(Saya yakin operator penggabungan nol baik-baik saja di pohon ekspresi.)

Jon Skeet
sumber
Jika Anda menggunakan Dynamic LINQ (System.Linq.Dynamic.Core), Anda dapat menggunakan np()metode ini. Lihat github.com/StefH/System.Linq.Dynamic.Core/wiki/NullPropagation
Stef Heyenrath
10

Kode yang Anda tautkan untuk digunakan List<T>. List<T>mengimplementasikan IEnumerable<T>tetapi tidak IQueryable<T>. Dalam hal ini, proyeksi dijalankan dalam memori dan ?.berfungsi.

Anda menggunakan beberapa IQueryable<T>, yang bekerja sangat berbeda. Untuk IQueryable<T>, representasi proyeksi dibuat, dan penyedia LINQ Anda memutuskan apa yang harus dilakukan dengannya saat runtime. Untuk alasan kompatibilitas mundur, ?.tidak dapat digunakan di sini.

Bergantung pada penyedia LINQ Anda, Anda mungkin bisa menggunakan biasa .dan masih belum mendapatkannya NullReferenceException.


sumber
@hvd Bisakah Anda menjelaskan mengapa ini diperlukan untuk kompatibilitas mundur?
jag
1
@jag Semua penyedia LINQ yang telah dibuat sebelum pengenalan ?.tidak akan siap untuk menangani ?.dengan cara yang masuk akal.
1
Tapi ?.apakah ada operator baru? Jadi kode lama tidak akan digunakan ?.dan dengan demikian tidak akan rusak. Penyedia Linq tidak siap untuk menangani banyak hal lain seperti metode CLR.
jag
2
@jag Benar, kode lama yang dikombinasikan dengan penyedia LINQ lama tidak akan terpengaruh. Kode lama tidak akan digunakan ?.. Kode baru mungkin menggunakan penyedia LINQ tua, yang sedang disiapkan untuk menangani metode CLR mereka tidak mengenali (dengan melemparkan pengecualian), karena mereka cocok baik ke dalam model objek pohon ekspresi yang ada. Jenis simpul pohon ekspresi yang benar-benar baru tidak cocok.
3
Mengingat jumlah pengecualian yang telah diberikan oleh penyedia LINQ, tampaknya bukan trade-off yang berharga - "kami dulu tidak mendukung, jadi kami lebih suka tidak pernah bisa"
NetMage
1

Jawaban Jon Skeet benar, dalam kasus saya, saya menggunakan DateTimeuntuk kelas Entitas saya. Ketika saya mencoba menggunakan suka

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

Saya mengalami kesalahan

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

Jadi saya perlu mengubah DateTime?kelas entitas saya dan

(a.DateProperty == null ? default : a.DateProperty.Value.Date)
Ugur Ozturk
sumber
Ini bukan tentang operator propagasi null.
Gert Arnold
Saya suka bagaimana Anda menyebutkan bahwa Jon Skeet benar, menunjukkan bahwa dia mungkin salah. Bagus!
Klicker
0

Sementara pohon ekspresi tidak mendukung penyebaran null C # 6.0, apa yang dapat kita lakukan adalah membuat pengunjung yang mengubah pohon ekspresi untuk propagasi null yang aman, seperti yang dilakukan operator!

Ini milikku:

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

Ini lolos dalam tes berikut:

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