LINQ to Entities erkennt die Methode 'System.String Format (System.String, System.Object, System.Object)' nicht.

86

Ich habe diese Linq-Abfrage:

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

Es hat jedoch Probleme. Ich versuche Aufgaben zu erstellen. Für jede neue Aufgabe ist es in Ordnung, wenn ich den Linktext auf eine konstante Zeichenfolge wie "Hallo" setze. Oben versuche ich jedoch, den Eigenschaftslinktext unter Verwendung der Eigenschaften der Rechnung zu erstellen.

Ich erhalte diesen Fehler:

base {System.SystemException} = {"LINQ to Entities erkennt die Methode 'System.String Format (System.String, System.Object, System.Object)' nicht und diese Methode kann nicht in einen Speicherausdruck übersetzt werden." }}

Weiß jemand warum? Kennt jemand eine alternative Möglichkeit, dies zu tun, damit es funktioniert?

AnonyMouse
quelle
Ja, habe das ursprünglich verpasst
AnonyMouse

Antworten:

145

Entity Framework versucht, Ihre Projektion auf der SQL-Seite auszuführen, für die es keine Entsprechung gibt string.Format. Verwenden Sie AsEnumerable()diese Option , um die Auswertung dieses Teils mit Linq to Objects zu erzwingen.

Basierend auf der vorherigen Antwort, die ich Ihnen gegeben habe, würde ich Ihre Anfrage wie folgt umstrukturieren:

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

Ich sehe auch, dass Sie verwandte Entitäten in der Abfrage ( Organisation.Name) verwenden. Stellen Sie sicher, dass Sie IncludeIhrer Abfrage die richtigen hinzufügen , oder materialisieren Sie diese Eigenschaften speziell für die spätere Verwendung, dh:

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
                });
Glassplitter
quelle
Zusätzlich zu der Tatsache, dass der Teil "Neue Aufgabe auswählen" aufgrund der Übersetzung des Ausdrucksbaums nicht serverseitig erfolgen kann, sollte auch beachtet werden, dass dies unerwünscht ist. Vermutlich möchten Sie, dass die Aufgaben clientseitig erstellt werden. Daher könnte die Trennung von Abfrage und Erstellung von Aufgaben noch expliziter sein.
Tormod
3
Ich würde auch empfehlen, einen anonymen Typ auszuwählen, der nur die erforderliche Rechnungsnummer und den Organisationsnamen enthält. Wenn die Rechnungsentität groß ist, zieht die Auswahl i mit der nachfolgenden AsEnumerable jede Spalte zurück, obwohl Sie nur zwei verwenden.
Devin
1
@ Devin: Ja, ich stimme zu - genau das macht das zweite Abfragebeispiel.
BrokenGlass
15

IQueryableergibt sich aus IEnumerableder Haupt Ähnlichkeit ist , dass , wenn Sie Ihre Abfrage machen es in seiner Sprache in die Datenbank - Engine geschrieben wird, ist die dünne Moment , in dem Sie C # sagen , die Daten auf dem Server (nicht Client - Seite) oder zu sagen , SQL Griff zu handhaben Daten.

Wenn Sie also sagen IEnumerable.ToString(), erhält C # die Datenerfassung und ruft ToString()das Objekt auf. Wenn Sie jedoch sagen, dass IQueryable.ToString()C # SQL anweist, ToString()das Objekt aufzurufen , gibt es in SQL keine solche Methode.

Der Nachteil ist, dass beim Verarbeiten von Daten in C # die gesamte Sammlung, die Sie durchsuchen, im Speicher erstellt werden muss, bevor C # die Filter anwendet.

Am effizientesten ist es, die Abfrage wie IQueryablebei allen Filtern durchzuführen, die Sie anwenden können.

Bauen Sie es dann im Speicher auf und formatieren Sie die Daten 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")
                                                   });
Nikolay
quelle
3

SQL weiß zwar nicht, was mit einem zu tun ist string.Format, kann jedoch eine Zeichenfolgenverkettung durchführen.

Wenn Sie den folgenden Code ausführen, sollten Sie die Daten erhalten, nach denen Sie suchen.

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

Sobald Sie die Abfrage tatsächlich ausgeführt haben, sollte dies geringfügig schneller sein als die Verwendung AsEnumerable(zumindest habe ich dies in meinem eigenen Code gefunden, nachdem ich denselben ursprünglichen Fehler wie Sie hatte). Wenn Sie mit C # etwas Komplexeres machen, müssen Sie es trotzdem verwenden AsEnumerable.

d219
quelle
2
Ich bin mir nicht sicher, warum Linq nicht für die Verwendung der FORMATMESSAGE-Funktion docs.microsoft.com/en-us/sql/t-sql/functions/ angepasst werden konnte. Bis dahin ist Ihre Lösung (ohne Materialisierung zu erzwingen)
MemeDeveloper
2
Abhängig von der Datenbankstruktur und der Anzahl der zugehörigen Spalten kann die Verwendung dieser Methode anstelle von AsEnumerable()wesentlich effizienter sein. Vermeiden Sie AsEnumerable()und ToList()bis Sie wirklich alle Ergebnisse in Erinnerung behalten möchten.
Chris Schaller