#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();
}
Единственным недостатком является то, что вы застряли со стандартным значением по умолчанию, вместо того, чтобы указать его.