Почему 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 немного плохо работает: -)