LINQ to Entities nie rozpoznaje metody „System.String Format (System.String, System.Object, System.Object)”

88

Mam takie zapytanie 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
                   };
}

Ma jednak problemy. Próbuję tworzyć zadania. Dla każdego nowego zadania, kiedy ustawię tekst łącza na stały ciąg, taki jak „Hello”, wszystko jest w porządku. Jednak powyżej próbuję zbudować tekst linku właściwości przy użyciu właściwości faktury.

Otrzymuję ten błąd:

base {System.SystemException} = {"LINQ to Entities nie rozpoznaje metody 'System.String Format (System.String, System.Object, System.Object)' i tej metody nie można przetłumaczyć na wyrażenie magazynu." }

Czy ktoś wie, dlaczego? Czy ktoś zna alternatywny sposób, aby to zadziałało?

AnonyMouse
źródło
Tak, pierwotnie to przegapiłem
AnonyMouse

Odpowiedzi:

148

Entity Framework próbuje wykonać projekcję po stronie SQL, gdzie nie ma odpowiednika string.Format. Służy AsEnumerable()do wymuszania oceny tej części za pomocą Linq to Objects.

Na podstawie poprzedniej odpowiedzi, której ci udzieliłem, przeformułowałbym twoje zapytanie w następujący sposób:

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

Widzę również, że używasz powiązanych encji w zapytaniu ( Organisation.Name), upewnij się, że dodajesz właściwe Includedo zapytania lub konkretnie zmaterializujesz te właściwości do późniejszego wykorzystania, tj .:

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
                });
Rozbite szkło
źródło
Oprócz faktu, że część wybierz nowe zadanie nie może wystąpić po stronie serwera z powodu translacji drzewa wyrażeń, należy również zauważyć, że jest to niepożądane. Przypuszczalnie chcesz, aby zadania były tworzone po stronie klienta. Dlatego oddzielenie zapytania od tworzenia zadań mogłoby być jeszcze wyraźniejsze.
Tormod
3
Zalecałbym również wybranie anonimowego typu, który ma tylko potrzebny numer faktury i nazwę organizacji. Jeśli encja faktur jest duża, wówczas opcja select i z kolejnym AsEnumerable wycofa każdą kolumnę, nawet jeśli używasz tylko dwóch.
Devin,
1
@Devin: Tak, zgadzam się - w rzeczywistości to jest dokładnie to, co robi drugi przykład zapytania.
BrokenGlass
15

IQueryablewywodzi się z IEnumerable, główne podobieństwo polega na tym, że kiedy tworzysz zapytanie, jest ono wysyłane do silnika bazy danych w swoim języku, w cienkim momencie mówisz C #, aby obsłużył dane na serwerze (nie po stronie klienta) lub aby SQL obsługiwał dane.

Zasadniczo, gdy mówisz IEnumerable.ToString(), C # pobiera kolekcję danych i wywołuje ToString()obiekt. Ale kiedy mówisz, że IQueryable.ToString()C # mówi SQLowi, aby wywołał ToString()obiekt, ale nie ma takiej metody w SQL.

Wadą jest to, że podczas obsługi danych w języku C # cała przeglądana kolekcja musi zostać zgromadzona w pamięci, zanim C # zastosuje filtry.

Najskuteczniejszym sposobem jest ustawienie zapytania jako IQueryable przypadku wszystkich filtrów, które można zastosować.

Następnie zapisz go w pamięci i sformatuj dane w 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
źródło
3

Chociaż SQL nie wie, co zrobić z a string.Format, może wykonać konkatenację ciągów.

Jeśli uruchomisz poniższy kod, powinieneś otrzymać dane, których szukasz.

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

Gdy faktycznie wykonasz zapytanie, powinno to być nieznacznie szybsze niż użycie AsEnumerable(przynajmniej to znalazłem w moim własnym kodzie po tym samym pierwotnym błędzie co ty). Jeśli robisz coś bardziej złożonego w C #, nadal będziesz musiał używać AsEnumerable.

d219
źródło
2
Nie wiem, dlaczego LINQ nie mógł być przystosowany do korzystania FormatMessage funkcji docs.microsoft.com/en-us/sql/t-sql/functions/... Do tej pory twoje jest rozwiązanie (bez wymuszania materializacji)
MemeDeveloper
2
W zależności od struktury bazy danych i liczby powiązanych kolumn, użycie tej metody zamiast AsEnumerable()może być znacznie bardziej wydajne. Unikaj AsEnumerable()i ToList()dopóki naprawdę nie zechcesz zapamiętać wszystkich wyników.
Chris Schaller