LINQ to Entities не распознает метод метода System.String ToString (), и этот метод нельзя преобразовать в выражение хранилища.

126

Я переношу кое-что с одного сервера mysql на сервер sql, но не могу понять, как заставить этот код работать:

using (var context = new Context())
{
    ...

    foreach (var item in collection)
    {
        IQueryable<entity> pages = from p in context.pages
                                   where  p.Serial == item.Key.ToString()
                                   select p;
        foreach (var page in pages)
        {
            DataManager.AddPageToDocument(page, item.Value);
        }
    }

    Console.WriteLine("Done!");
    Console.Read();
}

Когда он входит во второй, foreach (var page in pages)он выдает исключение, говорящее:

LINQ to Entities не распознает метод System.String ToString (), и этот метод нельзя преобразовать в выражение хранилища.

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

Эрре Эфе
источник

Ответы:

134

Просто сохраните строку во временную переменную, а затем используйте ее в своем выражении:

var strItem = item.Key.ToString();

IQueryable<entity> pages = from p in context.pages
                           where  p.Serial == strItem
                           select p;

Проблема возникает из-за того, что на ToString()самом деле не выполняется, она превращается в MethodGroup, а затем анализируется и транслируется в SQL. Поскольку ToString()эквивалента нет , выражение неверно.

Примечание:

Убедитесь, что вы также ознакомились с ответом Алекса относительно SqlFunctionsвспомогательного класса, который был добавлен позже. Во многих случаях это может устранить необходимость во временной переменной.

мистифицировать
источник
14
Что, если мой ToString () применяется в левой части равенства? egpSerial.ToString () = элемент.
dotNET
3
@dotNet Это все равно не удастся, потому что все это превращается в выражение, которое Entity Framework пытается превратить в действительный SQL. Есть несколько методов, с которыми он умеет обращаться, но ToString()не один из них.
Джош
7
@ Джош: Я понимаю, что это не удастся. То, что я просил, - это решение этого сценария, потому что вышеупомянутое решение явно не может быть применено там.
dotNET
3
@ Джош: Я борюсь с одним из таких сценариев. Скажем, мой столбец OrderNumber - int, но мой пользователь хочет иметь возможность фильтровать список OrderNumber по мере ввода. Если он набрал 143 в поле поиска, ему нужны только те записи, которые имеют OrderNumber LIKE '% 143%' , Разве мне не нужно выполнять ToString () в столбце OrderNumber для этого?
dotNET
5
@dotNET - это один из тех сценариев, когда ORM падает прямо на лицо. Я думаю, что в таких ситуациях нормально перейти к прямому SQL через ExecuteQueryили с помощью Entity SQL сObjectQuery<T>
Джош
69

Как ответили другие, это ломается, потому что .ToString не может преобразовать в соответствующий SQL на пути в базу данных.

Однако Microsoft предоставляет класс SqlFunctions, который представляет собой набор методов, которые можно использовать в подобных ситуациях.

В этом случае вы ищете SqlFunctions.StringConvert :

from p in context.pages
where  p.Serial == SqlFunctions.StringConvert((double)item.Key.Id)
select p;

Хорошо, когда решение с временными переменными по каким-либо причинам нежелательно.

Подобно SqlFunctions, у вас также есть EntityFunctions (при этом EF6 устарел DbFunctions ), который предоставляет другой набор функций, которые также не зависят от источника данных (не ограничиваются, например, SQL).

Alex
источник
4
Они добавили класс SqlFunctions обратно в .NET 4, и я только узнаю об этом? Отличная находка.
Джеймс Скемп
24

Проблема в том, что вы вызываете ToString в запросе LINQ to Entities. Это означает, что синтаксический анализатор пытается преобразовать вызов ToString в его эквивалентный SQL (что невозможно ... отсюда и исключение).

Все, что вам нужно сделать, это переместить вызов ToString в отдельную строку:

var keyString = item.Key.ToString();

var pages = from p in context.entities
            where p.Serial == keyString
            select p;
Джастин Нисснер
источник
9

Была аналогичная проблема. Решил это, вызвав ToList () в коллекции сущностей и запросив список. Если коллекция небольшая, это вариант.

IQueryable<entity> pages = context.pages.ToList().Where(p=>p.serial == item.Key.ToString())

Надеюсь это поможет.

cynicaldoctor
источник
42
Обратите внимание, что при этом будут извлечены все сущности страницы из базы данных, и будет выполняться фильтрация на стороне клиента, а не в базе данных. Обычно это не очень хорошо.
lambinator
3
Верно, что этот метод был бы неэффективен для любой таблицы, содержащей более одной записи, то есть для всех существующих таблиц :-). Однако этот ответ действительно помог мне сегодня, потому что я делал проекцию .Select, которая включала toString (), поэтому вызов .ToList () перед этим не имел для меня никакого ущерба производительности, а вызов .ToList () позволил мне использовать .ToString () форматирование и мой оператор .Select ...
Натан Пратер
6

Измените его так, и он должен работать:

var key = item.Key.ToString();
IQueryable<entity> pages = from p in context.pages
                           where  p.Serial == key
                           select p;

Причина, по которой исключение не генерируется в строке, в которой объявлен запрос LINQ, а в строке, foreachявляется функция отложенного выполнения, т.е. запрос LINQ не выполняется, пока вы не попытаетесь получить доступ к результату. И это происходит в, foreachа не раньше.

Дэниел Хилгарт
источник
6

Приведите таблицу в Enumerable, затем вы вызываете методы LINQ с использованием ToString()метода внутри:

    var example = contex.table_name.AsEnumerable()
.Select(x => new {Date = x.date.ToString("M/d/yyyy")...)

Но будьте осторожны при вызове методов AsEnumerableили, ToListпотому что перед этим методом вы запросите все данные от всех сущностей. В моем случае выше я прочитал все table_nameстроки по одному запросу.

neustart47
источник
5
как правило , это не выбор хороший, .AsEnumerable () поместить все данные в памяти, вы можете более видеть об этом здесь: stackoverflow.com/questions/3311244/...
kavain
5

У меня сработало обновление до Entity Framework версии 6.2.0 .

Раньше у меня была версия 6.0.0.

Надеюсь это поможет,

93Ramadan
источник
1

В MVC предположим, что вы ищете запись (записи) на основе ваших требований или информации. Он работает правильно.

[HttpPost]
[ActionName("Index")]
public ActionResult SearchRecord(FormCollection formcollection)
{       
    EmployeeContext employeeContext = new EmployeeContext();

    string searchby=formcollection["SearchBy"];
    string value=formcollection["Value"];

    if (formcollection["SearchBy"] == "Gender")
    {
        List<MvcApplication1.Models.Employee> emplist = employeeContext.Employees.Where(x => x.Gender == value).ToList();
        return View("Index", emplist);
    }
    else
    {
        List<MvcApplication1.Models.Employee> emplist = employeeContext.Employees.Where(x => x.Name == value).ToList();
        return View("Index", emplist);
    }         
}
шакти
источник
2
Для лучшей практики или в производственных типах кода вы всегда должны иметь события базы данных на уровне службы или уровне данных, а не непосредственно в действии.
TGarrett 04
0

Если вы действительно хотите ввести ToStringтекст внутри своего запроса, вы можете написать посетителя дерева выражений, который перезаписывает вызов ToStringс помощью вызова соответствующей StringConvertфункции :

using System.Linq;
using System.Data.Entity.SqlServer;
using System.Linq.Expressions;
using static System.Linq.Expressions.Expression;
using System;

namespace ToStringRewriting {
    class ToStringRewriter : ExpressionVisitor {
        static MethodInfo stringConvertMethodInfo = typeof(SqlFunctions).GetMethods()
                 .Single(x => x.Name == "StringConvert" && x.GetParameters()[0].ParameterType == typeof(decimal?));

        protected override Expression VisitMethodCall(MethodCallExpression node) {
            var method = node.Method;
            if (method.Name=="ToString") {
                if (node.Object.GetType() == typeof(string)) { return node.Object; }
                node = Call(stringConvertMethodInfo, Convert(node.Object, typeof(decimal?));
            }
            return base.VisitMethodCall(node);
        }
    }
    class Person {
        string Name { get; set; }
        long SocialSecurityNumber { get; set; }
    }
    class Program {
        void Main() {
            Expression<Func<Person, Boolean>> expr = x => x.ToString().Length > 1;
            var rewriter = new ToStringRewriter();
            var finalExpression = rewriter.Visit(expr);
            var dcx = new MyDataContext();
            var query = dcx.Persons.Where(finalExpression);

        }
    }
}
Зев Шпиц
источник
Следует использовать FirstOrDefault, а не только First ... Если это первичный ключ, используйте Find, так как это работает лучше.
TGarrett 04
@TGarrett Единственное использование Firstздесь - для результатов GetMethods()возврата MethodInfo[]. AFAIK, MethodInfo[]не имеет Findметода и такого метода расширения. Но мне действительно следует использовать, Singleпотому что этот метод обнаруживается посредством отражения, и не будет ошибки времени компиляции, если соответствующий метод не может быть разрешен.
Зев Шпиц
0

В этом случае у меня такая же ошибка:

var result = Db.SystemLog
.Where(log =>
    eventTypeValues.Contains(log.EventType)
    && (
        search.Contains(log.Id.ToString())
        || log.Message.Contains(search)
        || log.PayLoad.Contains(search)
        || log.Timestamp.ToString(CultureInfo.CurrentUICulture).Contains(search)
    )
)
.OrderByDescending(log => log.Id)
.Select(r => r);

Потратив слишком много времени на отладку, я понял, что ошибка появилась в логическом выражении.

Первая строка search.Contains(log.Id.ToString())работает нормально, но последняя строка, посвященная объекту DateTime, вызвала ужасный сбой:

|| log.Timestamp.ToString(CultureInfo.CurrentUICulture).Contains(search)

Удалите проблемную строку и проблема решена.

Я не совсем понимаю, почему, но похоже, что ToString () - это выражение LINQ для строк, но не для сущностей. LINQ for Entities работает с такими запросами к базе данных, как SQL, а SQL не имеет понятия ToString (). Таким образом, мы не можем добавить ToString () в предложение .Where ().

Но как тогда работает первая строка? Вместо ToString () в SQL есть CASTи CONVERT, поэтому я предполагаю, что linq для сущностей использует это в некоторых простых случаях. Объекты DateTime не всегда оказываются такими простыми ...

pekaaw
источник
-8

Просто превратите запрос LINQ to Entity в запрос LINQ to Objects (например, вызов ToArray) в любое время, когда вам понадобится использовать вызов метода в запросе LINQ.

Т. Вебстер
источник
3
«в любое время, когда вам нужно использовать вызов метода» - плохой совет - при большом количестве записей это может стать большой проблемой. Принятый ответ намного лучше для этого сценария.
PeteGO