LINQ to Entities non riconosce il metodo "System.String ToString ()" e questo metodo non può essere tradotto in un'espressione di archivio


126

Sto migrando alcune cose da un server mysql a un server sql ma non riesco a capire come far funzionare questo codice:

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

Quando entra nel secondo foreach (var page in pages), genera un'eccezione che dice:

LINQ to Entities non riconosce il metodo "System.String ToString ()" e questo metodo non può essere tradotto in un'espressione di archivio.

Qualcuno sa perché questo accade?


Risposte:


134

Basta salvare la stringa in una variabile temporanea e quindi usarla nella tua espressione:

var strItem = item.Key.ToString();

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

Il problema sorge perché ToString()non viene realmente eseguito, viene trasformato in un MethodGroup e quindi analizzato e tradotto in SQL. Poiché non esiste un ToString()equivalente, l'espressione ha esito negativo.

Nota:

Assicurati di controllare anche la risposta di Alex per quanto riguarda la SqlFunctionsclasse di supporto che è stata aggiunta in seguito. In molti casi può eliminare la necessità della variabile temporanea.


14
Che cosa succede se il mio ToString () viene applicato sul lato sinistro dell'uguaglianza? egpSerial.ToString () = item.
dotNET,

3
@dotNet Ciò non funzionerà ancora perché l'intera cosa viene trasformata in un'espressione, che Entity Framework tenta di trasformare in SQL valido. Ci sono alcuni metodi che sa come gestire, ma ToString()non è uno di questi.
Josh

7
@Josh: capisco che fallirà. Quello che stavo chiedendo è una soluzione di quello scenario, perché la soluzione di cui sopra ovviamente non può essere applicata lì.
dotNET il

3
@Josh: sto lottando con uno di questi scenari. Dì che la colonna OrderNumber è int, ma il mio utente desidera poter filtrare l'elenco di OrderNumber mentre digita. Se ha digitato 143 nella casella di ricerca, desidera solo quei record che hanno un OrderNumber COME '% 143%' . Non è necessario eseguire ToString () nella colonna OrderNumber per ottenerlo?
dotNET,

5
@dotNET questo è uno di quegli scenari in cui un ORM cade sulla sua faccia. Penso che sia ok in quelle situazioni scendere in SQL diretto tramite ExecuteQueryo usando Entity SQL conObjectQuery<T>
Josh

69

Come altri hanno risposto, questo si interrompe perché .ToString non riesce a tradurre in SQL rilevante sulla strada nel database.

Tuttavia, Microsoft fornisce la classe SqlFunctions che è una raccolta di metodi che possono essere utilizzati in situazioni come questa.

Per questo caso, quello che stai cercando qui è SqlFunctions.StringConvert :

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

Buono quando la soluzione con variabili temporanee non è desiderabile per qualsiasi motivo.

Simile a SqlFunctions hai anche EntityFunctions (con EF6 obsoleto da DbFunctions ) che fornisce un diverso set di funzioni che sono anche indipendenti dall'origine dati (non limitati ad esempio a SQL).


4
Hanno aggiunto la classe SqlFunctions in .NET 4 e sto solo imparando? Ottima scoperta.
James Skemp,

24

Il problema è che si chiama ToString in una query LINQ to Entities. Ciò significa che il parser sta cercando di convertire la chiamata ToString nel suo equivalente SQL (che non è possibile ... da qui l'eccezione).

Tutto quello che devi fare è spostare la chiamata ToString su una linea separata:

var keyString = item.Key.ToString();

var pages = from p in context.entities
            where p.Serial == keyString
            select p;

9

Ha avuto un problema simile. Risolto chiamando ToList () sulla raccolta di entità e interrogando l'elenco. Se la raccolta è piccola, questa è un'opzione.

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

Spero che questo ti aiuti.


42
Si noti che questo recupererà tutte le entità Page dal database e eseguirà il filtraggio sul lato client anziché il db .. di solito non è una buona cosa.
lambinator

3
È vero che questo metodo sarebbe inefficiente per qualsiasi tabella che contiene più di un record, il che significa che tutte le tabelle esistenti :-). Tuttavia, questa risposta mi ha aiutato oggi perché stavo facendo una proiezione .Select che includeva toString () così chiamando .ToList () prima mano non aveva penalità di prestazione per me e chiamando .ToList () mi ha permesso di usare .ToString () formattazione e la mia dichiarazione .Select ...
Nathan Prather,

6

Modificalo in questo modo e dovrebbe funzionare:

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

Il motivo per cui l'eccezione non viene generata nella riga in cui viene dichiarata la query LINQ ma nella riga della foreachè la funzione di esecuzione differita, ovvero la query LINQ non viene eseguita fino a quando non si tenta di accedere al risultato. E questo succede nel foreache non prima.


6

Trasmetti la tabella a Enumerable, quindi chiami i metodi LINQ usando il ToString()metodo all'interno:

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

Ma fai attenzione quando chiami AsEnumerableo ToListmetodi perché richiederai tutti i dati da tutte le entità prima di questo metodo. Nel mio caso sopra ho letto tutte le table_namerighe di una richiesta.


5
normalmente non è una scelta buona, .AsEnumerable () ha messo tutti i dati in memoria, si può vedere di più qui: stackoverflow.com/questions/3311244/...
kavain

5

L'aggiornamento a Entity Framework Versione 6.2.0 ha funzionato per me.

Ero precedentemente sulla versione 6.0.0.

Spero che questo ti aiuti,


1

In MVC, supponi di cercare record in base alle tue esigenze o informazioni. Funziona correttamente.

[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
Per una migliore pratica, o nei tipi di produzione di codice, è necessario disporre sempre degli eventi del database in un livello di servizio o livello dati e non direttamente nell'azione.
TGarrett,

0

Se si desidera davvero digitare ToStringall'interno della query, è possibile scrivere un visitatore dell'albero delle espressioni a cui riscrive la chiamata ToStringcon una chiamata alla StringConvertfunzione appropriata :

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

        }
    }
}

Dovrebbe usare FirstOrDefault e non solo First ... Se è una chiave primaria, allora usa Trova, poiché funziona meglio.
TGarrett,

@TGarrett L'unico utilizzo di Firstquesto è sui risultati di GetMethods()cui ritorna MethodInfo[]. AFAIK, MethodInfo[]non ha un Findmetodo, né esiste un tale metodo di estensione. Ma dovrei davvero usarlo Singleperché questo metodo viene trovato tramite reflection e non ci sarà un errore di compilazione se il metodo appropriato non può essere risolto.
Zev Spitz,

0

Ho avuto lo stesso errore in questo caso:

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

Dopo aver passato troppo tempo a eseguire il debug, ho capito che l'errore appariva nell'espressione logica.

La prima riga search.Contains(log.Id.ToString())funziona bene, ma l'ultima riga che si occupa di un oggetto DateTime ha fatto fallire miseramente:

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

Rimuovere la linea problematica e il problema risolto.

Non capisco bene perché, ma sembra che ToString () sia un'espressione LINQ per le stringhe, ma non per le entità. LINQ for Entities gestisce le query del database come SQL e SQL non ha nozioni di ToString (). Di conseguenza, non possiamo gettare ToString () in una clausola .Where ().

Ma come funziona la prima riga? Invece di ToString (), SQL ha CASTe CONVERT, quindi la mia ipotesi migliore finora è che linq per le entità lo usi in alcuni casi semplici. Gli oggetti DateTime non sono sempre così semplici ...


-8

Basta trasformare la query LINQ to Entity in una query LINQ to Objects (ad esempio, chiamare ToArray) ogni volta che è necessario utilizzare una chiamata di metodo nella query LINQ.


3
"ogni volta che è necessario utilizzare una chiamata di metodo" è un cattivo consiglio - con molti record questo potrebbe essere un grosso problema. La risposta accettata è molto meglio per questo scenario.
PeteGO
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.