Макс или по умолчанию? - PullRequest
       60

Макс или по умолчанию?

166 голосов
/ 04 декабря 2008

Каков наилучший способ получить значение Max из запроса LINQ, который может не возвращать строки? Если я просто сделаю

Dim x = (From y In context.MyTable _
         Where y.MyField = value _
         Select y.MyCounter).Max

Я получаю сообщение об ошибке, когда запрос не возвращает строк. Я мог бы сделать

Dim x = (From y In context.MyTable _
         Where y.MyField = value _
         Select y.MyCounter _
         Order By MyCounter Descending).FirstOrDefault

но для такого простого запроса это немного глупо. Мне не хватает лучшего способа сделать это?

ОБНОВЛЕНИЕ: вот предыстория: я пытаюсь получить следующий счетчик правомочности из дочерней таблицы (устаревшая система, не начинайте ...). Первая строка приемлемости для каждого пациента всегда равна 1, вторая - 2 и т. Д. (Очевидно, это не первичный ключ дочерней таблицы). Итак, я выбираю максимальное существующее значение счетчика для пациента, а затем добавляю к нему 1, чтобы создать новую строку. Когда нет дочерних значений, мне нужно, чтобы запрос возвратил 0 (поэтому добавление 1 даст мне значение счетчика 1). Обратите внимание, что я не хочу полагаться на необработанное количество дочерних строк в случае, если унаследованное приложение вводит пробелы в значениях счетчика (возможно). Моя плохая попытка сделать вопрос слишком общим.

Ответы [ 17 ]

3 голосов
/ 03 мая 2011

Почему бы не что-то более прямое, как:

Dim x = context.MyTable.Max(Function(DataItem) DataItem.MyField = Value)
2 голосов
/ 04 марта 2010

Просто чтобы все знали, что использует Linq to Entities, описанные выше методы не будут работать ...

Если вы попытаетесь сделать что-то вроде

var max = new[]{0}
      .Concat((From y In context.MyTable _
               Where y.MyField = value _
               Select y.MyCounter))
      .Max();

Будет сгенерировано исключение:

System.NotSupportedException: тип узла выражения LINQ 'NewArrayInit' не поддерживается в LINQ to Entities ..

Я бы предложил просто сделать

(From y In context.MyTable _
                   Where y.MyField = value _
                   Select y.MyCounter))
          .OrderByDescending(x=>x).FirstOrDefault());

И FirstOrDefault вернет 0, если ваш список пуст.

1 голос
/ 04 декабря 2008

Одно интересное отличие, которое, кажется, стоит отметить, заключается в том, что хотя FirstOrDefault и Take (1) генерируют один и тот же SQL (согласно LINQPad, в любом случае), FirstOrDefault возвращает значение - значение по умолчанию - при отсутствии соответствующих строк и Take ( 1) не возвращает результатов ... по крайней мере, в LINQPad.

1 голос
/ 05 января 2012
decimal Max = (decimal?)(context.MyTable.Select(e => e.MyCounter).Max()) ?? 0;
0 голосов
/ 08 июня 2016

Для Entity Framework и Linq to SQL мы можем достичь этого, определив метод расширения, который изменяет Expression, переданный методу IQueryable<T>.Max(...):

static class Extensions
{
    public static TResult MaxOrDefault<T, TResult>(this IQueryable<T> source, 
                                                   Expression<Func<T, TResult>> selector)
        where TResult : struct
    {
        UnaryExpression castedBody = Expression.Convert(selector.Body, typeof(TResult?));
        Expression<Func<T, TResult?>> lambda = Expression.Lambda<Func<T,TResult?>>(castedBody, selector.Parameters);
        return source.Max(lambda) ?? default(TResult);
    }
}

Использование:

int maxId = dbContextInstance.Employees.MaxOrDefault(employee => employee.Id);
// maxId is equal to 0 if there is no records in Employees table

Сгенерированный запрос идентичен, он работает так же, как обычный вызов метода IQueryable<T>.Max(...), но если нет записей, он возвращает значение по умолчанию типа T вместо выброса исключения

0 голосов
/ 08 октября 2015

Я выбил MaxOrDefault метод расширения. В этом нет ничего особенного, но его присутствие в Intellisense является полезным напоминанием о том, что Max в пустой последовательности вызовет исключение. Кроме того, метод позволяет указать значение по умолчанию, если требуется.

    public static TResult MaxOrDefault<TSource, TResult>(this 
    IQueryable<TSource> source, Expression<Func<TSource, TResult?>> selector,
    TResult defaultValue = default (TResult)) where TResult : struct
    {
        return source.Max(selector) ?? defaultValue;
    }

Использование в столбце или свойстве типа int с именем Id:

    sequence.DefaultOrMax(s => (int?)s.Id);
0 голосов
/ 12 июля 2013

У меня просто была похожая проблема, мои модульные тесты прошли с использованием Max (), но потерпели неудачу при запуске с действующей базой данных.

Мое решение состояло в том, чтобы отделить запрос от выполняемой логики, а не объединять их в одном запросе.
Мне нужно было решение для работы в модульных тестах с использованием Linq-объектов (в Linq-objects Max () работает с нулями) и Linq-sql при выполнении в реальной среде.

(я высмеиваю Select () в моих тестах)

var requiredDataQuery = _dataRepo.Select(x => new { x.NullableDate1, .NullableDate2 }); 
var requiredData.ToList();
var maxDate1 = dates.Max(x => x.NullableDate1);
var maxDate2 = dates.Max(x => x.NullableDate2);

Менее эффективно? Наверное.

Меня волнует, пока мое приложение не упадет в следующий раз? Нету.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...