Jak zmusić LINQ Sum () do zwrócenia 0, gdy kolekcja źródłowa jest pusta

183

Zasadniczo, gdy wykonuję następujące zapytanie, jeśli nie zostaną dopasowane żadne potencjalne szanse, poniższe zapytanie zgłasza wyjątek. W takim przypadku wolałbym, aby suma wyrównała 0, zamiast zgłaszania wyjątku. Czy byłoby to możliwe w samym zapytaniu - mam na myśli raczej niż zapisywanie zapytania i sprawdzanie query.Any()?

double earnings = db.Leads.Where(l => l.Date.Day == date.Day
                && l.Date.Month == date.Month
                && l.Date.Year == date.Year
                && l.Property.Type == ProtectedPropertyType.Password
                && l.Property.PropertyId == PropertyId).Sum(l => l.Amount);
John Mayer
źródło
2
WhereNie wróci null, jeśli nie znaleźliśmy żadnych rekordów, to zwróci listę elementów zerowych. Jaki jest wyjątek?
Mike Perrenoud,
3
Jaki jest wyjątek?
Toto
3
Pojawia się wyjątek: rzutowanie na typ wartości „Int32” nie powiodło się, ponieważ zmaterializowana wartość ma wartość NULL. Albo parametr ogólny typu wyniku, albo zapytanie musi używać typu zerowalnego.
John Mayer,
1
@Stijn, nie to, co zrobiłeś, nadal by nie zadziałało. Problemem jest sposób SQLgenerowania. Amounttak naprawdę nie jest null, to naprawdę problem z tym, jak radzi sobie z zerowymi wynikami. Spójrz na udzieloną odpowiedź.
Mike Perrenoud,
39
Nie powinieneś używać podwójnych kwot w dolarach! Nawet ułamkowe kwoty w dolarach. Nigdy, nigdy, nigdy nie używaj podwójnie, gdy dokładna kwota jest zamierzona. Kolumny bazy danych powinny być decimal, twój kod powinien użyć decimal. Zapomnij, że kiedykolwiek znał floati doublew swojej karierze programowania aż do dnia, ktoś powie Ci, aby ich użyć do celów statystycznych lub gwiazdy luminancji lub wynikach stochastycznego procesu lub ładunku elektronu! Do tego czasu robisz to źle .
ErikE

Odpowiedzi:

390

Spróbuj zmienić zapytanie na to:

db.Leads.Where(l => l.Date.Day == date.Day
            && l.Date.Month == date.Month
            && l.Date.Year == date.Year
            && l.Property.Type == ProtectedPropertyType.Password
            && l.Property.PropertyId == PropertyId)
         .Select(l => l.Amount)
         .DefaultIfEmpty(0)
         .Sum();

W ten sposób zapytanie wybierze tylko Amountpole. Jeśli kolekcja jest pusta, zwróci jeden element o wartości, 0a następnie zostanie zastosowana suma.

Simon Belanger
źródło
To z pewnością załatwia sprawę, ale czy nie wybrałby najpierw listy wartości Kwoty i Sumich po stronie serwera, a nie po stronie bazy danych? Rozwiązanie imo 2kay jest bardziej optymalne, przynajmniej poprawne semantycznie.
Maksim Vi.
3
@MaksimVI EF wygeneruje zapytanie po pierwszym materializacji, gdy IQueryable<T>łańcuch zatrzymuje się (zazwyczaj podczas rozmowy ToList, AsEnumerableitd .. i w tym przypadku Sum). Sumjest znaną i obsługiwaną metodą przez dostawcę zapytań EF i wygeneruje powiązaną instrukcję SQL.
Simon Belanger,
@ SimonBelanger Stoję poprawiony, suma jest dokonywana po stronie DB, ale jest dokonywana na podzapytaniu, które najpierw wybiera Kwoty. Zasadniczo zapytanie jest SELECT SUM(a.Amount) FROM (SELECT Amount FROM Leads WHERE ...) AS azamiast po prostu SELECT SUM(Amount) FROM Leads. Również podzapytanie ma dodatkową kontrolę zerową i dziwne połączenie zewnętrzne z tabelą z jednym wierszem.
Maksim Vi.
Nie jest to znacząca różnica w wydajności i jest prawdopodobnie zoptymalizowana, ale nadal uważam, że inne rozwiązanie wygląda na czystsze.
Maksim Vi.
5
Należy pamiętać, że DefaultIfEmptynie jest obsługiwany przez wielu dostawców LINQ, dlatego ToList()przed użyciem w takich przypadkach należałoby wrzucić coś podobnego, aby zastosować go w scenariuszu Obiekty LINQ .
Christopher King,
188

Wolę użyć innego hacka:

double earnings = db.Leads.Where(l => l.Date.Day == date.Day
                                      && l.Date.Month == date.Month
                                      && l.Date.Year == date.Year
                                      && l.Property.Type == ProtectedPropertyType.Password
                                      && l.Property.PropertyId == PropertyId)
                          .Sum(l => (double?) l.Amount) ?? 0;
tukaef
źródło
18
Podczas korzystania z Linq do SQL generuje to znacznie krótszy kod SQL niż zaakceptowana odpowiedź
wertzui
3
To jest poprawna odpowiedź. Wszystkie inne zawodzą. Najpierw rzut na null, a następnie porównanie wyniku końcowego z wartością null.
Mohsen Afshin
3
Jest to znacznie lepsze niż zaakceptowana odpowiedź dla Linq To EF. Dla mnie wygenerowany SQL działa około 3,8 razy lepiej niż DefaultIfEmpty.
Florian
2
To DUŻO SZYBCIEJ.
frakon
1
nie nazwałbym tego hackem, ponieważ jest to dokładnie to, do czego przeznaczone są
nullaby
7

Spróbuj tego zamiast tego, jest krótszy:

db.Leads.Where(..).Aggregate(0, (i, lead) => i + lead.Amount);
Kovács Róbert
źródło
2
Czy to pozwala uniknąć wyjątku?
Adrian Wragg,
czy mógłbyś opracować?
DanielV
4

To dla mnie wygrana:

int Total = 0;
Total = (int)Db.Logins.Where(L => L.id == item.MyId).Sum(L => (int?)L.NumberOfLogins ?? 0);

W mojej tabeli LOGIN w polu NUMBEROFLOGINS niektóre wartości mają wartość NULL, a inne mają liczbę INT. Łączę tutaj całkowitą liczbę NUMBEROFLOGINS wszystkich użytkowników jednej korporacji (każdy identyfikator).

Pedro Ramos
źródło
1

Próbować:

podwójne zarobki = db.Leads.Where (l => l.ShouldBeIncluded) .Sum (l => (double?) l.Amount) ?? 0 ;

Zapytanie „ SELECT SUM ([Kwota]) ” zwróci NULL dla pustej listy. Ale jeśli użyjesz LINQ, spodziewa się, że „ Sum (l => l.Amount) ” zwraca wartość podwójną i nie pozwala na użycie operatora „ ?? ” do ustawienia 0 dla pustej kolekcji.

Aby uniknąć tej sytuacji, musisz sprawić, by LINQ oczekiwał „ podwójnej? ”. Możesz to zrobić, przesyłając „ (double?) L.Amount ”.

Nie wpływa na zapytanie SQL, ale sprawia, że ​​LINQ działa dla pustych kolekcji.

Maxim Lukoshko
źródło
0
db.Leads.Where(l => l.Date.Day == date.Day
        && l.Date.Month == date.Month
        && l.Date.Year == date.Year
        && l.Property.Type == ProtectedPropertyType.Password
        && l.Property.PropertyId == PropertyId)
     .Select(l => l.Amount)
     .ToList()
     .Sum();
Mona
źródło
1
Dodaj informacje do odpowiedzi na temat kodu
Jaqen H'ghar
1
Wystąpił błąd podczas próby bez ToList (), ponieważ nic nie zwraca. Ale ToList () utworzy pustą listę i nie spowoduje błędu, gdy wykonam ToList (). Sum ().
Mona
2
Prawdopodobnie nie chciałbyś użyć ToListtutaj, jeśli wszystko, czego chcesz, to suma. Spowoduje to powrót całego zestawu wyników (tylko Amountdla każdego rekordu w tym przypadku) do pamięci, a następnie do Sum()tego zestawu. Znacznie lepiej użyć innego rozwiązania, które wykonuje obliczenia za pomocą SQL Server.
Josh M.,