Entity Framework, LINQ: không thể sử dụng Any với StartsWith?

Sep 04 2020

Tôi có một truy vấn LINQ Khung thực thể đơn giản. Mục tiêu là tìm tất cả các Nhà cung cấp dịch vụ bắt đầu bằng A, B hoặc C:

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

tôi có

System.InvalidOperationException: 'Biểu thức LINQ' DbSet .Where (c => __letters_0 .Any (val => val == "" || c.Name! = Null && val! = Null && c.Name.StartsWith (val) )) 'không thể dịch được. Viết lại truy vấn trong một biểu mẫu có thể được dịch hoặc chuyển sang đánh giá ứng dụng khách một cách rõ ràng bằng cách chèn lệnh gọi tới AsEnumerable (), AsAsyncEnumerable (), ToList () hoặc ToListAsync (). Xemhttps://go.microsoft.com/fwlink/?linkid=2101038 để biết thêm thông tin.'

Không có cách nào để làm điều này?

Trả lời

2 GertArnold Sep 08 2020 at 18:46

Trong Entity Framework 6 truy vấn này sẽ chạy tốt. EF6 hỗ trợ dịch SQL StartsWithvà nó hỗ trợ sử dụng chuỗi cục bộ ( letters) trong biểu thức truy vấn.

EF core 3 (phiên bản trong câu hỏi như thông báo ngoại lệ cho biết) cũng hỗ trợ dịch SQL của StartsWith. Vấn đề ở đây (mà câu trả lời khác bỏ sót hoàn toàn) là trình tự cục bộ được sử dụng theo cách không được hỗ trợ. Một truy vấn như ...

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

... sẽ được hỗ trợ vì letterscó thể được dịch đơn giản thành một INmệnh đề. Nhưng tất nhiên đó là một truy vấn hoàn toàn khác.

Việc sử dụng letters.Anyyêu cầu EF phải chuyển đổi lettersthành "cái gì đó" có thể được kết hợp với trong SQL. EF6 thực hiện điều này bằng cách xây dựng tập kết quả trong truy vấn SQL:

    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

Điều này hoạt động, nhưng không thể mở rộng chút nào. EF core 3 không hỗ trợ nó và không có cách giải quyết dễ dàng như được đề xuất trong câu trả lời khác.

Một công việc có thể thực hiện là xây dựng một vị từ với các vị từ ||(Hoặc), ví dụ:

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

Trình tạo PredicateBuildervị từ như Linqkit hoặc cái này ở đâu . Nhưng phương pháp này cũng không thể mở rộng. EF tạo một tham số cho mỗi mục nhập letters, vì vậy bạn có thể đạt đến ngưỡng 2100 tham số trong Sql Server.

1 ArsenKhachaturyan Sep 04 2020 at 19:47

Khung thực thể không cho phép sử dụng các vị từ tùy chỉnh. Đó là lý do tại sao truy vấn của bạn không thành công với thông báo:

... không thể dịch được. Viết lại truy vấn trong một biểu mẫu có thể được dịch ...

Vì vậy, điều này rất có thể có nghĩa là phiên bản EF của bạn không hỗ trợ StartsWithhoặc có một số kết hợp, giống như StartsWithvà Anykhông thể dịch được.

Một cách giải quyết có thể là:

  1. Đầu tiên, bạn có thể lọc truy vấn của mình bằng cách gần nhất, bằng Containsphương pháp này.
var results = students.Where(s => letters.Any(l => s.name.Contains(l)));

Đây là một ví dụ tương tự mà tôi đã viết cho .NET fiddle.


  1. Tiếp theo, trong trường hợp nếu bạn vẫn muốn sử dụng StartsWithnhưng EF của bạn không hỗ trợ, bạn có thể chuyển đổi truy vấn của mình thành bộ sưu tập IN-MEMORY:
var results = filter_by_contains_query
              .ToList() // convert filtered result to in-memory collection
              .Where(i => letters.Any(l => i.StudentName.StartsWith(l)));

đâu filter_by_contains_querylà một truy vấn từ phần đầu tiên.


Hơn:

  • Giới thiệu về các phương pháp được EF hỗ trợ, hãy kiểm tra tại đây .
  • Thảo luận xung quanh StartsWith, EndsWithvà Contains ở đây