LINQ to Entities ne reconnaît pas la méthode 'System.String ToString ()', et cette méthode ne peut pas être traduite en une expression de magasin

126

Je migre certaines choses d'un serveur mysql vers un serveur SQL mais je ne peux pas comprendre comment faire fonctionner ce code:

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

Quand il entre dans la seconde, foreach (var page in pages)il lance une exception disant:

LINQ to Entities ne reconnaît pas la méthode 'System.String ToString ()', et cette méthode ne peut pas être traduite en une expression de magasin.

Quelqu'un sait pourquoi cela se produit?

Erre Efe
la source

Réponses:

134

Enregistrez simplement la chaîne dans une variable temporaire, puis utilisez-la dans votre expression:

var strItem = item.Key.ToString();

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

Le problème survient car il ToString()n'est pas vraiment exécuté, il est transformé en MethodGroup , puis analysé et traduit en SQL. Puisqu'il n'y a pas d' ToString()équivalent, l'expression échoue.

Remarque:

Assurez-vous également de consulter la réponse d'Alex concernant la SqlFunctionsclasse d'assistance qui a été ajoutée plus tard. Dans de nombreux cas, cela peut éliminer le besoin de la variable temporaire.

Josh
la source
14
Que faire si mon ToString () est appliqué sur le côté gauche de l'égalité? egpSerial.ToString () = élément.
dotNET
3
@dotNet Cela échouera toujours parce que tout est transformé en une expression, qu'Entity Framework essaie de transformer en SQL valide. Il existe certaines méthodes qu'il sait gérer, mais ToString()n'en fait pas partie.
Josh
7
@Josh: Je comprends que cela échouera. Ce que je demandais, c'est une solution de ce scénario, car la solution ci-dessus ne peut évidemment pas y être appliquée.
dotNET
3
@Josh: J'ai du mal avec un tel scénario. Disons que ma colonne OrderNumber est int, mais que mon utilisateur souhaite pouvoir filtrer la liste des OrderNumbers au fur et à mesure de sa saisie. S'il a tapé 143 dans le champ de recherche, il ne veut que les enregistrements qui ont un OrderNumber LIKE '% 143%' . N'ai-je pas besoin de faire ToString () sur la colonne OrderNumber pour y parvenir?
dotNET
5
@dotNET c'est l'un de ces scénarios où un ORM tombe sur son visage. Je pense que c'est correct dans ces situations de passer soit à SQL direct via ExecuteQueryou en utilisant Entity SQL avecObjectQuery<T>
Josh
69

Comme d'autres l'ont répondu, cela se rompt car .ToString ne parvient pas à se traduire en SQL pertinent sur le chemin dans la base de données.

Cependant, Microsoft fournit la classe SqlFunctions qui est une collection de méthodes pouvant être utilisées dans des situations comme celle-ci.

Dans ce cas, ce que vous recherchez ici est SqlFunctions.StringConvert :

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

Bon lorsque la solution avec des variables temporaires n'est pas souhaitable pour quelque raison que ce soit.

Semblable à SqlFunctions, vous avez également les EntityFunctions (avec EF6 obsolète par DbFunctions ) qui fournit un ensemble différent de fonctions qui sont également indépendantes de la source de données (non limitées à SQL par exemple).

Alex
la source
4
Ils ont ajouté la classe SqlFunctions dans .NET 4 et je suis juste en train d'apprendre à ce sujet? Excellente trouvaille.
James Skemp
24

Le problème est que vous appelez ToString dans une requête LINQ to Entities. Cela signifie que l'analyseur essaie de convertir l'appel ToString en son équivalent SQL (ce qui n'est pas possible ... d'où l'exception).

Tout ce que vous avez à faire est de déplacer l'appel ToString sur une ligne distincte:

var keyString = item.Key.ToString();

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

Eu un problème similaire. Résolu le problème en appelant ToList () sur la collection d'entités et en interrogeant la liste. Si la collection est petite, c'est une option.

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

J'espère que cela t'aides.

docteur cynique
la source
42
Veuillez noter que cela récupérera toutes les entités Page de la base de données et effectuera le filtrage du côté client au lieu de la base de données. Ce n'est généralement pas une bonne chose.
lambinator
3
Il est vrai que cette méthode serait inefficace pour toute table contenant plus d'un enregistrement, c'est-à-dire toutes les tables existantes :-). Cependant, cette réponse m'a aidé aujourd'hui parce que je faisais une projection .Select qui incluait toString () donc appeler .ToList () avant la main n'avait pas de pénalité de performance pour moi et appeler .ToList () m'a permis d'utiliser le .ToString () mise en forme et ma déclaration .Select ...
Nathan Prather
6

Changez-le comme ça et cela devrait fonctionner:

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

La raison pour laquelle l'exception n'est pas lancée dans la ligne de la requête LINQ est déclarée mais dans la ligne du foreachest la fonction d'exécution différée, c'est-à-dire que la requête LINQ n'est pas exécutée tant que vous n'essayez pas d'accéder au résultat. Et cela se produit dans le foreachet pas plus tôt.

Daniel Hilgarth
la source
6

Cast table vers Enumerable, puis vous appelez les méthodes LINQ avec using ToString()method inside:

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

Mais attention, lorsque vous appelez des méthodes AsEnumerableou des ToListméthodes, car vous demanderez toutes les données de toutes les entités avant cette méthode. Dans mon cas ci-dessus, je lis toutes les table_namelignes par une seule requête.

neustart47
la source
5
normalement n'est pas un bon choix, .AsEnumerable () met toutes les données en mémoire, vous pouvez en voir plus ici: stackoverflow.com/questions/3311244/...
kavain
5

La mise à niveau vers Entity Framework version 6.2.0 a fonctionné pour moi.

J'étais auparavant sur la version 6.0.0.

J'espère que cela t'aides,

93Ramadan
la source
1

Dans MVC, supposez que vous recherchez des enregistrements en fonction de vos besoins ou de vos informations. Cela fonctionne correctement.

[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
la source
2
Pour une meilleure pratique, ou dans les types de code de production, vous devez toujours avoir les événements de base de données dans une couche de service ou une couche de données et non directement dans l'action.
TGarrett
0

Si vous voulez vraiment taper ToStringdans votre requête, vous pouvez écrire un visiteur d'arborescence d'expression qui réécrit l'appel ToStringavec un appel à la StringConvertfonction appropriée :

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
la source
Devrait utiliser FirstOrDefault et pas seulement First ... S'il s'agit d'une clé primaire, utilisez Find, car cela fonctionne mieux.
TGarrett
@TGarrett La seule utilisation de Firstici est sur les résultats de GetMethods()quels retours MethodInfo[]. AFAIK, MethodInfo[]n'a pas de Findméthode, ni de méthode d'extension. Mais je devrais vraiment l'utiliser Singlecar cette méthode est trouvée par réflexion, et il n'y aura pas d'erreur de compilation si la méthode appropriée ne peut pas être résolue.
Zev Spitz
0

J'ai eu la même erreur dans ce cas:

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

Après avoir passé beaucoup trop de temps à déboguer, j'ai compris que l'erreur était apparue dans l'expression logique.

La première ligne search.Contains(log.Id.ToString())fonctionne bien, mais la dernière ligne qui traite d'un objet DateTime l'a fait échouer lamentablement:

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

Supprimez la ligne problématique et le problème est résolu.

Je ne comprends pas tout à fait pourquoi, mais il semble que ToString () soit une expression LINQ pour les chaînes, mais pas pour les entités. LINQ for Entities traite les requêtes de base de données comme SQL, et SQL n'a aucune notion de ToString (). En tant que tel, nous ne pouvons pas lancer ToString () dans une clause .Where ().

Mais comment fonctionne alors la première ligne? Au lieu de ToString (), SQL a CASTet CONVERT, donc ma meilleure estimation jusqu'à présent est que linq pour les entités l'utilise dans certains cas simples. Les objets DateTime ne sont pas toujours aussi simples ...

pekaaw
la source
-8

Transformez simplement la requête LINQ to Entity en requête LINQ to Objects (par exemple, appelez ToArray) chaque fois que vous devez utiliser un appel de méthode dans votre requête LINQ.

T. Webster
la source
3
"à chaque fois que vous devez utiliser un appel de méthode" est un mauvais conseil - avec de nombreux enregistrements, cela pourrait être un gros problème. La réponse acceptée est bien meilleure pour ce scénario.
PeteGO