Typ rzutowania na wartość „Int32” nie powiódł się, ponieważ zmaterializowana wartość ma wartość NULL

192

Mam następujący kod. Pojawia się błąd:

„Przekazanie do typu wartości„ Int32 ”nie powiodło się, ponieważ zmaterializowana wartość jest pusta. Albo parametr ogólny typu wyniku, albo zapytanie musi używać typu zerowego.”

gdy tabela CreditHistory nie ma rekordów.

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

Jak mogę zmodyfikować zapytanie, aby akceptowało wartości puste?

zosim
źródło

Odpowiedzi:

328

Kwerenda linq-to-sql nie jest wykonywana jako kod, ale raczej tłumaczona na SQL. Czasami jest to „nieszczelna abstrakcja”, która powoduje nieoczekiwane zachowanie.

Jednym z takich przypadków jest obsługa zerowa, w której mogą wystąpić nieoczekiwane wartości zerowe w różnych miejscach. ...DefaultIfEmpty(0).Sum(0)może pomóc w tym (dość prostym) przypadku, w którym mogą nie być elementów i SUMzwracane przez sql, nullpodczas gdy c # oczekuje 0.

Bardziej ogólnym podejściem jest użycie, ??które zostanie przetłumaczone, COALESCEgdy istnieje ryzyko, że wygenerowany SQL zwróci nieoczekiwany null:

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;

Najpierw rzuca się, int?aby powiedzieć kompilatorowi C #, że to wyrażenie może rzeczywiście zwrócić null, nawet jeśli Sum()zwraca an int. Następnie używamy zwykłego ??operatora do obsługi nullskrzynki.

Na podstawie tej odpowiedzi napisałem post na blogu ze szczegółami zarówno LINQ to SQL, jak i LINQ to Entities.

Anders Abel
źródło
3
dzięki Anders, rozwiązanie z DefaultIfEmpty (0) .Sum () działa dla mnie dobrze. Próbowałem także drugiego rozwiązania z (int?) ... ?? 0 ..., ale generuje ten sam wyjątek jak wcześniej ...
zosim
W końcu udało mi się to przetestować i dostosować, więc teraz działa również druga wersja.
Anders Abel,
1
Sum () i inne funkcje agregujące zwrócą null, gdy zostaną zastosowane do pustego zestawu danych. W przeciwieństwie do ich definicji, w rzeczywistości zwracają zerową wersję typu bazowego.
Suncat2000
2
@recursive: Twój przykład to LINQ-to-Objects, a nie LINQ-to-SQL (lub LINQ-to-Entities). Ich bazowi dostawcy danych sprawiają, że zachowują się inaczej.
Suncat2000
To był dobry pomysł. Zaktualizowałem mój obiekt zwrotny, aby miał właściwości zerowalne i działało to jako urok.
Kremena Lalova,
8

Aby zezwolić na Amountpole zerowalne , wystarczy użyć operatora koalescencji zerowej, aby przekonwertować wartości zerowe na 0.

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();
rekurencyjny
źródło
1
kiedy używam twojej wskazówki, kompilator mówi: Operator „??” nie można zastosować do argumentów typu „int” i „int”. zapomniałem czegoś?
zosim
@zosim: To jest powód, aby dodać obsadę do int?pierwszej.
Anders Abel
dodałem int ?, ale ten sam wyjątek. Będę wam wdzięczny, gdy będziecie mieć dev env. aby sprawdzić, co jest nie tak w tej składni.
zosim
1
@zosim: Nie rozumiem problemu. Jeśli Amountjest int, to jesteśmy już pewni, że nie może być zerowy, a łączenie nie jest konieczne. Jeśli Amountpojawia się błąd, który powiedziałeś, to nie jest on zerowalny, to po prostu int, w takim przypadku być może musisz zmienić kolumnę dbq linq2sql w projektancie, aby zezwolić na wartości null.
rekurencyjny
1
@recursive: Kwota jest liczbą całkowitą, jest OK. Kwota ma już wartość. Myślę, że wystąpił powyższy błąd, ponieważ tabela CreditHistory jest pusta. Mam jeden rekord w tabeli użytkowników i 0 rekordów w tabeli CreditHistory i wystąpił błąd. Kiedy używam DefaultIfEmpty (0) .Sum () działa dobrze, ale z ?? 0 rzuca błąd. Moje kolejne pytanie brzmi: jaka jest najlepsza praktyka w tym przypadku? DefaultIfEmpty (0)? dzięki
zosim
4

Używasz aggregatefunkcji, która nie zmusza elementów do wykonania akcji, musisz sprawdzić, czy zapytanie linq daje jakiś wynik, jak poniżej:

var maxOrderLevel =sdv.Any()? sdv.Max(s => s.nOrderLevel):0
Ashwini
źródło
11
Spowoduje to, że sdv wykona się dwukrotnie. Co nie jest tym, czego chcesz dla IQueryables
Ody
4

Miałem ten komunikat o błędzie, gdy próbowałem wybrać z widoku.

Problem polegał na tym, że widok zyskał ostatnio kilka nowych pustych wierszy (w kolumnie SubscriberId) i nie został zaktualizowany w EDMX (najpierw baza danych EF).

Kolumna musiała być typu Nullable, aby działała.

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

Przed odświeżeniem widoku:

public int SubscriberId { get; set; }

Po odświeżeniu widoku:

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

Usuwanie i dodawanie widoku z powrotem w EDMX działało.

Mam nadzieję, że to komuś pomoże.

żywa miłość
źródło
To był również mój problem i moja odpowiedź
Simon Nicholls,
4

Użyłem tego kodu i odpowiada on poprawnie, tylko wartość wyjściowa jest zerowa.

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

Widzę, że na to pytanie już udzielono odpowiedzi. Ale jeśli chcesz, aby został on podzielony na dwie wypowiedzi, można rozważyć następujące.

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

Dostałem ten błąd w Entity Framework 6 z tym kodem w czasie wykonywania:

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

Aktualizacja LeandroSoares:

Użyj tego do pojedynczego wykonania:

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

Oryginalny:

Zmieniłem to i wtedy zadziałało:

var fileEventsSum = db.ImportInformations.Any() ? db.ImportInformations.Sum(x => x.FileEvents) : 0;
Ogglas
źródło
1
Czy to nie wykonałoby tego dwukrotnie?
nawfal
To nie jest dobra odpowiedź. Będzie pobierał z DB dwa razy.
Leandro Soares,
@nawfal To prawda, ale jest o wiele lepszy niż błąd czasu wykonywania. Możesz absolutnie użyć linq-to-sql, ale z lambda jest trudniej. Możesz oczywiście złapać wyjątek, ale myślę, że rozwiązanie jest gorsze niż dwie egzekucje.
Ogglas,
@LeandroSoares patrz powyższy komentarz
Ogglas
1
@LeandroSoares Nice one! Zaktualizowałem swoją odpowiedź i użyłem podanego kodu oraz opisu, dlaczego go użyć.
Ogglas,
0

Miałem też do czynienia z tym samym problemem i rozwiązałem, tworząc kolumnę jako zerowalną za pomocą „?” operator.

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

Czasami zwracana jest wartość null.

użytkownik3820036
źródło