Как заставить 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
Вы не должны использовать double для долларовых сумм! Даже дробные суммы в долларах. Никогда, никогда, никогда не используйте double, когда намечено точное количество. Ваши столбцы базы данных должны быть decimal, ваш код должен использовать decimal. Забудьте вы когда - нибудь знал , floatи doubleв вашей карьере программирования до того дня , кто - то говорит вам , чтобы использовать их для статистики или звезды яркости или результатов стохастического процесса или заряд электрона! До тех пор, вы делаете это неправильно .
ErikE

Ответы:

391

Попробуйте изменить свой запрос на это:

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является известным и обрабатываемым методом EF Queryable Provider, который генерирует соответствующий оператор SQL.
Саймон Белэндж
@SimonBelanger Я исправлен, сумма сделана на стороне БД, но она сделана на подзапросе, который сначала выбирает Суммы. По сути, это запрос, SELECT SUM(a.Amount) FROM (SELECT Amount FROM Leads WHERE ...) AS aа не просто SELECT SUM(Amount) FROM Leads. Также подзапрос имеет дополнительную нулевую проверку и странное внешнее соединение с одной строкой таблицы.
Максим Ви.
Не существенная разница в производительности, и, вероятно, она оптимизирована, но я все еще думаю, что другое решение выглядит чище.
Максим Ви.
5
Имейте в виду, что DefaultIfEmptyэто не поддерживается рядом провайдеров LINQ, поэтому вам придется добавить ToList()или что-то подобное перед тем, как использовать его в этих случаях, чтобы он применялся в сценарии LINQ to Objects .
Кристофер Кинг
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;
tukaef
источник
18
При использовании Linq для SQL это генерирует намного более короткий код SQL, чем принятый ответ
wertzui
3
Это правильный ответ. Все остальные терпят неудачу. Сначала приведение к nullable, а затем сравнение окончательного результата с нулем.
Мохсен Афшин
3
Это намного лучше, чем принятый ответ для Linq To EF. Для меня сгенерированный SQL работает примерно в 3,8 раза лучше, чем DefaultIfEmpty.
Флориан
2
Это намного быстрее.
Фракон
1
я бы не назвал это хаком, поскольку именно для этого и предназначены
nullables
7

Попробуйте это вместо этого, это короче:

db.Leads.Where(..).Aggregate(0, (i, lead) => i + lead.Amount);
Ковач Роберт
источник
2
Это избегает исключения?
Адриан Рагг,
не могли бы вы уточнить?
DanielV
4

Это победа для меня:

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

В моей таблице LOGIN в поле NUMBEROFLOGINS некоторые значения имеют значение NULL, а другие имеют номер INT. Я суммирую здесь общее количество (NUMBEROFLOGINS) всех пользователей одной корпорации (каждый идентификатор).

Педро Рамос
источник
1

Пытаться:

двойной доход = db.Leads.Where (l => l.ShouldBeIncluded) .Sum (l => (double?) l.Amount) ?? 0 ;

Запрос « SELECT SUM ([Amount]) » вернет NULL для пустого списка. Но если вы используете LINQ, он ожидает, что « Sum (l => l.Amount) » возвращает значение double, и это не позволяет вам использовать оператор « ?? » для установки 0 для пустой коллекции.

Чтобы избежать этой ситуации, вам нужно заставить LINQ ожидать " double? ". Вы можете сделать это, применив " (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();
Mona
источник
1
Пожалуйста, добавьте некоторую информацию в ответ о коде
Jaqen H'ghar
1
Я получаю сообщение об ошибке при попытке без ToList (), так как он ничего не возвращает. Но ToList () создаст пустой список, и он не выдаст никакой ошибки, когда я делаю ToList (). Sum ().
Мона
2
Вы, вероятно, не хотите использовать ToListздесь, если все, что вы хотите, это сумма. Это вернет весь набор результатов (только Amountдля каждой записи в этом случае) в память и затем Sum()этот набор. Гораздо лучше использовать другое решение, которое выполняет вычисления через SQL Server.
Джош М.