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

Однако у него есть проблемы. Пытаюсь создавать задачи. Для каждой новой задачи, когда я устанавливаю текст ссылки в виде постоянной строки, такой как «Привет», это нормально. Однако выше я пытаюсь создать текст ссылки свойства, используя свойства счета-фактуры.

Я получаю такую ​​ошибку:

base {System.SystemException} = {"LINQ to Entities не распознает метод 'System.String Format (System.String, System.Object, System.Object)', и этот метод не может быть преобразован в выражение хранилища." }

Кто-нибудь знает почему? Кто-нибудь знает альтернативный способ сделать это, чтобы он работал?


Да, изначально пропустил это
— AnonyMouse

Ответы:


148

Entity Framework пытается выполнить вашу проекцию на стороне SQL, где нет эквивалента string.Format. Используйте AsEnumerable()для принудительной оценки этой части с помощью Linq to Objects.

Основываясь на предыдущем ответе, который я вам дал, я бы реструктурировал ваш запрос следующим образом:

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

Также я вижу, что вы используете связанные сущности в query ( 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
                });

В дополнение к тому факту, что часть выбора новой задачи не может выполняться на стороне сервера из-за преобразования дерева выражений, следует также отметить, что это нежелательно. Предположительно, вы хотите, чтобы задачи создавались на стороне клиента. Поэтому разделение запроса и создания задач могло бы быть еще более явным.
— Tormod

3
Я также рекомендую выбрать анонимный тип, в котором есть только необходимые InvoiceNumber и Organisation.Name. Если объект invoices большой, то при выборе i с последующим AsEnumerable откатывается каждый столбец, даже если вы используете только два.
— Девин

1
@Devin: Да, я согласен - на самом деле это именно то, что делает второй пример запроса.
— 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 не знает, что с ним делать, 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.