【问题标题】:Attempted to divide by zero Linq试图除以零 Linq
【发布时间】:2018-05-28 19:25:50
【问题描述】:

我正在尝试根据列的总和以及有多少列来计算平均值。

每部电影都可以有一个电影评分,我试图将这些电影评分相加,然后将它们除以评分数量,以获得一部电影的平均评分。

(reviews.Where(w => w.MovieID == m.MovieID).Sum(o => o.MovieRating)) / reviews.Where(z => z.MovieID == m.MovieID).Count();

但问题是如果计数为 0。我该怎么办?

【问题讨论】:

  • 如果没有匹配的ID,期望的结果是什么?
  • 如果没有评论,只需将其设置为 null。
  • 我也看到了多个枚举
  • Linq 已经有一个Average 方法
  • 你将如何区分没有评论的电影和有一堆 0 评论的电影?还是允许的最低评论值大于零?

标签: c# linq


【解决方案1】:

使用Average函数内置的LINQ怎么样?

reviews
    .Where(z => z.MovieID == m.MovieID)
    .Select(z => z.MovieRating)
    .DefaultIfEmpty(0)
    .Average()

要让查询在服务器上完全运行(我猜,取决于您的数据库服务器):

reviews
    .Where(z => z.MovieID == m.MovieID)
    .Average(z => (decimal?)z.MovieRating) ?? 0

【讨论】:

  • 警告:Microsoft.EntityFrameworkCore.Query[20500] 无法翻译 LINQ 表达式“DefaultIfEmpty(__p_0)”,将在本地计算。
  • 警告:Microsoft.EntityFrameworkCore.Query[20500] LINQ 表达式“Average()”无法翻译,将在本地进行计算。
  • ?? null ?认真的吗?
  • @bonmelding ?? nullCOALESCE 添加到生成的 SQL 中,经过反思,在这种情况下不需要,我已将其删除
【解决方案2】:

你可以使用

var ratingList = reviews
   .Where(z => z.MovieID == m.MovieID)
   .Select(z => z.MovieRating)
   .ToList();
if(ratingList.Count > 0)
{
   double result = ratingList.Sum() / ratingList.Count;
}

【讨论】:

  • 如果reviews 是 EF 上的表,是否可以在一次往返中完成,完全是 SQL 端(无需下载评论)?
  • @xanatos:这是一次往返,只有一个查询,它被加载到一个列表中。其余的在内存中完成。
  • 您可以在ToList 之前添加.Select(m => m.MovieRating)
  • @Tim 如果评论是一大行,您正在下载整行...如果有 1.000.000 行,您正在下载 1,000,000 行...我是什么人,我可以@ EF 中的 987654325@? (这只是出于好奇,可能在 OP 的问题之外)
  • 这也是我想到的一个解决方案(不过你打败了我)但感觉我们应该做些什么来优化它。
【解决方案3】:
double? score = reviews
   .Where(w => w.MovieID == m.MovieID)
   .Average(w => (double?) w.MovieRating)
   // ?? 0   // optional: pick a default when Average returns null
   ;

【讨论】:

  • @CamiloTerevinto 这个答案的解释与两个投票的答案完全相同。
【解决方案4】:

如果条件如下,只需使用内联:

var avr = (from a1 in reviews
           where a1.MovieID == m.MovieID
           group a1 by a1.MovieID into g
           select new
           {
               avr = g.count() == 0 ? 0 : g.sum(r => r.MovieRating) / g.count()
           }).FirstOrDefault().avr;

【讨论】:

    【解决方案5】:

    不确定是否特别强调使用 LINQ,但我更愿意将其全部放在 for/foreach 循环中,而不是多个 LINQ。否则下面会起作用吗?

    var matchingReviews = reviews.Where(w => w.MovieID == m.MovieID);
    int matchingReviewCount = matchingReviews.Count();
    if (matchingReviewCount  > 0)
    {
        matchingReviews.Sum(o => o.MovieRating) / matchingReviewCount;
    }
    else
    {
        //return default value?
    }
    

    【讨论】:

      【解决方案6】:

      我想做一个查询(因为也许我们正在处理一个数据库,并且每个查询都有一个成本“仅仅因为它是一个查询”)并在 SQL 端进行所有计算,这样我就会收到一个单行(因为如果有 1000 条评论,我真的不需要下载所有评论...我想要他们的 MovieRating 和他们的计数的总和)。

      // Based on https://stackoverflow.com/a/45144198/613130
      var result = reviews
          .Where(x => x.MovieID == m.MovieID)
          .GroupBy(x => 1)
          .Select(x => ((double?)x.Sum(y => y.MovieRating)) / x.Count())
          .FirstOrDefault() ?? -1.0;
      

      如果MovieID 没有评论,result 将是 -1.0。请注意强制转换的使用,否则我们可能会进行整数除法(如果MovieRating 是整数)。

      请注意,如果没有找到一行,则不会有任何划分(因为不会有任何Group

      SQL 数据库上的等效查询应该是

      SELECT CAST(SUM(x.MovieRating) AS FLOAT) / COUNT(*) 
          FROM Reviews x 
          WHERE x.MovieID = @MovieID 
          GROUP BY 1
      

      但实际上,EF 以更复杂的方式对其进行了翻译 :-)(在 EF 6.2.0 上测试):

      SELECT 
          CAST( [GroupBy1].[A1] AS float) /  CAST( [GroupBy1].[A2] AS float) AS [C1]
      FROM ( SELECT 
          [Filter1].[K1] AS [K1], 
          SUM([Filter1].[A1]) AS [A1], 
          COUNT([Filter1].[A2]) AS [A2]
          FROM ( SELECT 
              1 AS [K1], 
              [Extent1].[MovieRating] AS [A1], 
              1 AS [A2]
              FROM #Reviews AS [Extent1]
              WHERE [Extent1].[MovieId] = @p__linq__0
          )  AS [Filter1]
          GROUP BY [K1]
      )  AS [GroupBy1]
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-06
        • 1970-01-01
        • 2010-10-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多