【问题标题】:Convert Sql query to C# linq entity framework将 Sql 查询转换为 C# linq 实体框架
【发布时间】:2021-04-22 17:21:39
【问题描述】:

我需要将以下 sql 查询转换为带有实体框架的 C# linq:

SELECT        AccNo, SUM(Debit) - SUM(Credit) AS Balance FROM  TrxAffair
WHERE        (TrxDate > '2021-01-01') GROUP BY AccNo

我尝试了以下方法,但我得到::'转换为值类型'System.Decimal'失败,因为物化值为空。结果类型的泛型参数或查询必须使用可为空的类型。'

部分无效 OnGetPreviousBalance(小数项);

public decimal GetPreviousBalance(DateTime trxDate)
{
    var items = context.TrxAffairs
                       .AsQueryable()
                       .Where(i => i.TrxDate < trxDate)
                       .GroupBy(o => o.AccNo)
                       .Select(s => s.Sum(i=>(i.Credit-i.Debit)));

    OnGetPreviousBalance(items.Sum(s=>s.Value));

    return items.Sum(s => s.Value);
}

【问题讨论】:

  • 在 SQL 中,您有总和的差异,而不是差异的总和。至于演员表,似乎抱怨其中一个类属性应该可以为空。您没有发布课程或表格架构,因此只能猜测。但是,如果其中一个为 null,i.Credit -i.Debit 也会导致 SQL 出现问题
  • 在 SQL 和 LINQ 中比较 TrxDate 似乎是相反的&gt; vs &lt;
  • 在 SQL 中,如果您使用 SUM(Credit - Debit),如果其中一个是 null,您将得到一个 NULL。而不是 +100 或 -100,您将一无所获。使用s.Sum(i.Credit) - s.Sum(i.Debit) 生成相同的SQL查询
  • AND 添加ToList(),以避免一遍又一遍地枚举结果。并将总计存储在变量中以避免计算两次

标签: c#


【解决方案1】:

LINQ 查询中有几个问题。

  • 聚合计算差的总和,而不是总和的差。如果任一字段为 NULL,则差值也将为 NULL。
  • 日期检查已反转。
  • 结果中缺少该帐户。

相当于:

var items = context.TrxAffairs
                   .Where(i => i.TrxDate > trxDate)
                   .GroupBy(o => o.AccNo)
                   .Select(s => new {
                       o.AccNo, 
                       Balanse=s.Sum(i=>i.Credit)-s.Sum(i=>i.Debit)
                   })
                   .ToList();

之后,只计算一次总计并重复使用它:

var grandTotal=items.Sum(s=>s.Value);
OnGetPreviousBalance(grandTotal);

return grandTotal;

您可以将 NULL 替换为 0。在 SQL 中,您将使用 ISNULL(Credit,0)。在 C#/LINQ 中,可以使用i.Credit ?? 0:

var items = context.TrxAffairs
                   .Where(i => i.TrxDate > trxDate)
                   .GroupBy(o => o.AccNo)
                   .Select(s => new {
                       AccNo=s.Key, 
                       Balanse=s.Sum(i=>(i.Credit??0) - (i.Debit??0))
                   })
                   .ToList();

如果您只想要总计,则没有理由分组:

var grandTotal = context.TrxAffairs
                   .Where(i => i.TrxDate > trxDate)
                   .Sum(i=>(i.Credit??0) - (i.Debit??0));

不要急于用 LINQ 替换 SQL。

LINQ 不是 SQL。 SQL 在报告和分析方面要强大得多。服务器比任何客户端都强大,并且可以在过滤数据的同时聚合数据。

如果您使用ROLLUP,您可以在 SQL 中获得总计和总计。这不能在 LINQ 中完成。在这种情况下,以下查询:

SELECT        AccNo, SUM(Debit) - SUM(Credit) AS Balance 
FROM  TrxAffair
WHERE        (TrxDate > '2021-01-01') 
GROUP BY AccNo
WITH ROLLUP

SELECT        AccNo, SUM(Debit) - SUM(Credit) AS Balance 
FROM  TrxAffair
WHERE        (TrxDate > '2021-01-01') 
GROUP BY ROLLUP (AccNo)

将返回总计作为最后一列,并带有NULL AccNo。当存在大量数据或多个分组级别时,这可能会产生重大影响。

其他子句,如CUBEGROUPING SETS 可用于生成多个组合的中间总计。在客户端上执行此操作将非常复杂(且成本高昂)。

例如,您可以在单个查询中按国家/产品产品/国家/地区汇总销售额:

SELECT Country, Product, SUM(Sales) AS TotalSales
FROM Sales
GROUP BY GROUPING SETS ( (Country, Product), (Product, Country) );

将为两种不同的组合生成小计。如果没有这个,您将不得不编写两个不同的查询。

【讨论】:

  • o.AccNo --> s.Key
  • @Magnus 哎呀,已修复
  • @Charlieface 哎呀。当我意识到在这两种情况下使用CountryRegion 并不是最好的例子时,我开始复制文档示例。然后我忘了清理剩下的查询
【解决方案2】:

尽管在 cmets 中声明了编辑,但请确保 CreditDebit 值不会与空值一起传播。

如果 CreditDebit 值可以为空,GetValueOrDefault() 可能会有所帮助

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-16
    相关资源
    最近更新 更多