Исключение нулевого запроса LINQ, когда Where возвращает 0 строк

#c# #.net #linq #linq-to-sql #.net-3.5

#c# #.net #linq #linq-to-sql #.net-3.5

Вопрос:

У меня есть следующий метод LINQ, который работает должным образом, за исключением случаев, когда строки не найдены, тогда я получаю исключение Null. Я пытаюсь понять, как изменить это, чтобы вернуть 0, если это произойдет.

     public static int GetLastInvoiceNumber(int empNumber)
    {
        using (var context = new CmoDataContext(Settings.Default.LaCrosse_CMOConnectionString))
        {
            context.Log = Console.Out;

            IQueryable<tblGreenSheet> tGreenSheet = context.GetTable<tblGreenSheet>();

            return (tGreenSheet
                            .Where(gs => gs.InvoiceNumber.Substring(2, 4) == empNumber.ToString())
                            .DefaultIfEmpty()
                            .Max(gs => Convert.ToInt32(gs.InvoiceNumber.Substring(6, gs.InvoiceNumber.Length)))
                            );
        }
    }
  

Спасибо


Я попробовал одно из предложений Джона Скита, приведенных ниже, и теперь я получаю Unsupported overload used for query operator 'DefaultIfEmpty'

     public static int GetLastInvoiceNumber(int empNumber)
    {
        using (var context = new CmoDataContext(Settings.Default.LaCrosse_CMOConnectionString))
        {
            context.Log = Console.Out;

            IQueryable<tblGreenSheet> tGreenSheet = context.GetTable<tblGreenSheet>();

            return tGreenSheet
                            .Where(gs => gs.InvoiceNumber.Substring(2, 4) == empNumber.ToString())
                            .Select(gs => Convert.ToInt32(gs.InvoiceNumber.Substring(6, gs.InvoiceNumber.Length)))
                            .DefaultIfEmpty(0)
                            .Max();                                
        }
    }
  

Комментарии:

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

Ответ №1:

Вы используете

 .Where(...)
.DefaultIfEmpty()
  

это означает, что если результатов нет, представьте, что это последовательность с одним нулевым результатом. Затем вы пытаетесь использовать этот нулевой результат в вызове Max…

Вероятно, вы можете изменить его на:

 return tGreenSheet.Where(gs => ...)
                  .Max(gs => (int?) Convert.ToInt32(...)) ?? 0;
  

При этом используется перегрузка, определяющая максимальное количество int? значений, и возвращает int? null значение, если значений не было. ?? 0 Затем преобразует это нулевое значение в 0. По крайней мере, это поведение LINQ to Objects … вам нужно будет проверить, дает ли оно тот же результат для вас.

Конечно, вам не нужно использовать ?? 0 if вы рады изменить сигнатуру метода для возврата int? вместо этого. Это дало бы дополнительную информацию, поскольку вызывающий объект мог бы определить разницу между «нет данных» и «некоторые данные с максимальным значением 0»:

 return tGreenSheet.Where(gs => ...)
                  .Max(gs => (int?) Convert.ToInt32(...));
  

Другой вариант — использовать перегрузку DefaultIfEmpty() , которая принимает значение, например, так:

 return tGreenSheet.Where(gs => ...)
                  .Select(gs => Convert.ToInt32(...))
                  .DefaultIfEmpty(0)
                  .Max();
  

Комментарии:

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

2. @tvanfosson: В общем, я бы тоже, но я пытался соответствовать требованиям OP. Отредактирует, чтобы указать это.

3. Я бы хорошо вернулся int? , но я не могу заставить его компилироваться таким образом. Я получаю это Error 8 'System.Linq.IQueryable<MatrixReloaded.Data.CMO.tblGreenSheet>' does not contain a definition for 'Max' and the best extension method overload 'System.Linq.Enumerable.Max<TSource>(System.Collections.Generic.IEnumerable<TSource>, System.Func<TSource,int>)' has some invalid arguments

4. @RefractedPaladin: Doh, неправильное количество аргументов типа. Исправит.

5. @Jon Skeet: Спасибо, это сделало это. Должен был видеть это сам. Как всегда, спасибо.

Ответ №2:

В подобных ситуациях, когда может быть или не быть совпадающего элемента, я предпочитаю возвращать объект, а не тип значения. Если вы возвращаете тип значения, у вас должна быть некоторая семантика о том, какое значение означает «здесь ничего нет». Я бы изменил его, чтобы вернуть последний счет-фактуру, а затем (если он не равен нулю) получить номер счета-фактуры из счета-фактуры. Добавьте метод в класс, чтобы вернуть числовой номер счета из строки.

 public static tbleGreenSheet GetLastInvoice(int empNumber)
{
    using (var context = new CmoDataContext(Settings.Default.LaCrosse_CMOConnectionString))
    {
        context.Log = Console.Out;

        return context.GetTable<tblGreenSheet>()
                      .Where(gs => gs.InvoiceNumber.Substring(2, 4) == empNumber.ToString())
                      .OrderByDescending(gs => Convert.ToInt32(gs.InvoiceNumber.Substring(6, gs.InvoiceNumber.Length)))
                      .FirstOrDefault();
    }
}

public class tbleGreenSheet
{
    ....
    public int NumericInvoice
    {
        get { return Convert.ToInt32(InvoiceNumber.Substring(6, InvoiceNumber.Length)); }
    }
    ...
}
  

Используется как

 var invoice = Foo.GetLastInvoice( 32 );
if (invoice != null)
{
     var invoiceNumber = invoice.NumericInvoice;

     ...do something...
}
else
{
     ...do something else...
}
  

Ответ №3:

У меня был удивительно похожий опыт работы с IQueryable<T> и NHibernate. Мое решение:

 public static TExpr MaxOrDefault<TItem, TExpr>(this IQueryable<TItem> query,
                                               Expression<Func<TItem, TExpr>> expression) {
  return query.OrderByDescending(expression).Select(expression).FirstOrDefault();
}
  

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