SQL 到具有多个连接、计数和左连接的 LINQ

SQL to LINQ with multiple join, count and left join

我用多个 JOIN(包括一个 LEFT JOIN)写了这个 SQL 请求。
它给了我预期的结果

SELECT DISTINCT c.Id, 
       c.Title, 
       COUNT(v.Id) AS 'Nb_V2',
       COUNT(DISTINCT v.IdUser) AS 'Nb_V1',
       r.cnt AS 'Nb_R'
FROM TABLE_C c
JOIN TABLE_V v on c.Id = v.Id
LEFT JOIN ( 
    SELECT Id, COUNT(*)  AS cnt 
    FROM TABLE_R 
    GROUP BY Id
) r ON c.Id = r.Id
WHERE c.IdUser = '1234'
GROUP BY c.Id, c.Title, r.cnt

但是,'我喜欢这个请求的 Linq 等价物,把它放在我的应用程序的数据访问层。

我试过类似的东西:

var qResult = from c in dbContext.TABLE_C
              join v in dbContext.TABLE_V on c.IdC equals v.IdC
              join r in dbContext.TABLE_R on v.IdC equals r.IdC into temp
              from x in temp.DefaultIfEmpty()
              group x by new { c.IdC, c.Title /*miss something ?*/} into grouped
              select new
              {
                  IdC = grouped.Key.IdC,          --good result
                  Title = grouped.Key.Title,      --good result
                  NbR = grouped.Distinct().Count(t => t.IdC > 0), --good, but "t.Id > 0" seems weird
                  Count = --I'm lost. No idea how to get my COUNT(...) properties (Nb_V1 and Nb_V2)
              };

我试着适应 this SO question 但我想不通。我迷失了分组子请求中的 Count
谁能解释我哪里错了?

专业提示:如果有人可以使用 lambda 表达式编写等价物,则可加分

将 SQL 转换为 LINQ 查询理解:

  1. 将 subselects 翻译为单独声明的变量除非它们引用 subselect 之外的列,在这种情况下使用括号创建 sub-query.
  2. 翻译 LINQ 子句顺序中的每个子句,将单子和聚合运算符(DISTINCTTOPMINMAX 等)翻译成应用于整体的函数LINQ 查询。
  3. 使用table别名作为范围变量。使用列别名作为匿名类型字段名称。
  4. 对多列(例如 groupby)使用匿名类型 (new { ... })。
  5. 使用 First().fieldgroupby 聚合范围变量中获取 non-key 值。
  6. 使用 EF 或 EF Core 时,将 JOIN 子句翻译成可能使用 .Include() 的导航属性。
  7. 否则 JOIN 两个 table 之间的多个 ANDed 相等性测试的子句应该被翻译成 equals 两侧的匿名对象。
  8. JOIN 并非所有与 AND 相等性测试的条件必须使用连接外的 where 子句或叉积(from .. . from ...) 然后 where。如果您正在执行 LEFT JOIN,请在连接范围变量和 DefaultIfEmpty() 调用之间添加一个 lambda Where 子句。
  9. LEFT JOIN 是通过使用 into joinvariable 并执行另一个 from joinvariable 来模拟的其次是 .DefaultIfEmpty().
  10. FROM 子句中的多个 table 翻译成多个 from 子句。
  11. FROM T1 CROSS APPLY T2 翻译成两个 from 从句,一个用于 T1,一个用于 T2
  12. FROM T1 OUTER APPLY T2翻译成两个from子句,一个用于T1,一个用于T2,但将.DefaultIfEmpty()添加到T2
  13. 用条件运算符 (?:) 和 null 测试替换 COALESCE
  14. IN 翻译成 .Contains() 并将 NOT IN 翻译成 !...Contains(),使用常量列表的文字数组或数组变量。
  15. xBETWEENlowANDhigh翻译成 <= x && x <= .
  16. CASEISNULLIIF翻译成三元条件运算符?:
  17. SELECT * 必须替换为 select range_variable 或对于连接,包含所有范围变量的匿名对象。
  18. SELECT 列必须替换为 select new { ... } 创建一个包含所有所需字段或表达式的匿名对象。
  19. 可以通过重复表达式或在首次使用前使用 let 命名表达式来翻译对计算 SELECT 列的引用。
  20. 正确的FULL OUTER JOIN必须用extension method处理。
  21. UNION 翻译成 Concat 除非 sub-queries 都是 DISTINCT,在这种情况下你可以翻译成 Union 并省略 [=12] =].
  22. 使用单例 GroupBy 翻译没有 GROUP BY 的聚合查询:添加 .GroupBy(r => 1) 然后翻译 Select.
  23. 中的聚合函数
  24. 可以使用 EF.Functions 访问日期数学和其他一些 canonical 函数,以获取 DbFunctions class(EF Core)的实例,EntityFunctions class (EF < 6) 或 DbFunctions 访问静态方法 (EntityFramework 6.x).
  25. 使用 (EF Core >= 2) EF.Functions.Like(column, pattern) 或 (EF 6.x) DbFunctions.Like(column, pattern).
  26. 翻译 SQL LIKE 表达式

将这些规则应用于您的 SQL 查询,您将得到:

var subrq = from r in Table_R
            group r by r.Id into rg
            select new { Id = rg.Key, cnt = rg.Count() };

var ansq = (from c in Table_C
            join v in Table_V on c.Id equals v.Id
            join r in subrq on c.Id equals r.Id into rj
            from r in rj.DefaultIfEmpty()
            where c.IdUser == "1234"
            group new { c, v, r } by new { c.Id, c.Title, r.cnt } into cvrg
            select new {
                cvrg.Key.Title,
                Nb_V2 = cvrg.Count(),
                Nb_V1 = cvrg.Select(cvr => cvr.v.IdUser).Distinct().Count(),
                Nb_R = (int?)cvrg.Key.cnt
            }).Distinct();

lambda 转换很棘手,但需要将 LEFT JOIN 转换为 GroupJoin...SelectMany

var subr2 = Table_R.GroupBy(r => r.Id).Select(rg => new { Id = rg.Key, cnt = rg.Count() });
var ans2 = Table_C.Where(c => c.IdUser == "1234")
                  .Join(Table_V, c => c.Id, v => v.Id, (c, v) => new { c, v })
                  .GroupJoin(subr, cv => cv.c.Id, r => r.Id, (cv, rj) => new { cv.c, cv.v, rj })
                  .SelectMany(cvrj => cvrj.rj.DefaultIfEmpty(), (cvrj, r) => new { cvrj.c, cvrj.v, r })
                  .GroupBy(cvr => new { cvr.c.Id, cvr.c.Title, cvr.r.cnt })
                  .Select(cvrg => new { cvrg.Key.Title, Nb_V2 = cvrg.Count(), Nb_V1 = cvrg.Select(cvr => cvr.v.IdUser).Distinct().Count(), Nb_R = (int?)cvrg.Key.cnt });