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