Cara memaksa LINQ Sum () untuk mengembalikan 0 saat pengumpulan sumber kosong

183

Pada dasarnya ketika saya melakukan kueri berikut, jika tidak ada arahan yang cocok dengan kueri berikut akan melempar pengecualian. Dalam hal ini saya lebih suka memiliki jumlah menyamakan 0 daripada pengecualian yang dilemparkan. Apakah ini mungkin terjadi dalam permintaan itu sendiri - maksud saya daripada menyimpan permintaan dan memeriksa 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
sumber
2
Tidak Whereakan kembali nulljika tidak menemukan catatan, itu akan mengembalikan daftar item nol. Apa pengecualiannya?
Mike Perrenoud
3
Apa pengecualiannya?
Toto
3
Saya mendapatkan pengecualian: Para pemain untuk tipe nilai 'Int32' gagal karena nilai material adalah nol. Parameter generik tipe hasil atau kueri harus menggunakan tipe nullable.
John Mayer
1
@Stijn, tidak apa yang Anda lakukan masih tidak akan berhasil. Masalahnya adalah cara SQLdihasilkannya. Amountsebenarnya tidak null, itu benar-benar masalah seputar bagaimana menangani hasil nol. Lihatlah jawaban yang disediakan.
Mike Perrenoud
39
Anda seharusnya tidak menggunakan ganda untuk jumlah dolar! Bahkan jumlah dolar fraksional. Tidak pernah, pernah, menggunakan ganda ketika jumlah yang tepat dimaksudkan. Kolom basis data Anda seharusnya decimal, kode Anda harus digunakan decimal. Lupakan yang pernah Anda kenal floatdan doubledalam karier pemrograman Anda sampai suatu hari seseorang memberi tahu Anda untuk menggunakannya, untuk statistik atau pencahayaan bintang atau hasil dari proses stokastik atau muatan elektron! Sampai saat itu, Anda salah melakukannya .
ErikE

Jawaban:

391

Coba ubah kueri Anda menjadi ini:

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

Dengan cara ini, kueri Anda hanya akan memilih Amountbidang. Jika koleksi kosong, itu akan mengembalikan satu elemen dengan nilai 0dan kemudian jumlah akan diterapkan.

Simon Belanger
sumber
Ini memang berhasil, tetapi bukankah akan memilih daftar nilai Jumlah pertama dan Summereka di sisi server, bukan di sisi database? solusi imo 2kay lebih optimal, setidaknya semantik benar.
Maksim Vi.
3
@MaksimVI EF akan menghasilkan query pada materialisasi pertama, ketika IQueryable<T>rantai berhenti (biasanya ketika Anda menelepon ToList, AsEnumerable, dll .. dan dalam hal ini Sum). Sumadalah metode yang diketahui dan ditangani oleh Penyedia Queryable EF dan akan menghasilkan pernyataan SQL terkait.
Simon Belanger
@SimonBelanger Saya berdiri dikoreksi, jumlahnya dibuat di sisi DB, tapi itu dibuat pada subquery yang memilih Jumlah pertama. Pada dasarnya kueri SELECT SUM(a.Amount) FROM (SELECT Amount FROM Leads WHERE ...) AS abukan hanya adil SELECT SUM(Amount) FROM Leads. Subquery juga memiliki cek nol tambahan dan join luar aneh dengan tabel baris tunggal.
Maksim Vi.
Bukan perbedaan kinerja yang signifikan dan mungkin dioptimalkan, tetapi saya masih berpikir solusi lain terlihat lebih bersih.
Maksim Vi.
5
Ketahuilah bahwa DefaultIfEmptyini tidak didukung oleh sejumlah penyedia LINQ sehingga Anda harus memasukkan ToList()atau sesuatu yang serupa sebelum menggunakannya dalam kasus-kasus tersebut sehingga itu akan diterapkan dalam skenario LINQ to Objects .
Christopher King
188

Saya lebih suka menggunakan hack lain:

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
sumber
18
Saat menggunakan Linq ke SQL, ini menghasilkan kode SQL yang jauh lebih pendek daripada jawaban yang diterima
wertzui
3
Ini jawaban yang benar. Semua yang lain gagal. Pertama casting ke nullable dan kemudian membandingkan hasil akhir dengan null.
Mohsen Afshin
3
Ini jauh lebih baik daripada jawaban yang diterima untuk Linq To EF. Bagi saya SQL yang dihasilkan bekerja sekitar 3,8 kali lebih baik daripada DefaultIfEmpty.
Florian
2
Ini BANYAK LEBIH CEPAT.
frakon
1
saya tidak akan menyebut ini peretasan, karena ini adalah penggunaan tepat untuk nullables, yaitu menunjukkan perbedaan antara tidak ada data dan nilai default
MikeT
7

Coba ini sebagai gantinya, ini lebih pendek:

db.Leads.Where(..).Aggregate(0, (i, lead) => i + lead.Amount);
Kovács Róbert
sumber
2
Apakah ini menghindari pengecualian?
Adrian Wragg
bisakah kamu menguraikan?
DanielV
4

Itu kemenangan bagi saya:

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

Di tabel LOGIN saya, di bidang NUMBEROFLOGINS beberapa nilai adalah NULL dan yang lain memiliki nomor INT. Saya menjumlahkan di sini total NUMBEROFLOGIN dari semua pengguna satu Korporasi (Setiap Id).

Pedro Ramos
sumber
1

Mencoba:

penghasilan ganda = db.Leads.Where (l => l.ShouldBeIncluded) .Sum (l => (double?) l.Amount) ?? 0 ;

Kueri " SELECT SUM ([Amount]) " akan mengembalikan NULL untuk daftar kosong. Tetapi jika Anda menggunakan LINQ itu mengharapkan bahwa " Jumlah (l => l.Amount) " mengembalikan dua kali lipat dan itu tidak memungkinkan Anda untuk menggunakan operator " ?? " untuk menetapkan 0 untuk koleksi kosong.

Untuk menghindari situasi ini, Anda perlu membuat LINQ mengharapkan " dua kali lipat? ". Anda dapat melakukannya dengan casting " (dobel?) L. Jumlah ".

Itu tidak mempengaruhi permintaan untuk SQL tetapi itu membuat LINQ bekerja untuk koleksi kosong.

Maxim Lukoshko
sumber
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
sumber
1
Harap tambahkan beberapa informasi ke jawaban tentang kode
Jaqen H'ghar
1
Saya mendapat kesalahan ketika saya mencoba tanpa ToList (), karena tidak mengembalikan apa pun. Tetapi ToList () akan membuat daftar kosong dan tidak memberikan kesalahan ketika saya melakukan ToList (). Sum ().
Mona
2
Anda mungkin tidak ingin menggunakan di ToListsini jika yang Anda inginkan adalah jumlah. Ini akan mengembalikan set seluruh hasil (hanya Amountuntuk setiap catatan dalam kasus ini) ke dalam memori dan kemudian Sum()set itu. Jauh lebih baik menggunakan solusi lain yang melakukan perhitungan melalui SQL Server.
Josh M.