【问题标题】:SQL to LINQ with multiple join, count and left joinSQL to LINQ 具有多重连接、计数和左连接
【发布时间】:2018-08-21 01:25:24
【问题描述】:

我用多个 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 表达式编写等效项,则加分

【问题讨论】:

  • 正确答案是——不要。 LINQ 不是 SQL 的替代品,它是一种基于 ORM 的语言。如果您需要在 LINQ 中执行 JOIN,则意味着您的 DbContext 缺少正确的关系。而不是加入你应该添加缺失的关系
  • 而且 DbSet 不是表,它们是实体的存储库,彼此之间具有配置的关系,可以根据需要动态生成 JOIN。 DbContext 既不是数据库的连接也不是模型,它实际上是一个工作单元。

标签: sql linq join left-join sql-to-linq-conversion


【解决方案1】:

用于将 SQL 转换为 LINQ 查询理解:

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

【讨论】:

  • 这是来自stackoverflow.com/a/48348721/2687153stackoverflow.com/a/48895078/2687153 的不错的复制/粘贴,但我已经找到了这些帖子,我希望得到更个性化的答案。因为我担心我无法适应我的问题......
  • 是的,我也写了这些答案 :) - 你有没有尝试应用这个食谱?
  • 我尝试了您的 Linq 查询,但我遇到了 在值类型 System.Int32 中强制转换失败,因为具体化的值是 Null。结果类型或请求的泛型参数必须使用可空类型(粗略翻译)是不是因为左连接产生了一些空值? subrq 很好,但 ansq 会产生此错误。这似乎是失败的 group new 子句。会不会是r.cnt
  • @user2687153 抱歉,这是我测试的结果!我通常使用 LINQ to Objects 在 LINQPad 中进行测试,它不会像 LINQ to SQL 那样自动处理空值,所以我必须输入 ?. 但有时我在粘贴到 SO 时忘记将它们取出。我修好了它。我的猜测是最终选择可能需要在 lambda 版本中使用相同的 (int?) 强制转换。
  • 最好添加一个强烈提倡使用导航属性而不是连接的部分。这种“配方”可能会导致养成自动翻译连接字面意思而不是利用 ORM 的能力的坏习惯。
猜你喜欢
  • 1970-01-01
  • 2010-11-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-21
  • 2013-06-13
  • 1970-01-01
相关资源
最近更新 更多