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
источник
Да, изначально пропустил это
AnonyMouse
возможный дубликат LINQ to Entities не распознает метод 'System.String ToString ()' method
Джон Сондерс

Ответы:

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.

d219
источник
2
Не уверен, почему Linq не удалось адаптировать для использования функции FORMATMESSAGE docs.microsoft.com/en-us/sql/t-sql/functions/… До тех пор решение остается у вас (без принудительной материализации)
MemeDeveloper
2
В зависимости от структуры базы данных и количества связанных столбцов использование этого метода вместо AsEnumerable()может быть гораздо более эффективным. Избегайте AsEnumerable()и ToList()до тех пор, пока вы действительно не захотите запоминать все результаты.
Крис Шаллер,