Entity Framework, LINQ: StartsWith ile Any kullanılamaz mı?

Sep 04 2020

Basit bir Entity Framework LINQ sorgum var. Amaç, A, B veya C ile başlayan tüm Taşıyıcıları bulmaktır:

var letters = new List<string>() { "A", "B", "C" }; // Dynamic, can be many
var results = db.Carriers.AsNoTracking()
    .Where(c => letters.Any(val => c.Name.StartsWith(val)))
    .ToList();

alırım

System.InvalidOperationException: 'LINQ ifadesi' DbSet .Where (c => __letters_0 .Any (val => val == "" || c.Name! = Null && val! = Null && c.Name.StartsWith (val) )) 'çevrilemedi. Ya sorguyu çevrilebilecek bir biçimde yeniden yazın ya da AsEnumerable (), AsAsyncEnumerable (), ToList () ya da ToListAsync () 'e bir çağrı ekleyerek açıkça istemci değerlendirmesine geçin. Görmekhttps://go.microsoft.com/fwlink/?linkid=2101038 daha fazla bilgi için.'

Bunu yapmanın bir yolu yok mu?

Yanıtlar

2 GertArnold Sep 08 2020 at 18:46

Entity Framework 6'da bu sorgu sorunsuz çalışır. EF6, SQL çevirisini StartsWithdestekler lettersve bir sorgu ifadesinde yerel dizilerin ( ) kullanılmasını destekler .

EF core 3 (istisna mesajının gösterdiği gibi sorudaki sürüm) ayrıca StartsWith. Buradaki sorun (diğer cevabın tamamen gözden kaçırdığı) yerel sıranın desteklenmeyen bir şekilde kullanılmasıdır. Gibi bir sorgu ...

var results = db.Carriers.AsNoTracking()
    .Where(c => letters.Contains(c.Name))
    .ToList();

... desteklenir çünkü lettersbasitçe bir INcümleye çevrilebilir . Ama elbette bu tamamen farklı bir sorgu.

Kullanımı letters.Any, EF'in lettersSQL'de birleştirilebilecek "bir şeye" dönüştürülmesini gerektirir . EF6, SQL sorgusunda bir sonuç kümesi oluşturarak bunu yapar:

    WHERE  EXISTS (SELECT 
        1 AS [C1]
        FROM  (SELECT 
            N'A' AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]
        UNION ALL
            SELECT 
            N'B' AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable2]
        UNION ALL
            SELECT 
            N'C' AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable3]) AS [UnionAll2]
        WHERE ( CAST(CHARINDEX([UnionAll2].[C1], [Extent1].[Name]) AS int)) = 1

Bu işe yarıyor, ancak ölçeklenebilir değil. EF core 3 bunu desteklemiyor ve diğer yanıtta önerildiği gibi kolay bir çözüm yok.

Olası bir çözüm, ||(Or) yüklemleri ile bir yüklem oluşturmaktır , örneğin:

var pred = letters.Aggregate(PredicateBuilder.False<Carrier>(), 
    (p,x) => p = p.Or(c => c.Name.StartsWith(x)));
var results = db.Carriers.AsNoTracking()
    .Where(pred)
    .ToList();

PredicateBuilderLinqkit veya bunun gibi bir yüklem oluşturucu nerede ? Ancak bu yöntem de ölçeklenebilir değildir. EF, içindeki her girdi için bir parametre oluşturur letters, böylece Sql Sunucusunda 2100 parametreli eşiğe ulaşabilirsiniz.

1 ArsenKhachaturyan Sep 04 2020 at 19:47

Entity Framework, özel tahminlerin kullanımına izin vermez. Sorgunuzun bir mesajla başarısız olmasının nedeni budur:

... çevrilemedi. Ya sorguyu çevrilebilecek bir biçimde yeniden yazın ...

Yani, bu büyük olasılıkla sizin EF sürümü desteklemiyor ya anlamına gelir StartsWithveya bazı kombinasyonu gibi olduğunu StartsWithve Anybu tercüme edilemez.

Bir geçici çözüm şunlar olabilir:

  1. İlk olarak, Containsyöntemi kullanarak sorgunuzu en yakın şekilde filtreleyebilirsiniz .
var results = students.Where(s => letters.Any(l => s.name.Contains(l)));

İşte .NET keman için yazdığım benzer bir örnek.


  1. Sonraki durumda, hala kullanmak istiyorsanız, StartsWithancak EF'niz sorgunuzu IN-MEMORY koleksiyonuna dönüştürebileceğinizi desteklemiyorsa:
var results = filter_by_contains_query
              .ToList() // convert filtered result to in-memory collection
              .Where(i => letters.Any(l => i.StudentName.StartsWith(l)));

filter_by_contains_query1. bölümden bir sorgu nerede .


Daha:

  • EF tarafından desteklenen yöntemler hakkında buraya bakın .
  • Etrafında tartışmalar StartsWith, EndsWithve Contains burada