【问题标题】:return 0 for items count in linq statementlinq 语句中的项目计数返回 0
【发布时间】:2020-02-12 15:58:32
【问题描述】:

假设我有以下 EF6 Linq 语句,它计算 2 个表的项目数(表 1 有 10 个项目,表 2 没有项目):

var q = db.Table1.GroupBy(g => "Table1").Select(g => new { Name = g.Key, EntryCount = g.Count() })
          .Union(db.Table2.GroupBy(g => "Table2").Select(g => new { Name = g.Key, EntryCount = g.Count() }));

var r = q.ToList();

预期的结果应该类似于

Name    | EntryCount
---------------------
Table1  |   10
Table2  |    0

但是,因为 Table2 没有任何项目,所以它没有出现在最终结果中,我得到以下结果:

Name    | EntryCount
---------------------
Table1  |   10

如何确保表 2 始终出现在最终列表中,即使它为空且没有任何记录?

为了让您了解我为什么要执行此 Linq 语句的背景,我将以下 TSQL 语句转换为 linq 查询:

CREATE FUNCTION [dbo].[ufnGetLookups] ()
RETURNS
@lookupsWithItemCounts TABLE 
(
    [Name] VARCHAR(100),
    [EntryCount] INT
)

AS 
BEGIN
    INSERT INTO @lookupsWithItemCounts([Name],[EntryCount])
     VALUES
               ('Table1', (SELECT COUNT(*) FROM Table1)),
               ('Table2', (SELECT COUNT(*) FROM Table2)),
               ('Table3', (SELECT COUNT(*) FROM Table3))

    RETURN;
END

对于这个 linq 语句在一次数据库行程中运行而不是多次运行也非常重要。

【问题讨论】:

  • 为什么不只做简单的计数?即db.Table1.Count()db.Table2.Count(),然后担心捆绑?这似乎更有可能给您想要的结果;实际上,任何超过零行的分组都是零组,即使分组是“所有行”分组
  • 你为什么使用联合?好像你可以做这样的事情:var r = new[] { new { Name = "Table1", EntryCount = db.Table1.Count() }, new { Name = "Table2", EntryCount = db.Table2.Count() } };
  • 我正在尝试提出与我粘贴的 TSQL 语句等效的 linq。
  • @MarcGravell,我希望 linq 查询一次从数据库中获取结果并替换我粘贴的 TSQL 函数。如果这在单个 linq 查询中是不可能的,那么我将使用本机 sql 函数。
  • 如果在 Table2 的 Select 之前添加 DefaultIfEmpty() 会怎样?

标签: c# entity-framework linq linq-to-sql linq-to-entities


【解决方案1】:

如果你有一个表格列表,那么表格列表可以用于union 一行EntryCount = 0,然后在最终结果集上一个GroupBy 名称和Sum EntryCount 将提供所需的结果.

//List of tables
var tableList = new string[] { "Table1", "Table2" }; 

var res = db.Table1
    .GroupBy(t1 => "Table1")
    .Select(gt1 => new { Name = gt1.Key, EntryCount = gt1.Count()})
    .Union(db.Table2
           .GroupBy(t2 => "Table2")
           .Select(gt2 => new { Name = gt2.Key, EntryCount = gt2.Count()})
          )
    .Union(tableList
           .GroupBy(s => s)
           .Select(gs => new { Name = gs.Key, EntryCount = 0 })
          )
    .GroupBy(gg => gg.Name)
    .Select(fg => new {Name = fg.Key, EntryCount=fg.Select(ee => ee.EntryCount).Sum()})
       .ToList();

结果将具有列表中所有表的 EntryCount。

【讨论】:

  • 当我尝试这种变化时,EntryCount = 0 被 LINQ to SQL 翻译器拒绝。
  • AsEnumerable 放在那里似乎效果很好,尤其是在 EF 3.0 中,它会生成一个非常简单的 SQL 查询。有趣的是,最初的简单查询在 EF 3.0 中生成了一个 0 计数记录,因此不需要解决它。
  • Table2 查询零行:var ans = Table1 .GroupBy(t1 => "Table1") .Select(gt1 => new { Name = gt1.Key, EntryCount = gt1.Count()}) .Union(Table2 .GroupBy(t2 => "Table2") .Select(gt2 => new { Name = gt2.Key, EntryCount = gt2.Count()}) );针对 MS SQL Server 运行。生成的 SQL:SELECT N'Users' AS [Name], COUNT(*) AS [EntryCount] FROM [user] AS [u] UNION SELECT N'TblLogs' AS [Name], COUNT(*) AS [EntryCount] FROM [tblLog] AS [t] GO.
  • 我在回答中添加了注释。
【解决方案2】:

您可以通过创建一个包含单行的默认表来做到这一点。

var ans = Table1.GroupBy(u => "Table1")
                .Select(ug => new { Name = ug.Key, EntryCount = ug.Count() })
                .Union(Table2.GroupBy(l => "Table2")
                              .Select(lg => new { Name = lg.Key, EntryCount = lg.Count() })
                              .Union(OneRowTable.GroupBy(u2 => 1)
                                                .Select(u2g => new { Name = "Table2", EntryCount = u2g.Count()-1 }) )
                              .OrderByDescending(cg => cg.EntryCount)
                              .Take(1)
               );

这是由 LINQ to SQL 在一次往返数据库中评估的。我无法轻松地使用 LINQ to EF 进行测试。

请注意,在 EF Core 3.0 中,原始查询的转换方式是为任何空表返回 0 行。

【讨论】:

    猜你喜欢
    • 2015-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多