Як змусити 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якщо він не знайшов яких - або записів, вона повертає список нульових елементів. Який виняток?
Майк Перренод

3
Який виняток?
Тото

3
Я отримую виняток: тип передачі значення "Int32" не вдався, оскільки матеріалізоване значення є нульовим. Або загальний параметр типу результату, або запит повинен використовувати нульовий тип.
Джон Майер

1
@Stijn, ні те, що ти все-таки робив, не працювало б. Питання полягає в тому, SQLяк формується. Amountнасправді nullце справді не проблема, коли вона обробляє нульові результати. Подивіться на відповідь, яка була надана.
Майк Перренуд

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 є більш оптимальним, принаймні більш семантично правильним.
Максим Ві.

3
@MaksimVI EF буде генерувати запит при першій матеріалізації, коли IQueryable<T>ланцюг зупиняється (як правило, коли ви дзвоните ToList, AsEnumerableі т. Д. І в цьому випадку Sum). Sumє відомим і оброблюваним методом постачальником Queryable EF і генерує відповідний оператор SQL.
Саймон Белангер

@SimonBelanger Я виправлений, сума робиться на стороні БД, але це робиться на підзапиті, який вибирає Суми спочатку. В основному запит SELECT SUM(a.Amount) FROM (SELECT Amount FROM Leads WHERE ...) AS aзамість просто SELECT SUM(Amount) FROM Leads. Також підзапит має додаткову перевірку нуля та дивне зовнішнє з'єднання з таблицею з одним рядком.
Максим Ві.

Незначна різниця у продуктивності, і це, мабуть, оптимізовано, але я все одно думаю, що інше рішення виглядає більш чистим.
Максим Ві.

5
Майте на увазі, що DefaultIfEmptyце не підтримується багатьма постачальниками LINQ, тож вам доведеться ввести такі ToList()чи щось подібне перед тим, як використовувати його в тих випадках, щоб воно було застосовано в сценарії LINQ до об'єктів .
Крістофер Кінг

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
Це правильна відповідь. Усі інші провалюються. Спочатку підбираємо до нуля, а потім порівнюємо кінцевий результат з нульовим.
Мохсен Афшин

3
Це набагато краще, ніж прийнята відповідь для Linq To EF. Для мене згенерований SQL працює приблизно в 3,8 рази краще, ніж DefaultIfEmpty.
Флоріан

2
Це набагато швидше.
frakon

1
Я б не називав це злом, тому що саме для цього використані нульові
знаки


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

Спробуйте:

подвійний заробіток = db.Leads.Where (l => l.ShouldBeIncluded) .Sum (l => (подвійний?) l.Amount) ?? 0 ;

Запит " SELECT SUM ([Сума]) " поверне NULL для порожнього списку. Але якщо ви використовуєте LINQ, то очікується, що " Сума (l => l.Amount) " повертається вдвічі, і це не дозволяє вам використовувати " ?? оператор " для встановлення 0 для порожнього збору.

Щоб уникнути такої ситуації, вам потрібно змусити LINQ очікувати " подвійним? ". Ви можете це зробити, відкинувши " (подвійний?) 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 ().
Мона

2
Ви, мабуть, не хотіли б ToListтут використовувати, якщо все, що вам потрібно, - це сума. Це поверне весь набір результатів (тільки Amountдля кожного запису в даному випадку) в пам'ять, а потім і Sum()цей набір. Набагато краще використовувати інше рішення, яке робить розрахунок через SQL Server.
Джош М.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.