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
źródło
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.

Ron K.
źródło
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.

CptRobby
źródło
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();
Cheng Chen
źródło
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.

abkonsta
źródło
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.

Slauma
źródło
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;
}
Johan Tidén
źródło
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.

Andrey St
źródło
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.

Jecoms
źródło
0

Możesz spróbować tego:

int maxShoeSize = Workers.Where(x=>x.CompanyId == 8).Max(x => x.ShoeSize) ?? 0;
Carlos Toledo
źródło
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);
}
Wielebny Sfinks
źródło