2014-12-12 4 views
1

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

Проблема, с которой я сталкиваюсь, заключается в том, что функция суммы 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; 
} 

ответ

1

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

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

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

+0

Позвольте мне попробовать это и вернуться к вам. –

+0

Прохладный, это сработало. Благодаря! –

0

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

Linq Sum documentation

+0

Это неправда. В той же самой ссылке, на которую вы ссылаетесь, отображается множество перегрузок для Sum, которые возвращают тип данных с нулевыми значениями. [Enumerable.Sum Method (IEnumerable >)] (http://msdn.microsoft.com/en-us/library/vstudio/bb156065 (v = vs.100) .aspx) Он даже утверждает, что «This метод возвращает ноль, если источник не содержит элементов. " –

+0

LOL, он также заявляет: «Результат не включает значения, которые являются нулевыми». Я не знаю, почему тогда возвращаемый тип был бы нулевым, если он никогда не намеревается вернуть нуль. –

+0

@ELMOJO Я предположил, что он возвращает значение nullable, потому что он возвращает null для пустой коллекции, но в этом случае также возвращает 0. Я предполагаю, что они просто хотели сохранить тип возврата так же, как и тип, который суммируется. – juharr

0

На основании ответа Juharr, я первый попытался это:

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; 
    }); 

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