LINQ Lambda Summing NULL

1

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

Проблема, с которой я сталкиваюсь, заключается в том, что функция суммы LINQ суммирует NULL как ноль (0). Я ожидал бы, что если бы у меня было значение "15", а другое значение "нуль", то сумма должна быть "15". Но я ожидал бы, что если первое значение было "null", а второе значение было "null", то сумма также должна быть "null". Однако мне говорят, что сумма равна "0".

Как я могу заставить его функционировать так, как я ожидал бы? Я хочу, чтобы он вел себя, возвращая значение, если есть хотя бы одно значение или возвращает "null", если нет значений.

Теперь для некоторого кода:

virtual public IStatSplit Totals
{
  get
  {
    var cSplit = _splits.Where(s => s.Split == SplitType.COMBINED).SingleOrDefault();

    if( cSplit != null )
    { return cSplit; }

    cSplit = _splits.Where( s => s.Split != SplitType.COMBINED )
                    .GroupBy( g => 1 == 1 ).Select( x => new StatSplit
    {
      AB = (uint?)x.Sum( q => q.AB ),
      CI = (uint?)x.Sum( q => q.CI ),
      B2 = (uint?)x.Sum( q => q.B2 ),
      B3 = (uint?)x.Sum( q => q.B3 ),
      GDP = (uint?)x.Sum( q => q.GDP ),
      H = (uint?)x.Sum( q => q.H ),
      HB = (uint?)x.Sum( q => q.HB ),
      HR = (uint?)x.Sum( q => q.HR ),
      RBI = (uint?)x.Sum( q => q.RBI ),
      IBB = (uint?)x.Sum( q => q.IBB ),
      SF = (uint?)x.Sum( q => q.SF ),
      SH = (uint?)x.Sum( q => q.SH ),
      SO = (uint?)x.Sum( q => q.SO ),
      BB = (uint?)x.Sum( q => q.BB ),
      Split = SplitType.COMBINED
    } ).SingleOrDefault();

    return cSplit;
  }
}

И вот тестовые данные, которые не пройдут единичный тест:

[TestMethod]
public void PitchingTotals()
{
  var splits = GetSplits();
  var pitching = new Base.Pitching();
  pitching.Splits = splits;

  var expected = GetTotalSplit();
  var result = pitching.Totals;

  // result.RBI = 0
  // expected.RBI = null
  // this fails because the "0" is not expected

  Assert.AreEqual( expected, result );
}

private List<IStatSplit> GetSplits()
{
  var lhSplit = new Base.StatSplit
  {
    AB = 442,
    H = 97,
    B2 = 14,
    B3 = 0,
    HR = 6,
    BB = 28,
    HB = 6,
    SF = 1,
    SH = 5,
    SO = 73,
    GDP = 7,
    IBB = 4,
    CI = 0,
    RBI = null,
    Split = Enumerations.SplitType.VS_LEFT
  };

  var rhSplit = new Base.StatSplit
  {
    AB = 633,
    H = 101,
    B2 = 9,
    B3 = 0,
    HR = 5,
    BB = 34,
    HB = 1,
    SF = 1,
    SH = 10,
    SO = 195,
    GDP = 11,
    IBB = 2,
    CI = 0,
    RBI = null,
    Split = Enumerations.SplitType.VS_RIGHT
  };

  List<IStatSplit> splits = new List<IStatSplit>();
  splits.Add( lhSplit );
  splits.Add( rhSplit );

  return splits;
}

private IStatSplit GetTotalSplit()
{
  var split = new Base.StatSplit
  {
    AB = 1075,
    H = 198,
    B2 = 23,
    B3 = 0,
    HR = 11,
    BB = 62,
    HB = 7,
    SF = 2,
    SH = 15,
    SO = 268,
    GDP = 18,
    IBB = 6,
    CI = 0,
    RBI = null,
    Split = Enumerations.SplitType.COMBINED
  };

  return split;
}
Теги:
linq
lambda

3 ответа

1
Лучший ответ

Вместо Sum вы можете использовать Aggregate

x.Aggregate(
    (uint?)null,
    (sum, currentItem) => 
        !sum.HasValue && !currentItem.AB.HasValue ? 
        (uint?)null : 
        sum.GetValueOrDefault() + currentItem.AB.GetValueOrDefault());

Это начнется с null uint? значения и итерации по каждому элементу. Если текущая sum и значение currentItem.AB равны null тогда следующая sum будет оставаться null. Если какой- либо не null, то они добавляются и значение по умолчанию используются, если один является null, что для uint является 0.

  • 0
    Позвольте мне попробовать это и вернуться к вам.
  • 0
    Круто, это сработало. Спасибо!
0

Основываясь на реакции Юхарра, я сначала попробовал это:

cSplit = _splits.Where( s => s.Split != SplitType.COMBINED )
    .GroupBy( g => 1 == 1 ).Select( x => new StatSplit
    {
      AB = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.AB.HasValue ?
            (uint?)null : sum.GetValueOrDefault() + item.AB.GetValueOrDefault() ),
      CI = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.CI.HasValue ?
            (uint?)null : sum.GetValueOrDefault() + item.CI.GetValueOrDefault() ),
      B2 = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.B2.HasValue ?
            (uint?)null : sum.GetValueOrDefault() + item.B2.GetValueOrDefault() ),
      B3 = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.B3.HasValue ?
            (uint?)null : sum.GetValueOrDefault() + item.B3.GetValueOrDefault() ),
      GDP = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.GDP.HasValue ?
            (uint?)null : sum.GetValueOrDefault() + item.GDP.GetValueOrDefault() ),
      H = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.H.HasValue ?
            (uint?)null : sum.GetValueOrDefault() + item.H.GetValueOrDefault() ),
      HB = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.HB.HasValue ?
            (uint?)null : sum.GetValueOrDefault() + item.HB.GetValueOrDefault() ),
      HR = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.HR.HasValue ?
            (uint?)null : sum.GetValueOrDefault() + item.HR.GetValueOrDefault() ),
      RBI = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.RBI.HasValue ?
            (uint?)null : sum.GetValueOrDefault() + item.RBI.GetValueOrDefault() ),
      IBB = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.IBB.HasValue ?
            (uint?)null : sum.GetValueOrDefault() + item.IBB.GetValueOrDefault() ),
      SF = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.SF.HasValue ?
            (uint?)null : sum.GetValueOrDefault() + item.SF.GetValueOrDefault() ),
      SH = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.SH.HasValue ?
            (uint?)null : sum.GetValueOrDefault() + item.SH.GetValueOrDefault() ),
      SO = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.SO.HasValue ?
            (uint?)null : sum.GetValueOrDefault() + item.SO.GetValueOrDefault() ),
      BB = x.Aggregate( (uint?)null, ( sum, item ) => !sum.HasValue && !item.BB.HasValue ?
            (uint?)null : sum.GetValueOrDefault() + item.BB.GetValueOrDefault() ),
      Split = SplitType.COMBINED
    } ).SingleOrDefault();

Это сработало, но это выглядело уродливо для меня. Я немного переделал и придумал следующее:

cSplit = _splits.Aggregate( new StatSplit() { Split = SplitType.COMBINED },
    ( sum, item ) =>
    {
      sum.AB = !sum.AB.HasValue && !item.AB.HasValue ? (uint?)null : sum.AB.GetValueOrDefault() + item.AB.GetValueOrDefault();
      sum.CI = !sum.CI.HasValue && !item.CI.HasValue ? (uint?)null : sum.CI.GetValueOrDefault() + item.CI.GetValueOrDefault();
      sum.B2 = !sum.B2.HasValue && !item.B2.HasValue ? (uint?)null : sum.B2.GetValueOrDefault() + item.B2.GetValueOrDefault();
      sum.B3 = !sum.B3.HasValue && !item.B3.HasValue ? (uint?)null : sum.B3.GetValueOrDefault() + item.B3.GetValueOrDefault();
      sum.GDP = !sum.GDP.HasValue && !item.GDP.HasValue ? (uint?)null : sum.GDP.GetValueOrDefault() + item.GDP.GetValueOrDefault();
      sum.H = !sum.H.HasValue && !item.H.HasValue ? (uint?)null : sum.H.GetValueOrDefault() + item.H.GetValueOrDefault();
      sum.HB = !sum.HB.HasValue && !item.HB.HasValue ? (uint?)null : sum.HB.GetValueOrDefault() + item.HB.GetValueOrDefault();
      sum.HR = !sum.HR.HasValue && !item.HR.HasValue ? (uint?)null : sum.HR.GetValueOrDefault() + item.HR.GetValueOrDefault();
      sum.RBI = !sum.RBI.HasValue && !item.RBI.HasValue ? (uint?)null : sum.RBI.GetValueOrDefault() + item.RBI.GetValueOrDefault();
      sum.IBB = !sum.IBB.HasValue && !item.IBB.HasValue ? (uint?)null : sum.IBB.GetValueOrDefault() + item.IBB.GetValueOrDefault();
      sum.SF = !sum.SF.HasValue && !item.SF.HasValue ? (uint?)null : sum.SF.GetValueOrDefault() + item.SF.GetValueOrDefault();
      sum.SH = !sum.SH.HasValue && !item.SH.HasValue ? (uint?)null : sum.SH.GetValueOrDefault() + item.SH.GetValueOrDefault();
      sum.SO = !sum.SO.HasValue && !item.SO.HasValue ? (uint?)null : sum.SO.GetValueOrDefault() + item.SO.GetValueOrDefault();
      sum.BB = !sum.BB.HasValue && !item.BB.HasValue ? (uint?)null : sum.BB.GetValueOrDefault() + item.BB.GetValueOrDefault();
      return sum;
    } );

Первый из них занял около 12 мс, в списке всего два элемента. Утонченный второй забег примерно в 9 мс, и в списке всего два элемента. Я перехожу со второй записью, потому что она более эффективна, а также выглядит более чистым и легче следовать. Спасибо Юхарру за толкание в правильном направлении!

0

Вы должны написать собственный метод расширения SumOrNull, который может вернуть ваш "uint?". в описанной вами ситуации (null + null = null). Существующий метод Linq Sum возвращает только число, которое не является нулевым.

Документация Linq Sum

  • 0
    Это неправда. В той же ссылке, на которую вы ссылаетесь, она показывает множество перегрузок для Sum, которые возвращают обнуляемый тип данных. Метод Enumerable.Sum (IEnumerable <Nullable <Int32 >>) В нем даже говорится, что «Этот метод возвращает ноль, если источник не содержит элементов».
  • 0
    LOL, он также заявляет: «Результат не включает значения, которые являются нулевыми». Я не знаю, почему тогда возвращаемый тип будет обнуляемым, если он никогда не намеревается вернуть ноль.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню