89 votes

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

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

Mais il y a des problèmes. J'essaie de créer des tâches. Pour chaque nouvelle tâche, lorsque je fixe le texte du lien à une chaîne constante comme "Hello", tout va bien. Cependant, au-dessus, j'essaie de construire le texte du lien en utilisant les propriétés de la facture.

Je reçois 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-il pourquoi ? Quelqu'un connaît-il une autre façon de procéder pour que cela fonctionne ?

0 votes

Oui, j'ai manqué ça à l'origine.

0 votes

150voto

BrokenGlass Points 91618

Entity Framework essaie 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 partie avec Linq to Objects.

Basé sur sur la réponse précédente Je vous ai donné que 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
                });

Je vois également que vous utilisez des entités liées dans la requête ( Organisation.Name ) assurez-vous que vous ajoutez le bon Include à votre requête, ou matérialiser 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
                });

0 votes

Outre le fait que la partie select-new-task ne peut pas se produire côté serveur en raison de la traduction de l'arbre d'expression, il convient également de noter qu'il n'est pas souhaitable de le faire. On peut supposer que vous souhaitez 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 des tâches pourrait être encore plus explicite.

3 votes

Je recommande également de sélectionner un type anonyme qui ne contient que le numéro de facture et le nom de l'organisation. Si l'entité factures est grande, la sélection i avec le AsEnumerable qui s'ensuit fera apparaître toutes les colonnes, même si vous n'en utilisez que deux.

1 votes

@Devin : Oui, je suis d'accord - en fait, c'est exactement ce que fait le deuxième exemple de requête.

15voto

Nikolay Points 101

IQueryable provient de IEnumerable La principale ressemblance est que lorsque vous effectuez votre requête, elle est envoyée au moteur de la base de données dans son langage. Le moment délicat 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, en gros, quand vous dites IEnumerable.ToString() C# récupère la collection de données et appelle ToString() sur l'objet. Mais quand vous dites IQueryable.ToString() C# dit à SQL d'appeler ToString() sur l'objet mais il n'y a pas de telle méthode en SQL.

L'inconvénient est que lorsque vous manipulez des données en C#, la collection entière que vous recherchez doit être construite en mémoire avant que C# n'applique les filtres.

La manière la plus efficace de le faire est de faire la requête en tant que IQueryable avec tous les filtres que vous pouvez appliquer.

Et ensuite le construire en mémoire et faire 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")
                                                   });

3voto

d219 Points 1172

Alors que SQL ne sait pas quoi faire d'une string.Format il peut effectuer la concaténation de chaînes de caractères.

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 la requête exécutée, cette méthode devrait être légèrement plus rapide que l'utilisation de l'option AsEnumerable (du moins, c'est ce que j'ai trouvé dans mon propre code après avoir eu la même erreur initiale que vous). Si vous faites quelque chose de plus complexe avec C#, vous devrez quand même utiliser AsEnumerable cependant.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X