LINQ cho các thực thể không nhận dạng được phương pháp 'Định dạng chuỗi hệ thống (System.String, System.Object, System.Object)'


88

Tôi có truy vấn linq này:

private void GetReceivedInvoiceTasks(User user, List<Task> tasks)
{
    var areaIds = user.Areas.Select(x => x.AreaId).ToArray();

    var taskList = from i in _db.Invoices
                   join a in _db.Areas on i.AreaId equals a.AreaId
                   where i.Status == InvoiceStatuses.Received && areaIds.Contains(a.AreaId)
                   select new Task {
                       LinkText = string.Format(Invoice {0} has been received from {1}, i.InvoiceNumber, i.Organisation.Name),
                       Link = Views.Edit
                   };
}

Nó có vấn đề mặc dù. Tôi đang cố gắng tạo nhiệm vụ. Đối với mỗi tác vụ mới khi tôi đặt văn bản liên kết thành một chuỗi không đổi như "Xin chào" thì không sao cả. Tuy nhiên, ở trên, tôi đang cố gắng tạo văn bản liên kết thuộc tính bằng cách sử dụng các thuộc tính của hóa đơn.

Tôi gặp lỗi này:

base {System.SystemException} = {"LINQ to Entities không nhận dạng được phương thức 'System.String Format (System.String, System.Object, System.Object)' và phương thức này không thể dịch thành biểu thức lưu trữ." }

Có ai biết tại sao không? Bất cứ ai biết một cách thay thế để làm điều này để làm cho nó hoạt động?


Vâng, nhỡ mà ra ban đầu
AnonyMouse

Câu trả lời:


148

Entity Framework đang cố gắng thực thi phép chiếu của bạn ở phía SQL, nơi không có tương đương với string.Format. Sử dụng AsEnumerable()để buộc đánh giá phần đó với Linq to Objects.

Dựa trên câu trả lời trước đây mà tôi đã đưa cho bạn, tôi sẽ cấu trúc lại truy vấn của bạn như sau:

int statusReceived = (int)InvoiceStatuses.Received;
var areaIds = user.Areas.Select(x=> x.AreaId).ToArray();

var taskList = (from i in _db.Invoices
               where i.Status == statusReceived && areaIds.Contains(i.AreaId)
               select i)
               .AsEnumerable()
               .Select( x => new Task()
               {
                  LinkText = string.Format("Invoice {0} has been received from {1}", x.InvoiceNumber, x.Organisation.Name),
                  Link = Views.Edit
                });

Ngoài ra, tôi thấy bạn sử dụng các thực thể có liên quan trong truy vấn ( Organisation.Name), hãy đảm bảo rằng bạn thêm thích hợp Includevào truy vấn của mình hoặc cụ thể hóa các thuộc tính đó để sử dụng sau này, tức là:

var taskList = (from i in _db.Invoices
               where i.Status == statusReceived && areaIds.Contains(i.AreaId)
               select new { i.InvoiceNumber, OrganisationName = i.Organisation.Name})
               .AsEnumerable()
               .Select( x => new Task()
               {
                  LinkText = string.Format("Invoice {0} has been received from {1}", x.InvoiceNumber, x.OrganisationName),
                  Link = Views.Edit
                });

Ngoài thực tế là phần select-new-task không thể xảy ra phía máy chủ do dịch cây biểu thức, cũng cần lưu ý rằng bạn không nên làm như vậy. Có lẽ, bạn muốn các tác vụ được tạo phía máy khách. Do đó, việc tách truy vấn và tạo nhiệm vụ có thể rõ ràng hơn.
Tormod

3
Tôi cũng khuyên bạn nên chọn một loại ẩn danh chỉ có InvoiceNumber và Organisation.Name cần thiết trong đó. Nếu thực thể hóa đơn lớn thì nút chọn i với AsEnumerable tiếp theo sẽ kéo lại mọi cột mặc dù bạn chỉ đang sử dụng hai.
Devin

1
@Devin: Vâng, tôi đồng ý - trên thực tế, đó chính xác là những gì ví dụ truy vấn thứ hai đang làm.
BrokenGlass

15

IQueryablebắt nguồn từ IEnumerable, điểm tương đồng chính là khi bạn thực hiện truy vấn của mình, nó được đăng lên cơ sở dữ liệu bằng ngôn ngữ của nó, thời điểm mỏng là nơi bạn yêu cầu C # xử lý dữ liệu trên máy chủ (không phải phía máy khách) hoặc yêu cầu SQL xử lý dữ liệu.

Vì vậy, về cơ bản khi bạn nói IEnumerable.ToString(), C # thu thập dữ liệu và gọi ToString()đối tượng. Nhưng khi bạn nói IQueryable.ToString()C # yêu cầu SQL gọi ToString()đối tượng nhưng không có phương thức này trong SQL.

Hạn chế là khi bạn xử lý dữ liệu trong C #, toàn bộ bộ sưu tập mà bạn đang xem qua phải được tích hợp trong bộ nhớ trước khi C # áp dụng các bộ lọc.

Cách hiệu quả nhất để làm điều đó là tạo truy vấn như IQueryablevới tất cả các bộ lọc mà bạn có thể áp dụng.

Và sau đó xây dựng nó trong bộ nhớ và thực hiện định dạng dữ liệu trong C #.

IQueryable<Customer> dataQuery = Customers.Where(c => c.ID < 100 && c.ZIP == 12345 && c.Name == "John Doe");

 var inMemCollection = dataQuery.AsEnumerable().Select(c => new
                                                  {
                                                     c.ID
                                                     c.Name,
                                                     c.ZIP,
                                                     c.DateRegisterred.ToString("dd,MMM,yyyy")
                                                   });

3

Trong khi SQL không biết phải làm gì với một string.Formatthì nó có thể thực hiện nối chuỗi.

Nếu bạn chạy mã sau thì bạn sẽ nhận được dữ liệu bạn đang có.

var taskList = from i in _db.Invoices
               join a in _db.Areas on i.AreaId equals a.AreaId
               where i.Status == InvoiceStatuses.Received && areaIds.Contains(a.AreaId)
               select new Task {
                   LinkText = "Invoice " + i.InvoiceNumber + "has been received from " + i.Organisation.Name),
                   Link = Views.Edit
               };

Khi bạn thực sự thực hiện truy vấn, điều này sẽ nhanh hơn một chút so với việc sử dụng AsEnumerable(ít nhất đó là những gì tôi tìm thấy trong mã của riêng mình sau khi gặp lỗi ban đầu giống như bạn). Nếu bạn đang làm điều gì đó phức tạp hơn với C # thì bạn vẫn cần sử dụng AsEnumerable.


2
Bạn không chắc chắn lý do tại sao LINQ không thể được điều chỉnh để sử dụng chức năng FormatMessage docs.microsoft.com/en-us/sql/t-sql/functions/... Cho đến lúc đó của bạn là giải pháp (mà không buộc materialisation)
MemeDeveloper

2
Tùy thuộc vào cấu trúc cơ sở dữ liệu và số lượng cột liên quan, sử dụng phương pháp này thay vì AsEnumerable()có thể hiệu quả hơn rất nhiều. Tránh AsEnumerable()ToList()cho đến khi bạn thực sự muốn đưa tất cả kết quả vào bộ nhớ.
Chris Schaller
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.