Die Umwandlung in den Werttyp 'Int32' ist fehlgeschlagen, da der materialisierte Wert null ist

192

Ich habe den folgenden Code. Ich erhalte eine Fehlermeldung:

"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."

wenn die CreditHistory-Tabelle keine Datensätze enthält.

var creditsSum = (from u in context.User
                  join ch in context.CreditHistory on u.ID equals ch.UserID                                        
                  where u.ID == userID
                  select ch.Amount).Sum();

Wie kann ich die Abfrage so ändern, dass sie Nullwerte akzeptiert?

Zosim
quelle

Antworten:

328

Eine Linq-to-SQL-Abfrage wird nicht als Code ausgeführt, sondern in SQL übersetzt. Manchmal ist dies eine "undichte Abstraktion", die zu unerwartetem Verhalten führt.

Ein solcher Fall ist die Nullbehandlung, bei der an verschiedenen Stellen unerwartete Nullen auftreten können. ...DefaultIfEmpty(0).Sum(0)kann in diesem (recht einfachen) Fall helfen, in dem möglicherweise keine Elemente und SQL- SUMRückgaben vorhanden sind, nullwährend c # 0 erwartet.

Ein allgemeinerer Ansatz ist die Verwendung, ??der übersetzt wird, COALESCEwenn das Risiko besteht, dass die generierte SQL eine unerwartete Null zurückgibt:

var creditsSum = (from u in context.User
              join ch in context.CreditHistory on u.ID equals ch.UserID                                        
              where u.ID == userID
              select (int?)ch.Amount).Sum() ?? 0;

Dies wird zuerst ausgeführt int?, um dem C # -Compiler mitzuteilen, dass dieser Ausdruck tatsächlich zurückgegeben werden kann null, obwohl er einen Sum()zurückgibt int. Dann verwenden wir den normalen ??Operator, um den nullFall zu behandeln .

Basierend auf dieser Antwort habe ich einen Blog-Beitrag mit Details zu LINQ to SQL und LINQ to Entities geschrieben.

Anders Abel
quelle
3
danke Anders, die Lösung mit DefaultIfEmpty (0) .Sum () funktioniert gut für mich. Ich habe auch die zweite Lösung mit (int?) Versucht ... ?? 0 ..., aber es wirft die gleiche Ausnahme wie zuvor ..
Zosim
Endlich habe ich es getestet und angepasst, sodass jetzt auch die zweite Version funktioniert.
Anders Abel
1
Sum () und andere Aggregatfunktionen geben null zurück, wenn sie auf ein leeres Dataset angewendet werden. Entgegen ihrer Definition geben sie in Wirklichkeit eine nullfähige Version des zugrunde liegenden Typs zurück.
Suncat2000
2
@recursive: Ihr Beispiel ist LINQ-to-Objects, nicht LINQ-to-SQL (oder LINQ-to-Entities). Durch ihre zugrunde liegenden Datenanbieter verhalten sie sich anders.
Suncat2000
Das war eine gute Idee. Ich habe mein Rückgabeobjekt so aktualisiert, dass es nullfähige Eigenschaften hat, und das hat als Zauber gewirkt.
Kremena Lalova
8

Um ein nullbares AmountFeld zuzulassen , verwenden Sie einfach den Null-Koaleszenzoperator, um Nullen in 0 zu konvertieren.

var creditsSum = (from u in context.User
              join ch in context.CreditHistory on u.ID equals ch.UserID                                        
              where u.ID == userID
              select ch.Amount ?? 0).Sum();
rekursiv
quelle
1
Wenn ich Ihren Tipp verwende, sagt der Compiler: Operator '??' kann nicht auf Operanden vom Typ 'int' und 'int' angewendet werden. habe ich etwas vergessen
Zosim
@zosim: Das ist der Grund, die Besetzung int?zuerst hinzuzufügen .
Anders Abel
Ich habe int hinzugefügt?, aber die gleiche Ausnahme. Ich werde dir dankbar sein, wenn du dev env hast. um zu überprüfen, was in dieser Syntax falsch ist.
Zosim
1
@ Zosim: Ich verstehe das Problem nicht. Wenn dies der Fall Amountist int, sind wir uns bereits sicher, dass es nicht null sein kann, und das Zusammenführen ist nicht erforderlich. Wenn Sie den Fehler erhalten, den Sie gesagt haben, Amountist er nicht nullbar, sondern nur ein Fehler. intIn diesem Fall müssen Sie möglicherweise Ihre linq2sql-DBML-Spalte im Designer ändern, um Nullen zuzulassen.
rekursiv
1
@recursive: Betrag ist int, es ist OK. Betrag hat bereits Wert. Ich denke, dass der obige Fehler aufgetreten ist, weil die CreditHistory-Tabelle leer ist. Ich habe einen Datensatz in der Benutzertabelle und 0 Datensätze in der CreditHistory-Tabelle, und es ist ein Fehler aufgetreten. Wenn ich DefaultIfEmpty (0) .Sum () verwende, funktioniert es gut, aber mit ?? 0 es wirft Fehler. Meine andere Frage ist, was in diesem Fall die beste Vorgehensweise ist. DefaultIfEmpty (0)? danke
zosim
4

aggregateWenn Sie eine Funktion verwenden, bei der die Elemente nicht zum Ausführen einer Aktion aufgefordert werden, müssen Sie sicherstellen, dass die Linq-Abfrage ein Ergebnis wie folgt liefert:

var maxOrderLevel =sdv.Any()? sdv.Max(s => s.nOrderLevel):0
Ashwini
quelle
11
Dies würde dazu führen, dass sdv zweimal ausgeführt wird. Welches ist nicht, was Sie für IQueryables wollen
Ody
4

Hatte diese Fehlermeldung, als ich versuchte, aus einer Ansicht auszuwählen.

Das Problem war, dass die Ansicht kürzlich einige neue Nullzeilen (in der Spalte SubscriberId) erhalten hatte und in EDMX (EF-Datenbank zuerst) nicht aktualisiert wurde.

Die Spalte musste vom Typ Nullable sein, damit sie funktioniert.

var Dealer = Context.Dealers.Where (x => x.dealerCode == DealerCode) .FirstOrDefault ();

Vor dem Aktualisieren der Ansicht:

public int SubscriberId { get; set; }

Nach Ansichtsaktualisierung:

public Nullable<int> SubscriberId { get; set; }

Das Löschen und Hinzufügen der Ansicht in EDMX hat funktioniert.

Hoffe es hilft jemandem.

Live-Liebe
quelle
Dies war auch mein Problem und meine Antwort
Simon Nicholls
4

Ich habe diesen Code verwendet und er reagiert korrekt, nur der Ausgabewert ist nullwertfähig.

var packesCount = await botContext.Sales.Where(s => s.CustomerId == cust.CustomerId && s.Validated)
                                .SumAsync(s => (int?)s.PackesCount);
                            if(packesCount != null)
                            {
                                // your code
                            }
                            else
                            {
                                // your code
                            }
MohammadSoori
quelle
1

Ich sehe, dass diese Frage bereits beantwortet ist. Wenn Sie jedoch möchten, dass es in zwei Anweisungen aufgeteilt wird, kann Folgendes in Betracht gezogen werden.

var credits = from u in context.User
              join ch in context.CreditHistory 
                  on u.ID equals ch.UserID                                        
              where u.ID == userID
              select ch;

var creditSum= credits.Sum(x => (int?)x.Amount) ?? 0;
LCJ
quelle
0

Ich habe diesen Fehler in Entity Framework 6 mit diesem Code zur Laufzeit erhalten:

var fileEventsSum = db.ImportInformations.Sum(x => x.FileEvents)

Update von LeandroSoares:

Verwenden Sie dies für eine einzelne Ausführung:

var fileEventsSum = db.ImportInformations.Sum(x => (int?)x.FileEvents) ?? 0

Original:

Geändert und dann hat es funktioniert:

var fileEventsSum = db.ImportInformations.Any() ? db.ImportInformations.Sum(x => x.FileEvents) : 0;
Ogglas
quelle
1
Würde das nicht zweimal ausgeführt werden?
Nawfal
Dies ist keine gute Antwort. Es wird zweimal aus der Datenbank abgerufen.
Leandro Soares
@nawfal Das stimmt, aber es ist viel besser als ein Laufzeitfehler. Sie können absolut Linq-to-SQL verwenden, aber mit Lambda ist es schwieriger. Sie können natürlich die Ausnahme fangen, aber ich denke, dass die Lösung schlechter ist als zwei Ausführungen.
Ogglas
@ LeandroSoares siehe obigen Kommentar
Ogglas
1
@LeandroSoares Schön! Ich habe meine Antwort aktualisiert und den von Ihnen angegebenen Code und die Beschreibung verwendet, warum ich ihn stattdessen verwenden soll.
Ogglas
0

Ich hatte auch das gleiche Problem und wurde gelöst, indem die Spalte mit "?" Operator.

Sequnce = db.mstquestionbanks.Where(x => x.IsDeleted == false && x.OrignalFormID == OriginalFormIDint).Select(x=><b>(int?)x.Sequence</b>).Max().ToString();

Manchmal wird null zurückgegeben.

user3820036
quelle