LINQ 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 Entities не розпізнає метод 'System.String ToString ()', і цей метод не може бути переведений у вираз зберігання.

Хтось знає, чому це відбувається?


Повідомлення, пов’язане з цим - LINQ до сутностей не розпізнає метод та реалізацію схеми специфікацій Entity Framework
RBT

Відповіді:


134

Просто збережіть рядок до змінної temp, а потім використовуйте це у своєму виразі:

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
@Josh: Я розумію, що це не вдасться. Те, що я просив, - це рішення цього сценарію, оскільки вищезазначене рішення явно не може бути застосовано там.
dotNET

3
@Josh: Я борюся з одним таким сценіором. Скажіть, що мій стовпчик OrderNumber - це int, але мій користувач хоче мати можливість фільтрувати список порядкових номерів під час введення. Якщо він набрав 143 у вікні пошуку, він хоче лише тих записів, які мають OrderNumber LIKE '% 143%' . Чи не потрібно робити ToString () у стовпці OrderNumber, щоб досягти цього?
dotNET

5
@dotNET - це один із тих сценаріїв, коли ORM падає на обличчя. Я думаю, що в цих ситуаціях нормально перейти до прямого SQL через ExecuteQueryабо за допомогою Entity SQL зObjectQuery<T>
Josh

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).


4
Вони додали клас SqlFunctions ще в .NET 4, і я просто про це дізнаюся? Відмінна знахідка.
Джеймс Скемп

24

Проблема полягає в тому, що ви викликаєте ToString у запиті LINQ до 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())

Сподіваюся, це допомагає.


42
Зауважте, що це вилучить усі об'єкти сторінки з бази даних, а фільтрування на стороні клієнта замість db .. зазвичай це не дуже добре.
ламбінатар

3
Це правда, цей метод виявиться неефективним для будь-якої таблиці, яка містить більше однієї записи, тобто всі існуючі таблиці :-). Однак ця відповідь мені сьогодні допомогла, тому що я робив проекцію .Select, яка включала toString (), так що дзвонила. форматування та мій .Вибір заяви ...
Nathan Prather

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рядки одним запитом.


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

5

Для мене працювало оновлення до Entity Framework версії 6.2.0 .

Раніше я був на версії 6.0.0.

Сподіваюся, це допомагає,


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

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

@TGarrett Єдине використання Firstтут є результатами, GetMethods()які повертаються MethodInfo[]. AFAIK MethodInfo[]не має Findметоду, а також немає такого методу розширення. Але я дійсно повинен використовувати, Singleтому що цей метод знаходимо за допомогою відображення, і не буде помилки часу компіляції, якщо відповідний метод не вдасться вирішити.
Zev Spitz

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 для рядків, але не для Entities. LINQ for Entities займається запитами до бази даних, як SQL, а SQL не має поняття ToString (). Таким чином, ми не можемо кидати ToString () у пункт .Where ().

Але як тоді працює перший рядок? Замість ToString (), SQL мають CASTі CONVERT, тому я найкраще здогадуюсь, що linq для сутностей використовує це в деяких простих випадках. Об'єкти DateTime не завжди виявляються такими простими ...


-8

Просто перетворіть LINQ на запит Entity у запит LINQ to Objects (наприклад, виклик ToArray) у будь-який час, коли вам потрібно використовувати виклик методу у вашому запиті LINQ.


3
"в будь-який час вам потрібно використовувати виклик методу" - це погана порада - при багатьох записах це може бути великою проблемою. Прийнята відповідь набагато краща для цього сценарію.
PeteGO
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.