Maksymalna wartość zwracana w przypadku pustego zapytania


175

Mam to zapytanie:

int maxShoeSize = Workers
    .Where(x => x.CompanyId == 8)
    .Max(x => x.ShoeSize);

Co się stanie, maxShoeSizejeśli firma 8 w ogóle nie będzie miała pracowników?

AKTUALIZACJA:
Jak mogę zmienić zapytanie, aby uzyskać 0, a nie wyjątek?


Naor: czy słyszałeś o LINQPad?
Mitch Wheat

3
Nie rozumiem, dlaczego pytasz „Co będzie w maxShoeSiześrodku?” jeśli już go wypróbowałeś.
jwg

@jwg: Chyba chciałem sprawdzić, czy znasz odpowiedź :) W końcu znalazłem lepszy sposób na zrobienie tego, o co prosiłem i o to mi chodziło.
Naor

@Naor, to nie jest zgadywanie. Odrzuciłbym również pierwotne pytanie. Jeśli znasz odpowiedź, daj nam ją, w przeciwnym razie wyglądasz leniwie. Właśnie miałem zrobić to samo pytanie i przygotować wszystkie informacje, w tym komunikat o wyjątku.
Juan Carlos Oropeza

Odpowiedzi:


298
int maxShoeSize = Workers.Where(x => x.CompanyId == 8)
                         .Select(x => x.ShoeSize)
                         .DefaultIfEmpty(0)
                         .Max();

Zerowanie DefaultIfEmptynie jest konieczne.


Działa :) Ale w moim kodzie zero w DefaultIfEmpty było konieczne.
Carlos Tenorio Pérez,

1
Odnośnie pierwszej części pytania, na które nie ma tu pełnej odpowiedzi: Zgodnie z oficjalną dokumentacją , jeśli typ ogólny pustej sekwencji jest typem referencyjnym, otrzymujesz wartość null. W przeciwnym razie, zgodnie z tym pytaniem , wywołanie Max()pustej sekwencji spowoduje błąd.
Raimund Krämer

59

Wiem, że to stare pytanie i przyjęta odpowiedź działa, ale to pytanie odpowiadało na moje pytanie, czy taki pusty zestaw skutkowałby wyjątkiem, czy default(int)wynikiem.

Przyjęta odpowiedź jednak, choć działa, nie jest idealnym rozwiązaniem IMHO, którego nie podano tutaj. W ten sposób udzielam go w mojej własnej odpowiedzi dla dobra każdego, kto tego szuka.

Oryginalny kod OP był następujący:

int maxShoeSize = Workers.Where(x => x.CompanyId == 8).Max(x => x.ShoeSize);

Oto jak napisałbym to, aby zapobiec wyjątkom i podać domyślny wynik:

int maxShoeSize = Workers.Where(x => x.CompanyId == 8).Max(x => x.ShoeSize as int?) ?? 0;

Powoduje to, że zwracany typ Maxfunkcji jest int?, który pozwala na nullwynik, a następnie ??zastępuje nullwynik przez 0.


EDYTUJ
Aby wyjaśnić coś z komentarzy, Entity Framework obecnie nie obsługuje assłowa kluczowego, więc sposób zapisania go podczas pracy z EF byłby następujący:

int maxShoeSize = Workers.Where(x => x.CompanyId == 8).Max<[TypeOfWorkers], int?>(x => x.ShoeSize) ?? 0;

Ponieważ [TypeOfWorkers]może to być długa nazwa klasy i jest żmudna w pisaniu, dodałem metodę rozszerzenia, aby pomóc.

public static int MaxOrDefault<T>(this IQueryable<T> source, Expression<Func<T, int?>> selector, int nullValue = 0)
{
    return source.Max(selector) ?? nullValue;
}

To tylko uchwyty int, ale to samo można zrobić dla long, doublelub innego rodzaju wartości, czego potrzebujesz. Korzystanie z tej metody rozszerzenia jest bardzo proste, po prostu przekazujesz swoją funkcję selektora i opcjonalnie dołączasz wartość, która ma być użyta dla null, która domyślnie wynosi 0. Więc powyższe można przepisać w następujący sposób:

int maxShoeSize = Workers.Where(x => x.CompanyId == 8).MaxOrDefault(x => x.ShoeSize);

Miejmy nadzieję, że pomoże to ludziom jeszcze bardziej.


3
Świetne rozwiązanie! Bardziej popularna DefaultIfEmptyodpowiedź działa dobrze tylko wtedy, gdy Maxnie przeprowadza oceny.
McGuireV10

@ McGuireV10 Tak, zazwyczaj nie lubię używać Selectjako pośrednika, kiedy mam zamiar używać funkcji agregującej, takiej jak Maxna wyniku. Ja również myślę (nie testowałem tego jeszcze), że wygenerowany SQL użyłby dodatkowy zapytanie podselekcji wykonując że chociaż będzie kopalnia właśnie do czynienia z pustym zestawem wracając null. Dzięki za uznanie i opinie! ;)
CptRobby

@ McGuireV10 Podobnie, jeśli ShoeSizefaktycznie w powiązanego Uniformpodmiotu, nie będę używać Workers.Where(x => x.CompanyId == 8).Select(x => x.Uniform).Max(x => x.ShoeSize), zamiast tego po prostu zachować całą ocenę w Maxfunkcji: Workers.Where(x => x.CompanyId == 8).Max(x => x.Uniform.ShoeSize). Wolę używać jak najmniejszej liczby metod w moich zapytaniach, aby umożliwić EF mieć największą swobodę w decydowaniu, jak efektywnie konstruować zapytania. ;-)
CptRobby

1
Nie mogłem zmusić tego do pracy w EntityFramework. Czy ktoś może rzucić jakieś światło? (Zadziałało użycie techniki DefaultIfEmpty).
Moe Sisko

1
Ogólna wersja metody rozszerzenia:public static TResult MaxOrDefault<TElement, TResult>(this IQueryable<TElement> items, Expression<Func<TElement, TResult>> selector, TResult defaultValue = default) where TResult : struct => items.Select(selector).Max(item => (TResult?)item) ?? defaultValue;
stosunkowo_random


17
int maxShoeSize = Workers.Where(x => x.CompanyId == 8)
                     .Select(x => x.ShoeSize)
                     .DefaultIfEmpty()
                     .Max();

10

Jeśli jest to Linq to SQL, nie lubię go używać, Any()ponieważ powoduje to wiele zapytań do serwera SQL.

Jeśli ShoeSizenie jest polem dopuszczającym wartość null, wówczas użycie samego polecenia .Max(..) ?? 0nie zadziała, ale następujące działania:

int maxShoeSize = Workers.Where(x = >x.CompanyId == 8).Max(x => (int?)x.ShoeSize) ?? 0;

Absolutnie nie zmienia wyemitowanego kodu SQL, ale zwraca 0, jeśli sekwencja jest pusta, ponieważ zmienia znak na Max()zwracający int?zamiast int.


4
int maxShoeSize=Workers.Where(x=>x.CompanyId==8)
    .Max(x=>(int?)x.ShoeSize).GetValueOrDefault();

(zakładając, że ShoeSizejest to typoweint )

Jeśli Workersjest DbSetlub ObjectSetz Entity Framework, Twoje początkowe zapytanie wyrzuciłoby InvalidOperationException, ale nie narzekało na pustą sekwencję, ale narzekało, że zmaterializowanej wartości NULL nie można przekonwertować na plik int.


2

Max wyrzuci System.InvalidOperationException "Sekwencja nie zawiera żadnych elementów"

class Program
{
    static void Main(string[] args)
    {
        List<MyClass> list = new List<MyClass>();

        list.Add(new MyClass() { Value = 2 });

        IEnumerable<MyClass> iterator = list.Where(x => x.Value == 3); // empty iterator.

        int max = iterator.Max(x => x.Value); // throws System.InvalidOperationException
    }
}

class MyClass
{
    public int Value;
}

2

Uwaga: zapytanie z DefaultIfEmpty()może być znacznie wolniejsze . W moim przypadku było to proste zapytanie z .DefaultIfEmpty(DateTime.Now.Date).

Byłem zbyt leniwy, aby go profilować, ale oczywiście EF próbował uzyskać wszystkie wiersze, a następnie przyjąć Max()wartość.

Wniosek: czasami obsługa InvalidOperationExceptionmoże być lepszym wyborem.


0

Możesz użyć trójargumentu wewnątrz .Max()do obsługi predykatu i ustawiania jego wartości;

// assumes Workers != null && Workers.Count() > 0
int maxShoeSize = Workers.Max(x => (x.CompanyId == 8) ? x.ShoeSize : 0);

Jeśli jest taka możliwość, musisz obsłużyć Workerszbiór jako pusty / pusty, ale będzie to zależeć od Twojej implementacji.


0

Możesz spróbować tego:

int maxShoeSize = Workers.Where(x=>x.CompanyId == 8).Max(x => x.ShoeSize) ?? 0;

Myślę, że to się nie powiedzie, ponieważ Max spodziewa się int i otrzymuje wartość null; więc błąd wystąpił już przed wejściem w życie operatora łączenia zerowego.
d219

0

Możesz sprawdzić, czy są jacyś pracownicy, zanim wykonasz Max ().

private int FindMaxShoeSize(IList<MyClass> workers) {
   var workersInCompany = workers.Where(x => x.CompanyId == 8);
   if(!workersInCompany.Any()) { return 0; }
   return workersInCompany.Max(x => x.ShoeSize);
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.