LINQ to Entities no reconoce el método 'System.String ToString ()', y este método no se puede traducir a una expresión de tienda

126

Estoy migrando algunas cosas de un servidor mysql a un servidor sql, pero no puedo entender cómo hacer que este código funcione:

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

Cuando entra en el segundo foreach (var page in pages), lanza una excepción que dice:

LINQ to Entities no reconoce el método 'System.String ToString ()', y este método no se puede traducir a una expresión de tienda.

Alguien sabe por qué pasa esto?

Erre Efe
fuente

Respuestas:

134

Simplemente guarde la cadena en una variable temporal y luego úsela en su expresión:

var strItem = item.Key.ToString();

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

El problema surge porque ToString()no se ejecuta realmente, se convierte en un MethodGroup y luego se analiza y traduce a SQL. Como no hay ToString()equivalente, la expresión falla.

Nota:

Asegúrese de consultar también la respuesta de Alex con respecto a la SqlFunctionsclase auxiliar que se agregó más tarde. En muchos casos, puede eliminar la necesidad de la variable temporal.

Josh
fuente
14
¿Qué sucede si mi ToString () se aplica en el lado izquierdo de la igualdad? egpSerial.ToString () = elemento.
dotNET
3
@dotNet Eso todavía fallará porque todo se convierte en una Expresión, que Entity Framework intenta convertir en SQL válido. Hay algunos métodos que sabe manejar, pero ToString()no es uno de ellos.
Josh
77
@ Josh: Entiendo que fallará. Lo que estaba pidiendo es una solución de ese escenario, porque la solución anterior obviamente no se puede aplicar allí.
dotNET
3
@ Josh: Estoy luchando con uno de esos escenarios. Digamos que mi columna OrderNumber es int, pero mi usuario quiere poder filtrar la lista de OrderNumbers a medida que escribe. Si ha escrito 143 en el cuadro de búsqueda, solo quiere aquellos registros que tengan un OrderNumber LIKE '% 143%' . ¿No necesito hacer ToString () en la columna OrderNumber para lograrlo?
dotNET
55
@dotNET este es uno de esos escenarios en los que un ORM cae sobre su cara. Creo que está bien en esas situaciones desplegarse en SQL directo a través de ExecuteQueryo utilizando Entity SQL conObjectQuery<T>
Josh
69

Como otros han respondido, esto se rompe porque .ToString no puede traducirse a SQL relevante en el camino hacia la base de datos.

Sin embargo, Microsoft proporciona la clase SqlFunctions que es una colección de métodos que se pueden usar en situaciones como esta.

Para este caso, lo que está buscando aquí es SqlFunctions.StringConvert :

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

Bueno cuando la solución con variables temporales no es deseable por alguna razón.

Similar a SqlFunctions, también tiene las EntityFunctions (con EF6 obsoleto por DbFunctions ) que proporciona un conjunto diferente de funciones que también son independientes del origen de datos (no limitado a, por ejemplo, SQL).

Alex
fuente
44
Agregaron la clase SqlFunctions de nuevo en .NET 4 y solo estoy aprendiendo sobre eso. Excelente hallazgo
James Skemp
24

El problema es que está llamando a ToString en una consulta LINQ to Entities. Eso significa que el analizador está tratando de convertir la llamada ToString en su SQL equivalente (que no es posible ... de ahí la excepción).

Todo lo que tiene que hacer es mover la llamada ToString a una línea separada:

var keyString = item.Key.ToString();

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

Tuve un problema similar Lo resolvió llamando a ToList () en la colección de entidades y consultando la lista. Si la colección es pequeña, esta es una opción.

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

Espero que esto ayude.

doctor cínico
fuente
42
Tenga en cuenta que esto recuperará todas las entidades de la página de la base de datos y hará el filtrado en el lado del cliente en lugar de la base de datos ... por lo general, no es algo bueno.
lambinator
3
Es cierto que este método sería ineficiente para cualquier tabla que contenga más de un registro, es decir, todas las tablas existentes :-). Sin embargo, esta respuesta me ayudó hoy porque estaba haciendo una proyección .Select que incluía toString (), por lo que llamar a .ToList () antes no tenía ninguna penalización de rendimiento y llamar a .ToList () me permitió usar .ToString () formato y mi declaración .Select ...
Nathan Prather
6

Cámbialo así y debería funcionar:

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

La razón por la cual la excepción no se arroja en la línea se declara la consulta LINQ sino en la línea de la foreachfunción de ejecución diferida, es decir, la consulta LINQ no se ejecuta hasta que intente acceder al resultado. Y esto sucede en el foreachy no antes.

Daniel Hilgarth
fuente
6

Transmitir tabla a Enumerable, luego llama a los métodos LINQ con el uso del ToString()método dentro:

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

Pero tenga cuidado, cuando llame AsEnumerableo ToListmétodos porque solicitará todos los datos de todas las entidades antes de este método. En mi caso anterior, leí todas las table_namefilas por una solicitud.

neustart47
fuente
55
normalmente no es una buena opción, .AsEnumerable () pone todos los datos en la memoria, puede ver más sobre esto aquí: stackoverflow.com/questions/3311244/…
kavain
5

La actualización a Entity Framework Versión 6.2.0 funcionó para mí.

Anteriormente estaba en la versión 6.0.0.

Espero que esto ayude,

93 Ramadán
fuente
1

En MVC, suponga que está buscando registros según sus requisitos o información. Funciona correctamente

[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
fuente
2
Para una mejor práctica, o en tipos de código de producción, siempre debe tener los eventos de la base de datos en una capa de servicio o capa de datos y no directamente en la acción.
TGarrett
0

Si realmente desea escribir ToStringdentro de su consulta, puede escribir un visitante del árbol de expresiones que reescriba la llamada ToStringcon una llamada a la StringConvertfunción adecuada :

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
fuente
Debería usar FirstOrDefault y no solo First ... Si es una clave principal, use Find, ya que funciona mejor.
TGarrett
@TGarrett El único uso de Firstaquí está en los resultados de los GetMethods()cuales regresa MethodInfo[]. AFAIK, MethodInfo[]no tiene un Findmétodo, ni existe tal método de extensión. Pero realmente debería usarlo Singleporque este método se encuentra a través de la reflexión, y no habrá un error en tiempo de compilación si no se puede resolver el método apropiado.
Zev Spitz
0

Recibí el mismo error en este 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);

Después de pasar demasiado tiempo depurando, descubrí que el error apareció en la expresión lógica.

La primera línea search.Contains(log.Id.ToString())funciona bien, pero la última línea que trata con un objeto DateTime hizo que fallara miserablemente:

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

Elimine la línea problemática y el problema resuelto.

No entiendo completamente por qué, pero parece que ToString () es una expresión LINQ para cadenas, pero no para Entidades. LINQ for Entities trata consultas de bases de datos como SQL, y SQL no tiene noción de ToString (). Como tal, no podemos lanzar ToString () en una cláusula .Where ().

¿Pero cómo funciona la primera línea? En lugar de ToString (), SQL tiene CASTy CONVERT, así que mi mejor suposición hasta ahora es que linq para entidades usa eso en algunos casos simples. Los objetos DateTime no siempre son tan simples ...

Pekaaw
fuente
-8

Simplemente convierta la consulta LINQ to Entity en una consulta LINQ to Objects (por ejemplo, llame a ToArray) cada vez que necesite usar una llamada a un método en su consulta LINQ.

T. Webster
fuente
3
"cada vez que necesite usar una llamada a un método" es un mal consejo; con muchos registros esto podría ser un gran problema. La respuesta aceptada es mucho mejor para este escenario.
PeteGO