LINQ to Entities ne reconnaît pas la méthode 'System.String Format (System.String, System.Object, System.Object)'

88

J'ai cette requête linq:

private void GetReceivedInvoiceTasks(User user, List<Task> tasks)
{
    var areaIds = user.Areas.Select(x => x.AreaId).ToArray();

    var taskList = from i in _db.Invoices
                   join a in _db.Areas on i.AreaId equals a.AreaId
                   where i.Status == InvoiceStatuses.Received && areaIds.Contains(a.AreaId)
                   select new Task {
                       LinkText = string.Format(Invoice {0} has been received from {1}, i.InvoiceNumber, i.Organisation.Name),
                       Link = Views.Edit
                   };
}

Il a cependant des problèmes. J'essaye de créer des tâches. Pour chaque nouvelle tâche, lorsque je règle le texte du lien sur une chaîne constante comme "Bonjour", tout va bien. Cependant ci-dessus, j'essaie de créer le lien de propriété en utilisant les propriétés de la facture.

J'obtiens cette erreur:

base {System.SystemException} = {"LINQ to Entities ne reconnaît pas la méthode 'System.String Format (System.String, System.Object, System.Object)', et cette méthode ne peut pas être traduite en une expression de magasin." }

Quelqu'un sait pourquoi? Quelqu'un connaît-il une autre façon de faire cela pour que cela fonctionne?

AnonyMouse
la source

Réponses:

148

Entity Framework tente d'exécuter votre projection du côté SQL, où il n'y a pas d'équivalent à string.Format. Utilisez AsEnumerable()pour forcer l'évaluation de cette pièce avec Linq aux objets.

Sur la base de la réponse précédente que je vous ai donnée, je restructurerais votre requête comme ceci:

int statusReceived = (int)InvoiceStatuses.Received;
var areaIds = user.Areas.Select(x=> x.AreaId).ToArray();

var taskList = (from i in _db.Invoices
               where i.Status == statusReceived && areaIds.Contains(i.AreaId)
               select i)
               .AsEnumerable()
               .Select( x => new Task()
               {
                  LinkText = string.Format("Invoice {0} has been received from {1}", x.InvoiceNumber, x.Organisation.Name),
                  Link = Views.Edit
                });

Aussi, je vois que vous utilisez des entités liées dans la requête ( Organisation.Name), assurez-vous d'ajouter le bon Includeà votre requête, ou matérialisez spécifiquement ces propriétés pour une utilisation ultérieure, c'est-à-dire:

var taskList = (from i in _db.Invoices
               where i.Status == statusReceived && areaIds.Contains(i.AreaId)
               select new { i.InvoiceNumber, OrganisationName = i.Organisation.Name})
               .AsEnumerable()
               .Select( x => new Task()
               {
                  LinkText = string.Format("Invoice {0} has been received from {1}", x.InvoiceNumber, x.OrganisationName),
                  Link = Views.Edit
                });
Verre brisé
la source
En plus du fait que la partie select-new-task ne peut pas se produire côté serveur en raison de la traduction de l'arborescence d'expression, il convient également de noter que cela n'est pas souhaitable. Vraisemblablement, vous voulez que les tâches soient créées côté client. Par conséquent, la séparation de la requête et de la création de tâches pourrait être encore plus explicite.
Tormod
3
Je recommanderais également de sélectionner un type anonyme qui contient juste le InvoiceNumber et Organisation.Name nécessaires. Si l'entité des factures est grande, le select i avec le AsEnumerable suivant retirera chaque colonne même si vous n'en utilisez que deux.
Devin
1
@Devin: Oui, je suis d'accord - en fait c'est exactement ce que fait le deuxième exemple de requête.
BrokenGlass
15

IQueryabledérive de IEnumerable, la ressemblance principale est que lorsque vous effectuez votre requête, elle est publiée dans le moteur de base de données dans son langage, le moment léger est celui où vous dites à C # de gérer les données sur le serveur (pas côté client) ou de dire à SQL de gérer Les données.

Donc, fondamentalement, quand vous dites IEnumerable.ToString(), C # obtient la collection de données et appelle ToString()l'objet. Mais quand vous dites que IQueryable.ToString()C # dit à SQL d'appeler ToString()l'objet, mais il n'y a pas de méthode de ce type dans SQL.

L'inconvénient est que lorsque vous gérez des données en C #, toute la collection que vous recherchez doit être créée en mémoire avant que C # applique les filtres.

Le moyen le plus efficace de le faire est de faire la requête comme IQueryableavec tous les filtres que vous pouvez appliquer.

Et puis construisez-le en mémoire et effectuez le formatage des données en C #.

IQueryable<Customer> dataQuery = Customers.Where(c => c.ID < 100 && c.ZIP == 12345 && c.Name == "John Doe");

 var inMemCollection = dataQuery.AsEnumerable().Select(c => new
                                                  {
                                                     c.ID
                                                     c.Name,
                                                     c.ZIP,
                                                     c.DateRegisterred.ToString("dd,MMM,yyyy")
                                                   });
Nikolay
la source
3

Bien que SQL ne sache pas quoi faire avec un, string.Formatil peut effectuer une concaténation de chaînes.

Si vous exécutez le code suivant, vous devriez obtenir les données que vous recherchez.

var taskList = from i in _db.Invoices
               join a in _db.Areas on i.AreaId equals a.AreaId
               where i.Status == InvoiceStatuses.Received && areaIds.Contains(a.AreaId)
               select new Task {
                   LinkText = "Invoice " + i.InvoiceNumber + "has been received from " + i.Organisation.Name),
                   Link = Views.Edit
               };

Une fois que vous avez réellement effectué la requête, cela devrait être légèrement plus rapide que l'utilisation AsEnumerable(du moins c'est ce que j'ai trouvé dans mon propre code après avoir eu la même erreur d'origine que vous). Si vous faites quelque chose de plus complexe avec C #, vous devrez toujours l'utiliser AsEnumerable.

d219
la source
2
Je ne sais pas pourquoi Linq n'a pas pu être adapté pour utiliser la fonction FORMATMESSAGE docs.microsoft.com/en-us/sql/t-sql/functions/... Jusque-là, la vôtre est la solution (sans forcer la matérialisation)
MemeDeveloper
2
Selon la structure de la base de données et le nombre de colonnes associées, l'utilisation de cette méthode au lieu de AsEnumerable()peut être beaucoup plus efficace. Évitez AsEnumerable()et ToList()jusqu'à ce que vous vouliez vraiment mettre tous les résultats en mémoire.
Chris Schaller le