LINQ to Entities non riconosce il metodo "System.String Format (System.String, System.Object, System.Object)"


88

Ho questa query 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
                   };
}

Tuttavia ha dei problemi. Sto cercando di creare attività. Per ogni nuova attività, quando imposto il testo del collegamento su una stringa costante come "Hello", va bene. Tuttavia, sopra sto cercando di creare il linktext della proprietà utilizzando le proprietà della fattura.

Ottengo questo errore:

base {System.SystemException} = {"LINQ to Entities non riconosce il metodo" System.String Format (System.String, System.Object, System.Object) "e questo metodo non può essere tradotto in un'espressione di archivio." }

Qualcuno sa perché? Qualcuno conosce un modo alternativo di farlo per farlo funzionare?


Sì, l'
ho

Risposte:


148

Entity Framework sta tentando di eseguire la proiezione sul lato SQL, dove non esiste alcun equivalente a string.Format. Utilizzare AsEnumerable()per forzare la valutazione di quella parte con Linq su Oggetti.

Sulla base della risposta precedente che ti ho dato, ristrutturerei la tua domanda in questo modo:

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

Inoltre vedo che usi entità correlate nella query ( Organisation.Name) assicurati di aggiungere il corretto Includealla tua query, o materializza specificamente quelle proprietà per un uso successivo, ad esempio:

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

Oltre al fatto che la parte di selezione di una nuova attività non può essere eseguita sul lato server a causa della traduzione dell'albero delle espressioni, si dovrebbe anche notare che non è desiderabile farlo. Presumibilmente, vuoi che le attività vengano create sul lato client. Pertanto la separazione tra query e creazione di attività potrebbe essere ancora più esplicita.
Tormod

3
Suggerirei anche di selezionare un tipo anonimo che contenga solo il numero di fattura e il nome organizzazione necessari. Se l'entità delle fatture è grande, la selezione i con il successivo AsEnumerable ritirerà ogni colonna anche se ne stai utilizzando solo due.
Devin

1
@ Devin: Sì, sono d'accordo - in effetti è esattamente ciò che sta facendo il secondo esempio di query.
BrokenGlass

15

IQueryablederiva da IEnumerable, la somiglianza principale è che quando si effettua la query viene pubblicata nel motore di database nella sua lingua, il momento sottile è dove si dice a C # di gestire i dati sul server (non lato client) o di dire a SQL di gestirli dati.

Quindi, in pratica, quando dici IEnumerable.ToString(), C # ottiene la raccolta dei dati e chiama ToString()l'oggetto. Ma quando dici che IQueryable.ToString()C # dice a SQL di chiamare ToString()l'oggetto ma non esiste un tale metodo in SQL.

Lo svantaggio è che quando gestisci i dati in C #, l'intera raccolta che stai esaminando deve essere creata in memoria prima che C # applichi i filtri.

Il modo più efficiente per farlo è eseguire la query come IQueryablecon tutti i filtri che puoi applicare.

Quindi crealo in memoria e fai la formattazione dei dati in 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")
                                                   });

3

Sebbene SQL non sappia cosa fare con un string.Format, può eseguire la concatenazione di stringhe.

Se esegui il codice seguente, dovresti ottenere i dati che stai cercando.

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

Una volta eseguita effettivamente la query, questa dovrebbe essere leggermente più veloce dell'utilizzo AsEnumerable(almeno è quello che ho trovato nel mio codice dopo aver avuto lo stesso errore originale di te). Se stai facendo qualcosa di più complesso con C #, dovrai comunque usarlo AsEnumerable.


2
Non sono sicuro del motivo per cui non è stato possibile adattare Linq per utilizzare la funzione FORMATMESSAGE docs.microsoft.com/en-us/sql/t-sql/functions/… Fino ad allora la soluzione è tua (senza forzare la materializzazione)
MemeDeveloper

2
A seconda della struttura del database e del numero di colonne correlate, l'utilizzo di questo metodo invece di AsEnumerable()può essere molto più efficiente. Evita AsEnumerable()e ToList()finché non vuoi davvero portare tutti i risultati nella memoria.
Chris Schaller
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.