String.IsNullOrWhiteSpace di LINQ Expression


151

Saya memiliki kode berikut:

return this.ObjectContext.BranchCostDetails.Where(
    b => b.TarrifId == tariffId && b.Diameter == diameter
        || (b.TarrifId==tariffId && !string.IsNullOrWhiteSpace(b.Diameter))
        || (!b.TarrifId.HasValue) && b.Diameter==diameter);

Dan saya mendapatkan kesalahan ini ketika saya mencoba menjalankan kode:

LINQ to Entities tidak mengenali metode metode 'Boolean IsNullOrWhiteSpace (System.String)', dan metode ini tidak dapat diterjemahkan ke dalam ekspresi toko. "

Bagaimana saya bisa menyelesaikan masalah ini dan menulis kode lebih baik dari ini?

Jawaban:


263

Anda harus mengganti

!string.IsNullOrWhiteSpace(b.Diameter)

dengan

!(b.Diameter == null || b.Diameter.Trim() == string.Empty)

Untuk Linq to Entities ini diterjemahkan ke dalam:

DECLARE @p0 VarChar(1000) = ''
...
WHERE NOT (([t0].[Diameter] IS NULL) OR (LTRIM(RTRIM([t0].[Diameter])) = @p0))

dan untuk Linq to SQL hampir tetapi tidak persis sama

DECLARE @p0 NVarChar(1000) = ''
...
WHERE NOT (LTRIM(RTRIM([t0].[TypeName])) = @p0)

3
Mengapa? Kode ini mengkompilasi:List<string> my = new List<string>(); var i = from m in my where !string.IsNullOrWhiteSpace(m) select m;
Eric J.

37
Mungkin dikompilasi, tetapi tidak akan diterjemahkan ke dalam SQL oleh Linq ke entitas. Metode 'Boolean IsNullOrWhiteSpace (System.String)' tidak memiliki terjemahan yang didukung ke SQL. Hal yang sama berlaku untuk IsNullOrEmpty.
Phil

1
Hal yang sama berlaku untuk Linq to SQL
Phil

3
Kata hati-hati: Sangat penting untuk menggunakan 'string. Hapus' lebih dari "" (alias string kosong). Yang pertama bekerja yang terakhir tidak (setidaknya sejauh menyangkut driver EF Oracle). Aka jika Anda menggunakan: b.Diameter.Trim () == "" <- ini tidak akan berfungsi sebagaimana mestinya (gila saya tahu ...)
XDS

tampaknya Trim () juga tidak didukung setidaknya untuk permintaan menggunakan MongoDB.Driver
Leandro hereñu

20

Dalam hal ini penting untuk membedakan antara IQueryable<T>dan IEnumerable<T>. Singkatnya IQueryable<T>diproses oleh penyedia LINQ untuk mengirimkan permintaan yang dioptimalkan. Selama transformasi ini tidak semua pernyataan C # didukung, karena tidak mungkin menerjemahkannya ke kueri spesifik back-end (misalnya SQL) atau karena pelaksana tidak melihat kebutuhan akan pernyataan tersebut.

Sebaliknya IEnumerable<T>dieksekusi terhadap benda konkret dan, oleh karena itu, tidak akan diubah. Jadi, sangat umum bahwa konstruksi, yang dapat digunakan dengan IEnumerable<T>, tidak dapat digunakan dengan IQueryable<T>dan juga yang IQueryables<T>didukung oleh penyedia LINQ yang berbeda tidak mendukung serangkaian fungsi yang sama.

Namun, ada beberapa solusi (seperti jawaban Phil ), yang mengubah kueri. Juga, sebagai pendekatan yang lebih umum dimungkinkan untuk kembali ke IEnumerable<T>sebelum melanjutkan dengan spesifikasi permintaan. Namun, ini mungkin memiliki hit kinerja - terutama ketika menggunakannya pada pembatasan (misalnya di mana klausa). Sebaliknya, ketika berhadapan dengan transformasi, hit kinerja jauh lebih kecil, kadang-kadang bahkan tidak ada - tergantung pada permintaan Anda.

Jadi kode di atas juga bisa ditulis ulang seperti ini:

return this.ObjectContext.BranchCostDetails
    .AsEnumerable()
    .Where(
        b => b.TarrifId == tariffId && b.Diameter == diameter
        || (b.TarrifId==tariffId && !string.IsNullOrWhiteSpace(b.Diameter))
        ||(!b.TarrifId.HasValue) && b.Diameter==diameter
    );

CATATAN: Kode THS akan memiliki dampak kinerja yang lebih tinggi daripada jawaban Phil . Namun, itu menunjukkan prinsipnya.


10

Gunakan pengunjung ekspresi untuk mendeteksi referensi ke string.IsNullOrWhiteSpace dan memecahnya menjadi ekspresi yang lebih sederhana (x == null || x.Trim() == string.Empty).

Jadi di bawah ini adalah pengunjung tambahan dan metode ekstensi untuk memanfaatkannya. Tidak memerlukan konfigurasi khusus untuk digunakan, cukup panggil WhereEx daripada Where.

public class QueryVisitor: ExpressionVisitor
{
    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        if (node.Method.IsStatic && node.Method.Name == "IsNullOrWhiteSpace" && node.Method.DeclaringType.IsAssignableFrom(typeof(string)))
        {
            //!(b.Diameter == null || b.Diameter.Trim() == string.Empty)
            var arg = node.Arguments[0];
            var argTrim = Expression.Call(arg, typeof (string).GetMethod("Trim", Type.EmptyTypes));

            var exp = Expression.MakeBinary(ExpressionType.Or,
                    Expression.MakeBinary(ExpressionType.Equal, arg, Expression.Constant(null, arg.Type)),
                    Expression.MakeBinary(ExpressionType.Equal, argTrim, Expression.Constant(string.Empty, arg.Type))
                );

            return exp;
        }

        return base.VisitMethodCall(node);
    }
}

public static class EfQueryableExtensions
{
    public static IQueryable<T> WhereEx<T>(this IQueryable<T> queryable, Expression<Func<T, bool>> where)
    {
        var visitor = new QueryVisitor();
        return queryable.Where(visitor.VisitAndConvert(where, "WhereEx"));
    }
}

Jadi jika Anda menjalankannya myqueryable.WhereEx(c=> !c.Name.IsNullOrWhiteSpace())akan dikonversi menjadi !(c.Name == null || x.Trim() == "")sebelum dilewatkan ke apa pun (LINQ ke sql / entitas) dan dikonversi ke sql.


Jauh lebih kompleks daripada jawaban Phil untuk persyaratan sederhana seperti itu, tetapi sangat menarik untuk tujuan pendidikan mengenai ExpressionVisitor, terima kasih
AFract

2

Anda juga dapat menggunakan ini untuk memeriksa spasi putih:

b.Diameter!=null && !String.IsNullOrEmpty(b.Diameter.Trim())

6
ini akan membuang pengecualian jika diameternya nol.
Okan Kocyigit

@OkanKocyigit Anda benar. Saya telah mengedit jawabannya. :)
Majid

0
!String.IsNullOrEmpty(b.Diameter.Trim()) 

akan membuang pengecualian jika b.Diameterini null.
Jika Anda masih ingin menggunakan pernyataan Anda, lebih baik gunakan cek ini

!String.IsNullOrWhiteSpace(b.Diameter), IsNullOrWhiteSpace = IsNullOrEmpty + WhiteSpace

2
Selamat datang di StackOverflow! Pertama-tama, terima kasih telah berpartisipasi di SO sebagai penjawab. Silakan lihat pemformatan untuk membuat jawaban yang jelas dan mudah dibaca.
Hille
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.