So erzwingen Sie, dass LINQ Sum () 0 zurückgibt, während die Quellensammlung leer ist

183

Wenn bei der folgenden Abfrage keine Leads gefunden werden, löst die folgende Abfrage eine Ausnahme aus. In diesem Fall würde ich es vorziehen, wenn die Summe 0 ist, anstatt dass eine Ausnahme ausgelöst wird. Wäre dies in der Abfrage selbst möglich - ich meine, anstatt die Abfrage zu speichern und zu überprüfen 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
quelle
2
Das Wherewürde nicht zurückkehren, nullwenn es keine Datensätze finden würde, es würde eine Liste von null Elementen zurückgeben. Was ist die Ausnahme?
Mike Perrenoud
3
Was ist die Ausnahme?
Toto
3
Ich erhalte die Ausnahme: Die Umwandlung in den Werttyp 'Int32' ist fehlgeschlagen, da der materialisierte Wert null ist. Entweder der generische Parameter des Ergebnistyps oder die Abfrage müssen einen nullbaren Typ verwenden.
John Mayer
1
@Stijn, nein was du noch getan hast hätte nicht funktioniert. Das Problem ist die Art und Weise, wie das SQLgeneriert wird. Amountist nicht wirklich null, es ist wirklich ein Problem, wie es mit Null-Ergebnissen umgeht. Schauen Sie sich die Antwort an, die gegeben wurde.
Mike Perrenoud
39
Sie sollten nicht doppelt für Dollarbeträge verwenden! Sogar gebrochene Dollarbeträge. Verwenden Sie niemals das Doppelte, wenn eine genaue Menge vorgesehen ist. Ihre Datenbankspalten sollten sein decimal, Ihr Code sollte verwenden decimal. Vergessen Sie, dass Sie jemals von floatund doublein Ihrer Programmierkarriere gewusst haben, bis zu dem Tag, an dem Ihnen jemand sagt, dass Sie sie verwenden sollen, für Statistiken oder Sternleuchtdichte oder die Ergebnisse eines stochastischen Prozesses oder der Ladung eines Elektrons! Bis dahin machst du es falsch .
ErikE

Antworten:

389

Versuchen Sie, Ihre Abfrage folgendermaßen zu ändern:

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

Auf diese Weise wählt Ihre Abfrage nur das AmountFeld aus. Wenn die Sammlung leer ist, wird ein Element mit dem Wert von zurückgegeben, 0und dann wird die Summe angewendet.

Simon Belanger
quelle
Es macht sicherlich den Trick, aber würde es nicht zuerst eine Liste der Betragswerte auswählen und Sumdiese auf der Serverseite anstatt auf der Datenbankseite? Die Lösung von imo 2kay ist optimaler, zumindest semantisch korrekter.
Maksim Vi.
3
@MaksimVI EF wird die Abfrage auf der ersten Materialisierung erzeugen, wenn die IQueryable<T>Kette stoppt ( in der Regel , wenn Sie anrufen ToList, AsEnumerableetc .. und in diesem Fall Sum). Sumist eine bekannte und vom EF Queryable Provider behandelte Methode und generiert die zugehörige SQL-Anweisung.
Simon Belanger
@SimonBelanger Ich stehe korrigiert da, die Summe wird auf der DB-Seite gemacht, aber es wird auf einer Unterabfrage gemacht, die zuerst Beträge auswählt. Grundsätzlich ist die Abfrage SELECT SUM(a.Amount) FROM (SELECT Amount FROM Leads WHERE ...) AS astatt nur SELECT SUM(Amount) FROM Leads. Außerdem hat die Unterabfrage eine zusätzliche Nullprüfung und einen seltsamen äußeren Join mit einer einzelnen Zeilentabelle.
Maksim Vi.
Kein signifikanter Leistungsunterschied und wahrscheinlich optimiert, aber ich denke immer noch, dass die andere Lösung sauberer aussieht.
Maksim Vi.
5
Beachten Sie, dass DefaultIfEmptydies von einer Reihe von LINQ-Anbietern nicht unterstützt wird. ToList()In diesen Fällen müssen Sie daher ein oder ähnliches Element einfügen, bevor Sie es verwenden, damit es im Szenario LINQ to Objects angewendet wird .
Christopher King
188

Ich bevorzuge einen anderen Hack:

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
quelle
18
Bei Verwendung von Linq to SQL wird ein viel kürzerer SQL-Code generiert als die akzeptierte Antwort
wertzui
3
Dies ist die richtige Antwort. Alle anderen scheitern. Zuerst in nullable umwandeln und dann das Endergebnis mit null vergleichen.
Mohsen Afshin
3
Dies ist viel besser als die akzeptierte Antwort für Linq To EF. Für mich ist die generierte SQL etwa 3,8-mal besser als DefaultIfEmpty.
Florian
2
Das ist viel schneller.
Frakon
1
Ich würde dies nicht als Hack bezeichnen, da dies genau die Verwendung ist, für die
Nullables
7

Versuchen Sie dies stattdessen, es ist kürzer:

db.Leads.Where(..).Aggregate(0, (i, lead) => i + lead.Amount);
Kovács Róbert
quelle
2
Vermeidet dies die Ausnahme?
Adrian Wragg
Könnten Sie näher darauf eingehen?
DanielV
4

Das ist ein Gewinn für mich:

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

In meiner LOGIN-Tabelle im Feld NUMBEROFLOGINS sind einige Werte NULL und andere haben eine INT-Nummer. Ich summiere hier die Gesamtzahl der NUMBEROFLOGINE aller Benutzer einer Corporation (jede ID).

Pedro Ramos
quelle
1

Versuchen:

doppeltes Einkommen = db.Leads.Where (l => l.ShouldBeIncluded) .Sum (l => (doppelt?) l.Amount) ?? 0 ;

Die Abfrage " SELECT SUM ([Amount]) " gibt für leere Liste NULL zurück. Wenn Sie jedoch LINQ verwenden, wird erwartet, dass die Summe " l (> l.Amount) " doppelt zurückgegeben wird, und Sie können den Operator " ?? " nicht verwenden, um 0 für die leere Sammlung festzulegen .

Um diese Situation zu vermeiden, muss LINQ " double? " Erwarten . Sie können es tun, indem Sie " (double?) L.Amount " wirken.

Die Abfrage an SQL wird nicht beeinflusst, aber LINQ funktioniert für leere Sammlungen.

Maxim Lukoshko
quelle
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
quelle
1
Bitte fügen Sie einige Informationen zur Antwort über den Code hinzu
Jaqen H'ghar
1
Beim Versuch ohne ToList () ist ein Fehler aufgetreten, da nichts zurückgegeben wird. Aber die ToList () macht die leere Liste und es gibt keinen Fehler, wenn ich ToList () mache. Sum ().
Mona
2
Sie würden ToListhier wahrscheinlich nicht verwenden wollen, wenn Sie nur die Summe wollen. Dies gibt die gesamte Ergebnismenge ( Amountin diesem Fall nur für jeden Datensatz) in den Speicher und dann Sum()diese Menge zurück. Viel besser ist es, eine andere Lösung zu verwenden, die die Berechnung über SQL Server durchführt.
Josh M.