LINQ to Entities erkennt die Methode 'System.String ToString ()' nicht und diese Methode kann nicht in einen Speicherausdruck übersetzt werden

126

Ich migriere einige Dinge von einem MySQL-Server auf einen SQL-Server, aber ich kann nicht herausfinden, wie dieser Code funktioniert:

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();
}

Wenn es in die Sekunde eintritt, wird foreach (var page in pages)eine Ausnahme ausgelöst, die besagt:

LINQ to Entities erkennt die Methode 'System.String ToString ()' nicht und diese Methode kann nicht in einen Speicherausdruck übersetzt werden.

Weiß jemand warum das passiert?

Erre Efe
quelle

Antworten:

134

Speichern Sie die Zeichenfolge einfach in einer temporären Variablen und verwenden Sie diese in Ihrem Ausdruck:

var strItem = item.Key.ToString();

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

Das Problem tritt auf, weil ToString()es nicht wirklich ausgeführt wird, sondern in eine MethodGroup umgewandelt und dann analysiert und in SQL übersetzt wird. Da es kein ToString()Äquivalent gibt, schlägt der Ausdruck fehl.

Hinweis:

Stellen Sie sicher, dass Sie auch Alex 'Antwort bezüglich der SqlFunctionsspäter hinzugefügten Hilfsklasse lesen . In vielen Fällen kann die Notwendigkeit der temporären Variablen entfallen.

Josh
quelle
14
Was ist, wenn mein ToString () auf der linken Seite der Gleichheit angewendet wird? egpSerial.ToString () = item.
dotNET
3
@dotNet Das wird immer noch fehlschlagen, da das Ganze in einen Ausdruck umgewandelt wird, den Entity Framework versucht, in gültiges SQL umzuwandeln. Es gibt einige Methoden, mit denen es umgehen kann, aber ToString()keine davon.
Josh
7
@ Josh: Ich verstehe, dass es scheitern wird. Was ich gefragt habe, ist eine Lösung dieses Szenarios, da die obige Lösung dort offensichtlich nicht angewendet werden kann.
dotNET
3
@ Josh: Ich kämpfe mit einem solchen Szenario. Angenommen, meine OrderNumber-Spalte ist int, aber mein Benutzer möchte in der Lage sein, die Liste der OrderNumbers während der Eingabe zu filtern. Wenn er 143 in das Suchfeld eingegeben hat, möchte er nur die Datensätze mit einer OrderNumber LIKE '% 143%'. . Muss ich nicht ToString () für die OrderNumber-Spalte ausführen, um dies zu erreichen?
dotNET
5
@dotNET Dies ist eines dieser Szenarien, in denen ein ORM ins Gesicht fällt. Ich denke, es ist in solchen Situationen in Ordnung, entweder über ExecuteQueryoder mithilfe von Entity SQL mitObjectQuery<T>
Josh
69

Wie andere geantwortet haben, bricht dies ab, da .ToString auf dem Weg in die Datenbank nicht in relevantes SQL übersetzt werden kann.

Microsoft stellt jedoch die SqlFunctions-Klasse bereit , eine Sammlung von Methoden, die in solchen Situationen verwendet werden können.

In diesem Fall suchen Sie hier nach SqlFunctions.StringConvert :

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

Gut, wenn die Lösung mit temporären Variablen aus irgendeinem Grund nicht wünschenswert ist.

Ähnlich wie bei SqlFunctions haben Sie auch die EntityFunctions (mit EF6, die von DbFunctions veraltet sind ), die einen anderen Satz von Funktionen bereitstellen, die auch datenquellenunabhängig sind (nicht beschränkt auf z. B. SQL).

Alex
quelle
4
Sie haben die SqlFunctions-Klasse in .NET 4 hinzugefügt, und ich lerne gerade davon? Hervorragender Fund.
James Skemp
24

Das Problem ist, dass Sie ToString in einer LINQ to Entities-Abfrage aufrufen. Das bedeutet, dass der Parser versucht, den ToString-Aufruf in das entsprechende SQL zu konvertieren (was nicht möglich ist ... daher die Ausnahme).

Sie müssen lediglich den ToString-Aufruf in eine separate Zeile verschieben:

var keyString = item.Key.ToString();

var pages = from p in context.entities
            where p.Serial == keyString
            select p;
Justin Niessner
quelle
9

Hatte ein ähnliches Problem. Es wurde behoben, indem ToList () für die Entitätssammlung aufgerufen und die Liste abgefragt wurde. Wenn die Sammlung klein ist, ist dies eine Option.

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

Hoffe das hilft.

zynischer Arzt
quelle
42
Bitte beachten Sie, dass dadurch alle Seitenentitäten aus der Datenbank abgerufen werden und die Filterung auf der Clientseite anstelle der Datenbank durchgeführt wird. Dies ist normalerweise keine gute Sache.
Lambinator
3
Es ist wahr, dass diese Methode für jede Tabelle, die mehr als einen Datensatz enthält, ineffizient wäre, was bedeutet, dass alle Tabellen existieren :-). Diese Antwort hat mir heute jedoch geholfen, da ich eine .Select-Projektion durchgeführt habe, die toString () enthielt, sodass das Aufrufen von .ToList () vorher keine Leistungseinbußen für mich hatte und das Aufrufen von .ToList () mir die Verwendung von .ToString () ermöglichte. Formatierung und meine .Select-Anweisung ...
Nathan Prather
6

Ändern Sie es so und es sollte funktionieren:

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

Der Grund, warum die Ausnahme nicht in der Zeile ausgelöst wird, in der die LINQ-Abfrage deklariert ist, sondern in der Zeile der foreachFunktion, ist die verzögerte Ausführung, dh die LINQ-Abfrage wird erst ausgeführt, wenn Sie versuchen, auf das Ergebnis zuzugreifen. Und das passiert im foreachund nicht früher.

Daniel Hilgarth
quelle
6

Cast table to Enumerable, dann rufen Sie LINQ-Methoden mit using- ToString()Methode auf:

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

Seien Sie jedoch vorsichtig, wenn Sie AsEnumerableoder ToListMethoden aufrufen , da Sie vor dieser Methode alle Daten von allen Entitäten anfordern. In meinem obigen Fall habe ich alle table_nameZeilen nach einer Anfrage gelesen .

neustart47
quelle
5
Normalerweise ist dies keine gute Wahl. DaEnumerable () alle Daten im Speicher ablegt, können Sie hier mehr darüber erfahren
kavain
5

Das Upgrade auf Entity Framework Version 6.2.0 hat bei mir funktioniert.

Ich war vorher auf Version 6.0.0.

Hoffe das hilft,

Ramaman
quelle
1

Nehmen Sie in MVC an, dass Sie Datensätze basierend auf Ihren Anforderungen oder Informationen suchen. Es funktioniert richtig.

[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);
    }         
}
Shakti
quelle
2
Für eine bessere Vorgehensweise oder bei Code-Produktionstypen sollten Sie die Datenbankereignisse immer in einer Service- oder Datenschicht und nicht direkt in der Aktion haben.
TGarrett
0

Wenn Sie ToStringIhre Abfrage wirklich eingeben möchten , können Sie einen Besucher des Ausdrucksbaums schreiben, der den Aufruf ToStringmit einem Aufruf der entsprechenden StringConvertFunktion neu schreibt :

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

        }
    }
}
Zev Spitz
quelle
Sollte FirstOrDefault verwenden und nicht nur First ... Wenn es sich um einen Primärschlüssel handelt, verwenden Sie Suchen, da dies eine bessere Leistung erbringt.
TGarrett
@TGarrett Die einzige Verwendung von Firsthier ist, auf deren Ergebnisse GetMethods()zurückgegeben wird MethodInfo[]. AFAIK MethodInfo[]hat weder eine FindMethode noch eine solche Erweiterungsmethode. Aber ich sollte es wirklich verwenden, Singleda diese Methode über Reflection gefunden wird und es keinen Fehler bei der Kompilierung gibt, wenn die entsprechende Methode nicht behoben werden kann.
Zev Spitz
0

Ich habe in diesem Fall den gleichen Fehler erhalten:

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

Nachdem ich viel zu viel Zeit mit dem Debuggen verbracht hatte, stellte ich fest, dass der logische Ausdruck einen Fehler aufwies.

Die erste Zeile search.Contains(log.Id.ToString())funktioniert einwandfrei, aber die letzte Zeile, die sich mit einem DateTime-Objekt befasst, hat dazu geführt, dass es kläglich fehlgeschlagen ist:

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

Entfernen Sie die problematische Linie und das Problem ist gelöst.

Ich verstehe nicht ganz warum, aber es scheint, dass ToString () ein LINQ-Ausdruck für Zeichenfolgen ist, aber nicht für Entitäten. LINQ for Entities behandelt Datenbankabfragen wie SQL, und SQL kennt ToString () nicht. Daher können wir ToString () nicht in eine .Where () -Klausel werfen.

Aber wie funktioniert dann die erste Zeile? Anstelle von ToString () hat SQL CASTund CONVERT, daher gehe ich bisher davon aus, dass linq for entity dies in einigen einfachen Fällen verwendet. DateTime-Objekte sind nicht immer so einfach ...

Pekaaw
quelle
-8

Verwandeln Sie die LINQ to Entity-Abfrage einfach in eine LINQ to Objects-Abfrage (z. B. ToArray aufrufen), wenn Sie einen Methodenaufruf in Ihrer LINQ-Abfrage verwenden müssen.

T. Webster
quelle
3
"Immer wenn Sie einen Methodenaufruf verwenden müssen" ist ein schlechter Rat - bei vielen Datensätzen könnte dies ein großes Problem sein. Die akzeptierte Antwort ist für dieses Szenario viel besser.
PeteGO