2010-03-16 33 views
17

Tôi có bộ sưu tập Linq là Things, trong đó Thing có thuộc tính Amount (thập phân).Làm thế nào để làm LINQ tập hợp khi có thể có một tập rỗng?

Tôi đang cố gắng để làm một tổng hợp về vấn đề này cho một nhóm nhỏ nào đó of Things:

var total = myThings.Sum(t => t.Amount); 

và hoạt động độc đáo. Nhưng sau đó tôi đã thêm một điều kiện để lại cho tôi không có điều trong kết quả:

var total = myThings.Where(t => t.OtherProperty == 123).Sum(t => t.Amount); 

Và thay vì nhận được tổng = 0 hay null, tôi nhận được một lỗi:

System.InvalidOperationException: The null value cannot be assigned to a member with type System.Decimal which is a non-nullable value type.

Đó là thực sự khó chịu, bởi vì tôi không mong đợi hành vi đó. Tôi đã có thể dự kiến ​​tổng số sẽ bằng không, có thể null - nhưng chắc chắn không phải để ném một ngoại lệ!

Tôi đang làm gì sai? Giải pháp/khắc phục sự cố là gì?

EDIT - ví dụ

Nhờ tất cả để lấy ý kiến ​​của bạn. Dưới đây là một số mã, sao chép và dán (không được đơn giản hóa). Đó là LinqToSql (có lẽ đó là lý do tại sao bạn không thể tái tạo vấn đề của tôi):

var claims = Claim.Where(cl => cl.ID < 0); 
var count = claims.Count(); // count=0 
var sum = claims.Sum(cl => cl.ClaimedAmount); // throws exception 
+1

Wow - that * is * really nasty! Nếu đó là cách mà LINQ được định nghĩa với các bộ kết quả rỗng, đó là một sự lựa chọn đáng buồn bởi các nhà thiết kế ngôn ngữ - vì nó sẽ yêu cầu MỌI việc sử dụng một tập hợp được bọc trong một bài kiểm tra cho một tập rỗng. – MtnViewMark

+2

Nó sẽ giúp ích nếu bạn hiển thị các loại một cách rõ ràng. Tôi chỉ cố gắng 'thập phân mới [] {1} .Where (i => i! = 1) .Sum()' trong LINQPad và có 0, như mong đợi. –

+0

@Craig Stuntz - điều này có thể là do bạn không truy cập vào một thuộc tính trong Sum() của bạn - nghĩa là nó không thể xử lý kết quả với Sum(), nhưng không có Sum (t => t.Amount) như t.Amount được gọi là "t", là null. – Fenton

Trả lời

22

tôi có thể tái tạo vấn đề của bạn với các truy vấn LINQPad sau đây đối với Northwind:

Employees.Where(e => e.EmployeeID == -999).Sum(e => e.EmployeeID) 

Có hai vấn đề ở đây:

  1. Sum() bị quá tải
  2. LINQ to SQL sau ngữ nghĩa SQL, không C# ngữ nghĩa.

Trong SQL, SUM(no rows) trả về null, không phải bằng không. Tuy nhiên, suy luận kiểu cho truy vấn của bạn cung cấp cho bạn decimal làm thông số loại, thay vì decimal?. Bản sửa lỗi này là để giúp loại suy luận chọn đúng loại, tức là:

Employees.Where(e => e.EmployeeID == -999).Sum(e => (int?)e.EmployeeID) 

Bây giờ, quá tải Sum() chính xác sẽ được sử dụng.

+0

+1 - và xem nhận xét của tôi về @Leom Burke rằng tôi nghĩ đây là một lỗi thiết kế trên phần của Microsoft. Rõ ràng là loại mong muốn là "thập phân", vì vậy buộc tôi phải tuyên bố rõ ràng là thực sự câm. –

+0

Không rõ L2S nên làm gì với quá tải 'decimal' cho' Sum', cho rằng nó tuân theo ngữ nghĩa SQL. Trình biên dịch C#, OTOH, chắc chắn * không nên * sử dụng quá tải 'thập phân? 'Vì một số nhà cung cấp LINQ ngẫu nhiên có thể không tuân theo ngữ nghĩa C#. Trình biên dịch đang làm điều đúng, vì bạn đã không đưa ra bất kỳ gợi ý kiểu nào. Phản ứng của L2S ít nhất cũng có thể tranh luận được. –

+0

+ câu trả lời tín dụng - đang dao động giữa bạn và @Leom Burke, cả hai bạn đều trả lời đúng. Nhưng bạn đã đưa ra nhiều nền tảng và giải thích hơn, vì vậy bạn nhận được tín dụng. Cảm ơn! :) –

0

nó gần như có vẻ tốt hơn để gắn bó với một cái gì đó đơn giản như

decimal total = decimal.Zero; 

foreach (Thing myThing in myThings) { 
    if (myThing.OtherProperty == 123) { 
     total = total + myThing.Amount; 
    } 
} 

Trừ, ví dụ này làm việc cho tôi (như đề xuất Craig)

Sử dụng lớp này ...

public class Location 
{ 
    public string Map { get; set; } 
    public int Top { get; set; } 
    public int Left { get; set; } 
} 

Và điều này thiết lập ...

 List<Location> myThings = new List<Location>(); 
     myThings.Add(new Location() 
     { 
      Map = "A", 
      Top = 10, 
      Left = 10 
     }); 

     var total = myThings.Where(t => t.Map == "B").Sum(t => t.Top); 

Nhận của bạn tổng cộng 0.

+2

Không, bạn * không nên * làm điều này, vì nó sẽ không sử dụng việc thực thi 'Sum' của nhà cung cấp truy vấn. Ví dụ: với LINQ to SQL, nó sẽ tìm nạp tất cả các hàng cho máy khách thay vì thực hiện 'SUM' trên máy chủ. –

+0

@Craig Stuntz - Tôi không chắc chắn mức độ chi tiết tồn tại trong câu hỏi để xác nhận tuyên bố của bạn là đúng sự thật. – Fenton

+1

Những gì tôi đã viết là đúng chung, không chỉ cho câu hỏi này: ** Không tái phát minh LINQ aggregates! ** Họ được thực hiện theo cách họ là vì lý do rất tốt. –

-1

Nếu t có một tài sản như một 'HasValue', sau đó tôi sẽ thay đổi biểu thức để:

var total = 
    myThings.Where(t => (t.HasValue) && (t.OtherProperty == 123)).Sum(t => t.Amount); 
+2

Chắc chắn là không. Toàn bộ vấn đề là tập hợp trống - không có "t" để gọi HasValue! –

2

nó ném một ngoại lệ vì kết quả của truy vấn sql kết hợp là null và không thể gán cho var thập phân. Nếu bạn đã làm rồi biến của bạn sau sẽ là null (tôi giả sử ClaimedAmount là số thập phân):

var claims = Claim.Where(cl => cl.ID < 0); 
var count = claims.Count(); // count=0 
var sum = claims.Sum(cl => cl.ClaimedAmount as decimal?); 

sau đó bạn sẽ nhận được các chức năng mà bạn mong muốn.

Bạn cũng có thể làm ToList() tại điểm của câu lệnh where và sau đó tổng sẽ trả về 0 nhưng điều đó sẽ không phù hợp với những gì đã được nói ở đâu đó về LINQ aggregates.

+0

+1 Bạn đúng - nhưng điều đó rất khó chịu với MS để ném một ngoại lệ ở đó. ClaimedAmount được định nghĩa là một số thập phân, vậy tại sao bạn phải khai báo nó là một số thập phân? chỉ để bạn có thể có nó trong một tổng hợp? Đó là câm. –

+0

Bạn không phải * tuyên bố * nó dưới dạng một số thập phân? '. Bạn * có thể * ** cast ** nó, mặc dù! –

+0

Vâng đó là ý của tôi ... :) –

5

Để nhận kết quả không thể vô hiệu, bạn cần truyền số tiền đó thành loại có thể vô hiệu hóa và sau đó xử lý trường hợp Sum trả về giá trị rỗng.

decimal total = myThings.Sum(t => (decimal?)t.Amount) ?? 0; 

Có một câu hỏi khác dành cho (ir)rationale.

Các vấn đề liên quan