Entity Framework, LINQ: StartsWith ile Any kullanılamaz mı?
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
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.
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:
- İ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.
- 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,EndsWithveContainsburada