วิธีการบังคับให้ LINQ Sum () ส่งคืนค่า 0 ในขณะที่การรวบรวมแหล่งข้อมูลว่างเปล่า


183

โดยทั่วไปเมื่อฉันทำแบบสอบถามต่อไปนี้หากไม่มีโอกาสในการขายตรงกับแบบสอบถามต่อไปนี้จะโยนข้อยกเว้น ในกรณีนี้ฉันต้องการให้ผลรวมเท่ากับ 0 มากกว่าข้อยกเว้นที่ถูกส่งออกไป สิ่งนี้จะเป็นไปได้ในแบบสอบถามเอง - ฉันหมายถึงแทนที่จะเก็บแบบสอบถามและตรวจสอบอยู่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);

2
Whereจะไม่กลับมาnullถ้ามันไม่พบระเบียนใด ๆ ก็จะกลับรายการของศูนย์รายการ ข้อยกเว้นคืออะไร
Mike Perrenoud

3
ข้อยกเว้นคืออะไร
โตโต้

3
ฉันได้รับข้อยกเว้น: การแปลงไปเป็นประเภทค่า 'Int32' ล้มเหลวเนื่องจากค่าที่ปรากฏเป็นโมฆะ พารามิเตอร์ทั่วไปของประเภทผลลัพธ์หรือแบบสอบถามจะต้องใช้ประเภทที่ไม่สามารถใช้ได้
John Mayer

1
@ Stijn ไม่มีสิ่งที่คุณยังไม่ได้ทำงาน ปัญหาคือวิธีที่SQLสร้างขึ้น Amountไม่ใช่ความจริงnullมันเป็นปัญหาที่ล้อมรอบวิธีจัดการผลลัพธ์เป็นศูนย์ ดูคำตอบที่ให้ไว้
Mike Perrenoud

39
คุณไม่ควรใช้สองเท่าสำหรับเงินดอลลาร์! แม้แต่จำนวนเงินดอลลาร์เศษส่วน ไม่เคยไม่เคยใช้เป็นสองเท่าเมื่อตั้งใจจำนวนที่แน่นอน คอลัมน์ฐานข้อมูลของคุณควรจะเป็นรหัสของคุณควรใช้decimal decimalลืมไปเลยว่าคุณเคยรู้จักfloatและเคยdoubleทำงานด้านการเขียนโปรแกรมมาแล้วจนกระทั่งวันที่มีคนบอกให้คุณใช้มันเพื่อสถิติหรือความส่องสว่างของดาวหรือผลลัพธ์ของกระบวนการสุ่มหรือการประจุอิเล็กตรอน! จนแล้วคุณกำลังทำมันผิด
ErikE

คำตอบ:


390

ลองเปลี่ยนคำถามของคุณเป็น:

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

ด้วยวิธีนี้แบบสอบถามของคุณจะเลือกเฉพาะAmountฟิลด์ หากคอลเลกชันว่างเปล่ามันจะส่งคืนองค์ประกอบหนึ่งที่มีค่า0แล้วจะนำไปใช้ผลรวม


มันเป็นการหลอกลวงอย่างแน่นอน แต่จะไม่เลือกรายการของค่า Amount ก่อนและSumพวกมันบนฝั่งเซิร์ฟเวอร์แทนที่จะเป็นด้านฐานข้อมูลใช่ไหม โซลูชันของ imo 2kay นั้นเหมาะสมที่สุดและอย่างน้อยก็ถูกต้องทางความหมายมากกว่า
Maksim Vi

3
@MaksimVI EF จะสร้างแบบสอบถามเมื่อเป็นตัวเป็นตนแรกเมื่อIQueryable<T>ห่วงโซ่หยุด (โดยปกติเมื่อคุณโทรToList, AsEnumerableฯลฯ .. และในกรณีนี้Sum) Sumเป็นวิธีการที่รู้จักและจัดการโดยผู้ให้บริการ Queryable ของ EF และจะสร้างคำสั่ง SQL ที่เกี่ยวข้อง
Simon Belanger

@SimonBelanger ฉันยืนแก้ไขผลรวมจะทำในด้าน DB แต่มันทำในแบบสอบถามย่อยที่เลือกจำนวนเงินก่อน โดยทั่วไปแบบสอบถามแทนเพียงSELECT SUM(a.Amount) FROM (SELECT Amount FROM Leads WHERE ...) AS a SELECT SUM(Amount) FROM Leadsนอกจากนี้แบบสอบถามย่อยยังมีการตรวจสอบโมฆะเพิ่มเติมและการเข้าร่วมด้านนอกที่แปลกด้วยตารางแถวเดียว
Maksim Vi

ไม่ใช่ความแตกต่างด้านประสิทธิภาพที่สำคัญและอาจปรับให้เหมาะสม แต่ฉันยังคิดว่าโซลูชันอื่นดูสะอาดกว่า
Maksim Vi

5
โปรดทราบDefaultIfEmptyว่าไม่ได้รับการสนับสนุนจากผู้ให้บริการ LINQ จำนวนหนึ่งดังนั้นคุณจะต้องโยนToList()หรือสิ่งที่คล้ายกันก่อนที่จะใช้ในกรณีเหล่านั้นเพื่อที่จะสามารถนำไปใช้ในสถานการณ์จำลองLINQ กับวัตถุ
Christopher King

188

ฉันชอบที่จะใช้แฮ็คอื่น:

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;

18
เมื่อใช้ Linq กับ SQL สิ่งนี้จะสร้างรหัส SQL ที่สั้นกว่าคำตอบที่ได้รับการยอมรับ
wertzui

3
นี่คือคำตอบที่ถูกต้อง คนอื่น ๆ ทั้งหมดล้มเหลว การร่ายครั้งแรกเป็นโมฆะจากนั้นทำการเปรียบเทียบผลลัพธ์สุดท้ายกับโมฆะ
Mohsen Afshin

3
นี่เป็นคำตอบที่ดีกว่า Linq ถึง EF สำหรับฉันแล้ว SQL ที่สร้างขึ้นจะทำงานได้ดีกว่า DefaultIfEmpty ประมาณ 3.8 เท่า
Florian

2
นี่คือเร็วกว่ามาก
frakon

1
ฉันจะไม่เรียกสิ่งนี้ว่าแฮ็คเนื่องจากนี่เป็นการใช้ nullables ที่ถูกต้องซึ่งได้รับการออกแบบมาเช่นการแสดงความแตกต่างระหว่างข้อมูลและค่าเริ่มต้น
MikeT

7

ลองใช้วิธีนี้แทนมันจะสั้นกว่า:

db.Leads.Where(..).Aggregate(0, (i, lead) => i + lead.Amount);

2
สิ่งนี้หลีกเลี่ยงข้อยกเว้นหรือไม่
Adrian Wragg

คุณสามารถทำอย่างละเอียด?
DanielV

4

นั่นคือชัยชนะสำหรับฉัน:

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

ในตารางเข้าสู่ระบบของฉันในฟิลด์ NUMBEROFLOGINS ค่าบางค่าเป็นค่า NULL และค่าอื่น ๆ มีหมายเลข INT ฉันรวมที่นี่ทั้งหมด NUMBEROFLOGINS ของผู้ใช้ทั้งหมดของหนึ่ง บริษัท (แต่ละรหัส)


1

ลอง:

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

แบบสอบถาม " SELECT SUM ([จำนวน]) " จะส่งคืนค่า NULL สำหรับรายการว่าง แต่ถ้าคุณใช้ LINQ คาดว่า " Sum (l => l.Amount) " จะคืนค่าสองเท่าและจะไม่อนุญาตให้คุณใช้ตัวดำเนินการ " ?? " เพื่อตั้งค่า 0 สำหรับคอลเลกชันที่ว่างเปล่า

เพื่อหลีกเลี่ยงสถานการณ์นี้คุณต้องทำให้ LINQ คาดหวังว่า " สองเท่า " คุณสามารถทำได้โดยเลือก " (double?) l.Amount "

ไม่ส่งผลกระทบต่อแบบสอบถามไปยัง SQL แต่ทำให้ LINQ ทำงานกับคอลเลกชันที่ว่าง


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

1
โปรดเพิ่มข้อมูลลงในคำตอบเกี่ยวกับรหัส
Jaqen H'ghar

1
ฉันพบข้อผิดพลาดเมื่อฉันลองโดยไม่ใช้ ToList () เนื่องจากไม่มีผลตอบแทนใด ๆ แต่ ToList () จะทำรายการว่างเปล่าและจะไม่ให้ข้อผิดพลาดใด ๆ เมื่อฉันทำ ToList (). Sum ()
Mona

2
คุณอาจไม่ต้องการใช้ToListที่นี่หากสิ่งที่คุณต้องการคือผลรวม การดำเนินการนี้จะส่งคืนชุดผลลัพธ์ทั้งหมด ( Amountสำหรับแต่ละเร็กคอร์ดในกรณีนี้) ในหน่วยความจำและSum()ชุดนั้น ดีกว่าที่จะใช้โซลูชันอื่นซึ่งทำการคำนวณผ่าน SQL Server
Josh M.
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.