Cách buộc LINQ Sum () trả về 0 trong khi bộ sưu tập nguồn trống


183

Về cơ bản khi tôi thực hiện truy vấn sau, nếu không có khách hàng tiềm năng nào phù hợp thì truy vấn sau sẽ ném ngoại lệ. Trong trường hợp đó, tôi muốn có tổng bằng 0 hơn là ngoại lệ được ném. Điều này có thể thực hiện được trong chính truy vấn không - ý tôi là thay vì lưu trữ truy vấn và kiểm tra 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
Các Wheresẽ không được trở về nullnếu nó không tìm thấy bất kỳ hồ sơ, nó sẽ trả về một danh sách các mục zero. Ngoại lệ là gì?
Mike Perrenoud

3
Ngoại lệ là gì?
Toto

3
Tôi nhận được ngoại lệ: Việc chuyển sang loại giá trị 'Int32' không thành công vì giá trị cụ thể hóa là null. Tham số chung của loại kết quả hoặc truy vấn phải sử dụng loại không thể.
John Mayer

1
@Stijn, không có gì bạn đã làm vẫn sẽ không làm việc. Vấn đề là cách SQLtạo ra. Amountthực ra không phải vậy null, nó thực sự là một vấn đề xoay quanh việc nó xử lý kết quả bằng không. Có một cái nhìn vào câu trả lời đã được cung cấp.
Mike Perrenoud

39
Bạn không nên sử dụng gấp đôi số tiền! Ngay cả số tiền đô la phân đoạn. Không bao giờ, bao giờ, sử dụng gấp đôi khi một số tiền chính xác được dự định. Cột cơ sở dữ liệu của bạn nên decimal, mã của bạn nên sử dụng decimal. Quên bạn đã từng biết floatdoubletrong sự nghiệp lập trình của bạn cho đến ngày ai đó bảo bạn sử dụng chúng, để thống kê hoặc độ chói sao hoặc kết quả của một quá trình ngẫu nhiên hoặc điện tích của một điện tử! Cho đến lúc đó, bạn đang làm sai .
ErikE

Câu trả lời:


389

Hãy thử thay đổi truy vấn của bạn thành điều này:

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

Bằng cách này, truy vấn của bạn sẽ chỉ chọn Amounttrường. Nếu bộ sưu tập trống, nó sẽ trả về một phần tử có giá trị 0và sau đó tổng sẽ được áp dụng.


Nó chắc chắn thực hiện được mẹo, nhưng nó sẽ không chọn danh sách các giá trị Số tiền trước và Sumchúng ở phía máy chủ, thay vì ở phía cơ sở dữ liệu? Giải pháp của imo 2kay là tối ưu hơn, ít nhất là đúng về mặt ngữ nghĩa.
Maksim Vi.

3
@MaksimVI EF sẽ tạo ra các truy vấn trên việc thực đầu tiên, khi các IQueryable<T>chuỗi dừng (thường là khi bạn gọi ToList, AsEnumerable, vv .. và trong trường hợp này Sum). Sumlà một phương thức đã biết và được xử lý bởi Nhà cung cấp truy vấn EF và sẽ tạo ra câu lệnh SQL có liên quan.
Simon Belanger

@SimonBelanger Tôi đã sửa, tổng được thực hiện ở bên DB, nhưng nó được thực hiện trên một truy vấn con chọn Số tiền trước. Về cơ bản các truy vấn là SELECT SUM(a.Amount) FROM (SELECT Amount FROM Leads WHERE ...) AS athay vì chỉ SELECT SUM(Amount) FROM Leads. Ngoài ra, truy vấn con có một kiểm tra null bổ sung và phép nối ngoài kỳ lạ với một bảng hàng duy nhất.
Maksim Vi.

Không phải là một sự khác biệt hiệu suất đáng kể và nó có thể được tối ưu hóa, nhưng tôi vẫn nghĩ rằng giải pháp khác có vẻ sạch hơn.
Maksim Vi.

5
Xin lưu ý rằng DefaultIfEmptykhông được hỗ trợ bởi một số nhà cung cấp LINQ, do đó bạn sẽ cần phải ToList()sử dụng một hoặc một cái gì đó tương tự trước khi sử dụng nó trong các trường hợp đó để nó được áp dụng trong kịch bản LINQ to Object .
Christopher King

188

Tôi thích sử dụng một hack khác:

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
Khi sử dụng Linq cho SQL, điều này tạo ra mã SQL ngắn hơn nhiều so với câu trả lời được chấp nhận
wertzui

3
Đây là câu trả lời chính xác. Tất cả những người khác đều thất bại. Lần đầu tiên chuyển thành nullable và sau đó so sánh kết quả cuối cùng với null.
Mohsen Afshin

3
Điều này tốt hơn nhiều so với câu trả lời được chấp nhận cho Linq To EF. Đối với tôi, SQL được tạo ra hoạt động tốt hơn khoảng 3,8 lần so với Default IfEmpty.
Florian

2
Đây là NHANH CHÓNG.
frakon

1
Tôi sẽ không gọi đây là hack, vì đây là
null null

7

Hãy thử điều này thay vào đó, nó ngắn hơn:

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

2
Điều này có tránh được ngoại lệ không?
Adrian Wragg

bạn có thể giải thích?
DanielV

4

Đó là chiến thắng cho tôi:

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

Trong bảng ĐĂNG NHẬP của tôi, trong trường NUMBEROFLOGIN, một số giá trị là NULL và các giá trị khác có số INT. Tôi tổng hợp ở đây tổng số NUMOFLOGIN của tất cả người dùng của một Công ty (Mỗi Id).


1

Thử:

thu nhập gấp đôi = db.Lead.Where (l => l.ShouldBeIncnced) .Sum (l => (double?) l <ount) ?? 0 ;

Truy vấn " CHỌN SUM ([Số tiền]) " sẽ trả về NULL cho danh sách trống. Nhưng nếu bạn sử dụng LINQ, nó hy vọng rằng " Tổng (l => l <ount) " trả về gấp đôi và nó không cho phép bạn sử dụng toán tử " ?? " để đặt 0 cho bộ sưu tập trống.

Để tránh tình trạng này, bạn cần làm cho LINQ mong đợi " đôi? ". Bạn có thể làm điều đó bằng cách đúc " (gấp đôi?) L <ount ".

Nó không ảnh hưởng đến truy vấn SQL nhưng nó làm cho LINQ hoạt động cho các bộ sưu tập trống.


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
Vui lòng thêm một số thông tin vào câu trả lời về mã
Jaqen H'ghar

1
Tôi đã gặp lỗi khi tôi thử mà không có ToList (), vì nó không trả về gì cả. Nhưng ToList () sẽ tạo danh sách trống và nó không đưa ra bất kỳ lỗi nào khi tôi thực hiện ToList (). Sum ().
Mona

2
Bạn có thể sẽ không muốn sử dụng ToListở đây nếu tất cả những gì bạn muốn là tổng. Điều này sẽ trả lại toàn bộ tập kết quả (chỉ Amountcho mỗi bản ghi trong trường hợp này) vào bộ nhớ và sau đó Sum()là bộ đó. Tốt hơn nhiều để sử dụng một giải pháp khác thực hiện tính toán thông qua SQL Server.
Josh M.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.