O LINQ to Entities não reconhece o método 'System.String ToString ()' e esse método não pode ser convertido em uma expressão de armazenamento

126

Estou migrando algumas coisas de um servidor mysql para um servidor sql, mas não consigo descobrir como fazer esse código funcionar:

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 no segundo foreach (var page in pages), lança uma exceção dizendo:

O LINQ to Entities não reconhece o método 'System.String ToString ()' e esse método não pode ser convertido em uma expressão de armazenamento.

Alguém sabe por que isso acontece?

Erre Efe
fonte

Respostas:

134

Apenas salve a string em uma variável temporária e use-a na sua expressão:

var strItem = item.Key.ToString();

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

O problema surge porque, na ToString()verdade, não é executado, ele é transformado em um MethodGroup e, em seguida, analisado e traduzido para SQL. Como não há ToString()equivalente, a expressão falha.

Nota:

Verifique também a resposta de Alex sobre a SqlFunctionsclasse auxiliar que foi adicionada mais tarde. Em muitos casos, pode eliminar a necessidade da variável temporária.

Josh
fonte
14
E se meu ToString () estiver sendo aplicado no lado esquerdo da igualdade? egpSerial.ToString () = item.
dotNET 26/03
3
@dotNet Isso ainda falhará porque a coisa toda se transformou em uma Expressão, que o Entity Framework tenta transformar em SQL válido. Existem alguns métodos que ele sabe manipular, mas ToString()não é um deles.
27413 Josh
7
@ Josh: Eu entendo que isso irá falhar. O que eu estava pedindo é uma solução para esse cenário, porque a solução acima obviamente não pode ser aplicada lá.
dotNET 27/03
3
@ Josh: Estou lutando com um desses cenários. Diga que minha coluna OrderNumber é int, mas meu usuário deseja filtrar a lista de OrderNumbers enquanto digita. Se ele digitou 143 na caixa de pesquisa, ele quer apenas os registros que possuem um OrderNumber LIKE '% 143%' . Não preciso fazer ToString () na coluna OrderNumber para alcançá-lo?
dotNET 27/03
5
@dotNET este é um daqueles cenários em que um ORM cai sobre ele. Eu acho que é ok nessas situações a cair para baixo em qualquer SQL reta via ExecuteQueryou usando Entity SQL comObjectQuery<T>
Josh
69

Como outros responderam, isso é interrompido porque .ToString falha ao converter para SQL relevante no caminho para o banco de dados.

No entanto, a Microsoft fornece a classe SqlFunctions que é uma coleção de métodos que podem ser usados ​​em situações como essa.

Para este caso, o que você está procurando aqui é SqlFunctions.StringConvert :

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

Bom quando a solução com variáveis ​​temporárias não é desejável por qualquer motivo.

Semelhante ao SqlFunctions, você também tem o EntityFunctions (com EF6 obsoleto pelo DbFunctions ) que fornece um conjunto diferente de funções que também são independentes de fonte de dados (não limitadas a, por exemplo, SQL).

Alex
fonte
4
Eles adicionaram a classe SqlFunctions novamente no .NET 4 e eu estou apenas aprendendo sobre isso? Excelente localização.
James Skemp
24

O problema é que você está chamando o ToString em uma consulta LINQ to Entities. Isso significa que o analisador está tentando converter a chamada ToString em seu SQL equivalente (o que não é possível ... daí a exceção).

Tudo o que você precisa fazer é mover a chamada ToString para uma linha separada:

var keyString = item.Key.ToString();

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

Teve um problema semelhante. Resolvido, chamando ToList () na coleção de entidades e consultando a lista. Se a coleção for pequena, essa é uma opção.

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

Espero que isto ajude.

médico cínico
fonte
42
Observe que isso recuperará todas as entidades da página do banco de dados e fará a filtragem no lado do cliente em vez do banco de dados. Geralmente não é uma coisa boa.
lambinator
3
É verdade que esse método seria ineficiente para qualquer tabela que contenha mais de um registro, significando todas as tabelas existentes :-). No entanto, essa resposta me ajudou hoje porque eu estava fazendo uma projeção .Select que incluía toString (), portanto, chamar .ToList () antes não tinha nenhuma penalidade de desempenho para mim e chamar .ToList () me permitiu usar o .ToString () formatação e minha declaração .Select ...
Nathan Prather
6

Altere assim e deve funcionar:

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

O motivo pelo qual a exceção não é lançada na linha em que a consulta LINQ é declarada, mas na linha do foreaché o recurso de execução adiada, ou seja, a consulta LINQ não é executada até que você tente acessar o resultado. E isso acontece no foreache não antes.

Daniel Hilgarth
fonte
6

Transmitir tabela para Enumerable, então você chama métodos LINQ usando o ToString()método dentro:

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

Mas tenha cuidado ao chamar métodos AsEnumerableou ToListporque você solicitará todos os dados de todas as entidades antes desse método. No meu caso acima, li todas as table_namelinhas por uma solicitação.

neustart47
fonte
5
normalmente não é uma boa escolha, .AsEnumerable () colocar todos os dados na memória, você pode ver mais sobre ele aqui: stackoverflow.com/questions/3311244/...
kavain
5

A atualização para o Entity Framework Versão 6.2.0 funcionou para mim.

Eu estava anteriormente na versão 6.0.0.

Espero que isto ajude,

93Ramadan
fonte
1

No MVC, suponha que você esteja pesquisando registros com base em seus requisitos ou informações. Está funcionando corretamente.

[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
fonte
2
Para uma prática melhor, ou nos tipos de código de produção, você sempre deve ter os eventos do banco de dados em uma camada de serviço ou camada de dados e não diretamente na ação.
TREPRODUZINDO #
0

Se você realmente deseja digitar ToStringdentro de sua consulta, pode escrever um visitante da árvore de expressão que reescreva a chamada ToStringcom uma chamada para a StringConvertfunção apropriada :

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
fonte
Deve estar usando FirstOrDefault e não apenas First ... Se for uma chave primária, use Find, pois isso tem um desempenho melhor.
TREVETT4
@TGarrett O único uso Firstaqui é nos resultados dos GetMethods()quais retorna MethodInfo[]. AFAIK, MethodInfo[]não possui um Findmétodo, nem existe um método de extensão. Mas eu realmente devo usar Singleporque esse método está sendo encontrado via reflexão e não haverá um erro em tempo de compilação se o método apropriado não puder ser resolvido.
Zev Spitz 04/10
0

Eu recebi o mesmo erro neste 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);

Depois de passar muito tempo depurando, descobri que o erro aparecia na expressão lógica.

A primeira linha search.Contains(log.Id.ToString())funciona bem, mas a última linha que lida com um objeto DateTime fez com que falhasse miseravelmente:

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

Remova a linha problemática e o problema resolvido.

Eu não entendo completamente o porquê, mas parece que ToString () é uma expressão LINQ para strings, mas não para Entidades. O LINQ for Entities lida com consultas de banco de dados como SQL e SQL não tem noção de ToString (). Como tal, não podemos lançar ToString () em uma cláusula .Where ().

Mas como funciona a primeira linha? Em vez de ToString (), o SQL tem CASTe CONVERT, portanto, o meu melhor palpite até agora é que o linq for entity use isso em alguns casos simples. Os objetos DateTime nem sempre são tão simples ...

pekaaw
fonte
-8

Basta transformar a consulta LINQ to Entity em uma consulta LINQ to Objects (por exemplo, chamar ToArray) sempre que precisar usar uma chamada de método na sua consulta LINQ.

T. Webster
fonte
3
"sempre que você precisar usar uma chamada de método" é um péssimo conselho - com muitos registros, isso pode ser um grande problema. A resposta aceita é muito melhor para esse cenário.
PeteGO