Максимальне значення повернення, якщо запит порожній


175

У мене є цей запит:

int maxShoeSize = Workers
    .Where(x => x.CompanyId == 8)
    .Max(x => x.ShoeSize);

Що буде, maxShoeSizeякщо компанія 8 взагалі не має працівників?

ОНОВЛЕННЯ:
Як я можу змінити запит, щоб отримати 0, а не виняток?


Наор: ви чули про LINQPad?
Мітч Пшеничний

3
Я не розумію, чому ви б запитали "Що буде maxShoeSize?" якщо ви вже випробували це.
jwg

@jwg: Напевно, я хотів побачити, чи знаєте ви відповідь :) Зрештою, я отримав кращий спосіб зробити те, про що я попросив, і це те, що я мав на увазі.
Наор

@Naor, це не гра в здогадки. Я б також спростував оригінальне запитання. Якщо ви знаєте відповідь, дайте нам її, інакше ви виглядаєте ледачою. Щойно я збирався зробити те саме питання, і я готую всю інформацію, включаючи повідомлення про виключення.
Хуан Карлос Оропеза

Відповіді:


298
int maxShoeSize = Workers.Where(x => x.CompanyId == 8)
                         .Select(x => x.ShoeSize)
                         .DefaultIfEmpty(0)
                         .Max();

Нуль в DefaultIfEmptyне потрібен.


Працює :) Але в моєму коді нуль у DefaultIfEmpty був необхідний.
Карлос Теноріо Перес

1
Щодо першої частини запитання, на яку тут не дано повного відповіді: Згідно з офіційною документацією , якщо загальний тип порожньої послідовності є референтним типом, ви отримуєте нуль. В іншому випадку, відповідно до цього питання , виклик Max()порожньої послідовності призводить до помилки.
Раймунд

59

Я знаю, що це старе запитання, і прийнята відповідь працює, але це запитання відповіло на моє запитання про те, чи призведе такий порожній набір до виключення чи default(int) результату.

Однак прийнята відповідь, хоча вона працює, не є ідеальним рішенням ІМХО, яке тут не дається. Таким чином, я надаю це у власній відповіді на користь тому, хто її шукає.

Оригінальний код ОП:

int maxShoeSize = Workers.Where(x => x.CompanyId == 8).Max(x => x.ShoeSize);

Ось як я написав би це, щоб запобігти виняткам та забезпечити результат за замовчуванням:

int maxShoeSize = Workers.Where(x => x.CompanyId == 8).Max(x => x.ShoeSize as int?) ?? 0;

Це спричинює повернення типу Maxфункції int?, що дозволяє отримати nullрезультат, а потім ??замінює nullрезультат на 0.


EDIT
Просто для того, щоб уточнити щось із коментарів, Entity Framework наразі не підтримує asключове слово, тому спосіб його написання під час роботи з EF був би таким:

int maxShoeSize = Workers.Where(x => x.CompanyId == 8).Max<[TypeOfWorkers], int?>(x => x.ShoeSize) ?? 0;

Оскільки [TypeOfWorkers]може бути довге ім'я класу і нудно писати, я додав метод розширення, щоб допомогти.

public static int MaxOrDefault<T>(this IQueryable<T> source, Expression<Func<T, int?>> selector, int nullValue = 0)
{
    return source.Max(selector) ?? nullValue;
}

Це тільки ручки int, але те ж саме можна було б зробити long, doubleабо будь-який інший тип значення вам потрібно. Використовуючи цей метод розширення дуже просто, ви просто передаєте свою функцію вибору та необов'язково включаєте значення, яке буде використано для null, яке за замовчуванням дорівнює 0. Отже, вищезгадане можна переписати так:

int maxShoeSize = Workers.Where(x => x.CompanyId == 8).MaxOrDefault(x => x.ShoeSize);

Сподіваємось, це допомагає людям ще більше.


3
Чудове рішення! Більш популярна DefaultIfEmptyвідповідь працює добре лише тоді, коли Maxне робиш оцінку.
McGuireV10

@ McGuireV10 Так, мені зазвичай не подобається використовувати Selectяк середнього чоловіка, коли я просто буду використовувати сукупну функцію, як Maxна результат. Я також думаю (я ще не перевіряв цього), що згенерований SQL використовував би додатковий запит під виділення, виконуючи це, тоді як мій би просто займався порожнім набором, повертаючи null. Дякуємо за відгуки та відгуки! ;)
CptRobby

@ McGuireV10 Точно так же, якщо ShoeSizeнасправді у відповідній Uniformсуті, я б не використати Workers.Where(x => x.CompanyId == 8).Select(x => x.Uniform).Max(x => x.ShoeSize), замість цього я б просто тримати всю оцінку в Maxфункції: Workers.Where(x => x.CompanyId == 8).Max(x => x.Uniform.ShoeSize). Я вважаю за краще використовувати якомога менше методів у своїх запитах, щоб дозволити EF отримати найбільшу свободу у вирішенні питання про ефективну побудову запитів. ;-)
CptRobby

1
Я не зміг змусити це працювати в EntityFramework. Хтось може пролити якесь світло? (Використовується техніка DefaultIfEmpty).
Moe Sisko

1
Узагальнена версія методу розширення:public static TResult MaxOrDefault<TElement, TResult>(this IQueryable<TElement> items, Expression<Func<TElement, TResult>> selector, TResult defaultValue = default) where TResult : struct => items.Select(selector).Max(item => (TResult?)item) ?? defaultValue;
відносно



10

Якщо це Linq до SQL, я не люблю використовувати, Any()оскільки це призводить до декількох запитів до SQL-сервера.

Якщо ShoeSizeце не нульове поле, то використання просто .Max(..) ?? 0волі не вийде, але наступне буде:

int maxShoeSize = Workers.Where(x = >x.CompanyId == 8).Max(x => (int?)x.ShoeSize) ?? 0;

Він абсолютно не змінює випущений SQL, але він повертає 0, якщо послідовність порожня, оскільки змінює значення Max()для повернення int?замість an int.


4
int maxShoeSize=Workers.Where(x=>x.CompanyId==8)
    .Max(x=>(int?)x.ShoeSize).GetValueOrDefault();

(припускаючи, що ShoeSizeце типint )

Якщо Workersце DbSetабо ObjectSetвід Entity Framework ваш початковий запит буде кидатися InvalidOperationException, але не скаржиться на порожній послідовності , але скаржиться , що матеріалізована значення NULL не може бути перетворені в int.


2

Макс кине System.InvalidOperationException "Послідовність не містить елементів"

class Program
{
    static void Main(string[] args)
    {
        List<MyClass> list = new List<MyClass>();

        list.Add(new MyClass() { Value = 2 });

        IEnumerable<MyClass> iterator = list.Where(x => x.Value == 3); // empty iterator.

        int max = iterator.Max(x => x.Value); // throws System.InvalidOperationException
    }
}

class MyClass
{
    public int Value;
}

2

Примітка: запит із DefaultIfEmpty()може бути значно повільнішим . У моєму випадку це був простий запит.DefaultIfEmpty(DateTime.Now.Date) .

Я був лінивий, щоб переглядати це, але очевидно, EF намагався отримати всі рядки, а потім взяти Max() значення.

Висновок: іноді обробка InvalidOperationExceptionможе бути кращим вибором.


0

Ви можете використовувати тернар всередині, .Max()щоб обробити присудок і встановити його значення;

// assumes Workers != null && Workers.Count() > 0
int maxShoeSize = Workers.Max(x => (x.CompanyId == 8) ? x.ShoeSize : 0);

Вам потрібно обробити Workersколекцію нульовою / порожньою, якщо це можливо, але це залежатиме від вашої реалізації.


0

Ви можете спробувати це:

int maxShoeSize = Workers.Where(x=>x.CompanyId == 8).Max(x => x.ShoeSize) ?? 0;

Я думаю, що це не вдасться, оскільки Макс очікує int, і він отримує нуль; тому помилка вже сталася до того, як оператор зв'язання з нулем набуде чинності.
d219

0

Ви можете перевірити, чи є працівники, перш ніж робити Max ().

private int FindMaxShoeSize(IList<MyClass> workers) {
   var workersInCompany = workers.Where(x => x.CompanyId == 8);
   if(!workersInCompany.Any()) { return 0; }
   return workersInCompany.Max(x => x.ShoeSize);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.