LINQ to Entities не розпізнає метод 'System.String Format (System.String, System.Object, System.Object)'


88

У мене є такий запит linq:

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
                   };
}

У нього є проблеми. Я намагаюся створювати завдання. Для кожного нового завдання, коли я встановлюю для тексту посилання константний рядок типу "Привіт", це нормально. Однак вище я намагаюся створити властивість linktext, використовуючи властивості рахунку-фактури.

Я отримую цю помилку:

base {System.SystemException} = {"LINQ to Entities не розпізнає метод 'System.String Format (System.String, System.Object, System.Object)', і цей метод не може бути перекладений у вираз зберігання." }

Хтось знає чому? Хтось знає альтернативний спосіб зробити це, щоб він працював?


Так, спочатку пропустив
AnonyMouse

Відповіді:


148

Entity Framework намагається виконати вашу проекцію на стороні SQL, де немає еквівалента string.Format. Використовуйте AsEnumerable()для примусової оцінки цієї частини за допомогою Linq до об’єктів.

На основі попередньої відповіді, яку я дав вам, я б змінив ваш запит так:

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

Також я бачу, що ви використовуєте пов'язані сутності у запиті ( Organisation.Name), переконайтесь, що ви додали належне Includeдо свого запиту або спеціально матеріалізували ці властивості для подальшого використання, тобто:

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

Окрім того, що частина select-new-task не може відбуватися на стороні сервера через переклад дерева виразів, слід також зазначити, що це небажано робити. Імовірно, ви хочете, щоб завдання створювалися на стороні клієнта. Тому поділ запиту та створення завдань може бути ще більш чітким.
Тормод,

3
Я б також порекомендував вибрати анонімний тип, який просто містить необхідні InvoiceNumber та Organisation.Name. Якщо сутність рахунків-фактур велика, тоді вибір i з наступним AsEnumerable витягне кожен стовпець, хоча ви використовуєте лише два.
Девін,

1
@ Девін: Так, я погоджуюсь - насправді це саме те, що робить другий приклад запиту.
BrokenGlass

15

IQueryableпоходить від IEnumerable, основна схожість полягає в тому, що коли ви робите ваш запит, він публікується на механізмі бази даних на цій мові, тонка мить - це те, коли ви говорите C # обробляти дані на сервері (не на стороні клієнта) або наказувати SQL обробляти даних.

Отже, в основному, коли ви говорите IEnumerable.ToString(), C # отримує збір даних та дзвінки ToString()на об'єкт. Але коли ви говорите, що IQueryable.ToString()C # каже SQL викликати ToString()об'єкт, але в SQL такого методу немає.

Недоліком є ​​те, що при обробці даних у C # вся колекція, яку ви переглядаєте, повинна бути створена в пам'яті, перш ніж C # застосовуватиме фільтри.

Найефективніший спосіб зробити це - зробити запит, як IQueryableі всі фільтри, які ви можете застосувати.

А потім створити його в пам'яті та зробити форматування даних на 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

Хоча SQL не знає, що робити з a, string.Formatвін може виконувати конкатенацію рядків.

Якщо ви запускаєте наступний код, ви повинні отримати дані, за якими ви переслідуєте.

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
               };

Після того, як ви фактично виконаєте запит, це повинно бути незначно швидше, ніж використання AsEnumerable(принаймні, це я знайшов у своєму власному коді після тієї ж оригінальної помилки, що і ви). Якщо ви робите щось більш складне з C #, тоді вам все одно доведеться використовувати AsEnumerable.


2
Не впевнений, чому Linq не може бути пристосований для використання функції FORMATMESSAGE docs.microsoft.com/en-us/sql/t-sql/functions/ ... До тих пір ваше рішення (без примусової матеріалізації)
MemeDeveloper

2
Залежно від структури бази даних та кількості пов’язаних стовпців, використання цього методу замість AsEnumerable()може бути значно ефективнішим. Уникайте AsEnumerable()і ToList()поки ви дійсно не захочете зберегти всі результати в пам’яті.
Кріс Шаллер,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.