Почему Entity Framework не сможет использовать ToString() в операторе LINQ?
Этот работает в LINQ-to-SQL:
var customersTest = from c in db.Customers
select new
{
Id = c.Id,
Addresses = from a in db.Addresses where c.Id.ToString() ==
a.ReferenzId select a
};
foreach (var item in customersTest)
{
Console.WriteLine(item.Id);
}
Но аналогичный пример в Entity Framework получает сообщение , в котором говорится, что он не может "перевести его на SQL", вот исходное сообщение об ошибке на немецком языке:
"LINQ to Entities" erkennt die Methode 'System.String ToString()' nicht, und diese Methode kann nicht in einen Speicherausdruck übersetzt Верден ".
Перевод:
"LINQ to Entities" не распознает Метод 'System.String ToString()', этот метод не может быть переведен выражение памяти.
Может ли кто-нибудь пролить свет на то, как мы могли бы заставить это выражение работать в Entity Framework или объяснить, почему он получает эту ошибку?
Ответы
Ответ 1
Проще говоря: LINQ to Entities не знает о преобразовании из вашего идентификатора в строку.
Каков тип c.ID
? Есть ли какая-нибудь причина, по которой это один тип для ID, а другой для ReferenzId
? Если это вообще возможно, сделайте их одного и того же типа, после чего у вас больше не будет проблем. Я не знаю, есть ли другие способы выполнения преобразований в LINQ для Entities - может быть, но выравнивание типов будет более чистым.
Кстати, это действительно похоже на соединение:
var query = from c in db.Customers
join a in db.Addresses on c.Id equals a.ReferenzId into addresses
select new { Id = c.Id, Addresses = addresses };
EDIT: для ответа на ваш комментарий - ToString
появляется в IntelliSense, потому что у компилятора нет реальной идеи, что ваш запрос будет означать или как он будет переведен. Он отлично подходит для С# и может генерировать правильное дерево выражений - это просто, что EF не знает, как преобразовать это дерево выражений в SQL.
Вы можете попробовать использовать Convert.ToString(c.Id)
вместо того, чтобы просто позвонить c.Id.ToString()
...
Ответ 2
Мне нет смысла, почему Linq2EF не переводит .ToString()
в правильный оператор SQL, как это делает Linq2SQL, только команда разработчиков Microsoft знает, почему они еще не реализовали ее. : - (
Но вы можете повысить приоритет для его реализации, если вы проголосуете за эту функцию, следуя этой ссылке.
К счастью, есть и 2 обходных решения, оба из которых я использовал недавно в EF-запросах:
I) Что помогло мне обойти это ограничение, было изменить запрос в список, например:
var customersList = (from c in db.Customers
select c).ToList(); // converts to IEnumerable<T> ...
var customersTest = (from c in customersList
select new {Id=c.ID.ToString()}); // ... which allows to use .ToString()
Оператор .ToList()
преобразуется в IEnumerable<T>
, где .ToString()
доступен. Примечание. В зависимости от требований вы также можете использовать .AsEnumerable()
, что имеет то преимущество, что поддерживается отсроченное исполнение, что лучше, если у вас есть несколько запросов linq, зависящих друг от друга, используя различные значения параметров (спасибо Divega за этот подсказку!).
Впоследствии вы можете использовать этот запрос, как хотите, например:
var customersTest2 = from c in customersTest
select new
{
Id = c.Id,
Addresses = from a in db.Addresses where c.Id == a.ReferenzId select a
};
Конечно, если вам нужно, вы можете добавить дополнительные свойства к объектам customersTest
по мере необходимости. Вы также можете оптимизировать запрос выше, я использовал только 3 шага для удобства чтения этого примера.
II). Для простых преобразований, и если вам нужно повторно использовать сгенерированный запрос в последующих подзапросах (и он должен оставаться IQueryable
), используйте SqlFunctions
из System.Data.Objects.SqlClient
, они будут быть правильно переведены в SQL-запросы.
Пример 1: преобразование даты (вы должны использовать даты, как показано ниже)
var customersTest = from c in db.Customers
select new {
strDate=SqlFunctions.DateName("dd", c.EndDate)
+"."+SqlFunctions.DateName("mm", c.EndDate)
+"."+SqlFunctions.DateName("yyyy", c.EndDate)
}
Пример 2: преобразование чисел в строку
var customersTest = from c in db.Customers
select new {
strID=SqlFunctions.StringConvert((double)c.ID)
}
Это должно помочь вам избавиться от большинства ситуаций, когда требуются преобразования в строки.
Обновление:. Если вы следовали за ссылкой, которую я дал вам в начале моего ответа, эта пропущенная функция тем временем получила 75 голосов и теперь (наконец!) реализованный Microsoft в EF 6.1. Всем, кто участвовал: Спасибо, что голосовали! Ваш голос был услышан.
Например:
var query = from e in context.Employees where e.EmployeeID.ToString() == "1" select e;
теперь будет переведен на:
DECLARE @p0 NVarChar(1000) = '1'
SELECT [t0].[EmployeeID], [t0].[LastName], [t0].[FirstName], [t0].[Title],
[t0].[TitleOfCourtesy], [t0].[BirthDate], [t0].[HireDate], [t0].[Address],[t0].[City],
[t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[HomePhone], [t0].[Extension],
[t0].[Photo], [t0].[Notes], [t0].[ReportsTo], [t0].[PhotoPath]
FROM [Employees] AS [t0]
WHERE (CONVERT(NVarChar,[t0].[EmployeeID])) = @p0
то есть. e.EmployeeID.ToString()
означает (CONVERT(NVarChar,[t0].[EmployeeID]))
.
Ответ 3
Entity Framework 6.1 RTM, которая была только что выпущена, теперь поддерживает .ToString()
Ответ 4
LINQ to Entities, насколько я понимаю (для v1), очень примитивен. В других словах он не знает, как взять метод расширения "ToString()" и сгенерировать SQL для него.
В LINQ to SQL он запускает метод расширения "ToString()" перед созданием SQL. Разница в том, что LINQ to Entities использует IQueryable вместо IEnumerable.
НО, из того, что я помню, кастинг должен работать (потому что кастинг - это тип данных, а SQL знает о CAST()).
So
c.Id.ToString() должно быть действительно (строка) c.Id
(также убедитесь, что это (строка), а не (String)).
Один из недостатков, которые я бы сказал об использовании Lambda (в Entity Framework) для генерации выражения SQL вместо чистого LINQ.
Помните, что использование CAST в левой части знака равенства в SQL немного плохо работает: -)