SQL to LINQ with multiple join, count and left joinSQL to LINQ with multiple join, count and left join - Solution Checker - solutionschecker.com - Find the solution for any programming question. We as a solution checker will focus on finding the fastest possible solution for developers. Main topics like coding, learning.
I wrote this SQL request with multiple JOIN (including a LEFT JOIN).
It gives me the expected result.
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
However, 'Id like the Linq equivalent of this request, to put it my application's Data Access layer.
I tried something like :
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)
};
I tried to adapt this SO question but I can't figure it out. I'm lost with the Count inside the groupped sub-request.
Can anyone explain me where i'm wrong ?
Pro tip : Bonus point if someone can write the equivalent with a lambda expression
Solution 1
For translating SQL to LINQ query comprehension:
- Translate subselects as separately declared variables unless they reference columns outside the subselect, in which case use parentheses to create a sub-query.
- Translate each clause in LINQ clause order, translating monadic and aggregate operators (
DISTINCT,TOP,MIN,MAXetc) into functions applied to the whole LINQ query. - Use table aliases as range variables. Use column aliases as anonymous type field names.
- Use anonymous types (
new {...}) for multiple columns (e.g. ingroupby). - Use
First().fieldto get non-key values from thegroupbyaggregate range variable. - When using EF or EF Core, translate
JOINclauses into navigation properties possibly using.Include(). - Otherwise
JOINclauses that are multipleANDed equality tests between the two tables should be translated into anonymous objects on each side ofequals. JOINconditions that aren't all equality tests withANDmust be handled usingwhereclauses outside the join, or with cross product (from...from...) and thenwhere. If you are doingLEFT JOIN, add a lambdaWhereclause between the join range variable and theDefaultIfEmpty()call.LEFT JOINis simulated by usingintojoinvariable and doing anotherfromthe joinvariable followed by.DefaultIfEmpty().- Translate multiple tables in the
FROMclause into multiplefromclauses. - Translate
FROM T1 CROSS APPLY T2into twofromclauses, one forT1and one forT2. - Translate
FROM T1 OUTER APPLY T2into twofromclauses, one forT1and one forT2, but add.DefaultIfEmpty()toT2. - Replace
COALESCEwith the conditional operator (?:)and anulltest. - Translate
INto.Contains()andNOT INto!...Contains(), using literal arrays or array variables for constant lists. - Translate x
BETWEENlowANDhigh to low<=x&&x<=high. - Translate
CASE,ISNULLandIIFto the ternary conditional operator?:. SELECT *must be replaced with select range_variable or for joins, an anonymous object containing all the range variables.SELECTcolumns must be replaced withselect new {...}creating an anonymous object with all the desired fields or expressions.- References to computed
SELECTcolumns can be translated by repeating the expression or by usingletto name the expression before its first use. - Proper
FULL OUTER JOINmust be handled with an extension method. - Translate
UNIONtoConcatunless both sub-queries areDISTINCT, in which case you can translate toUnionand leave off theDISTINCT. - Translate aggregate queries that have no
GROUP BYusing a singletonGroupBy: add.GroupBy(r => 1)and then translate the aggregate functions in theSelect. - Date Math and some other canonical functions can be accessed using
EF.Functionsto get an instance of theDbFunctionsclass (EF Core),EntityFunctionsclass (EF < 6) orDbFunctionsto access the static methods (EntityFramework 6.x). - Translate SQL
LIKEexpressions using (EF Core >= 2)EF.Functions.Like(column, pattern)or (EF 6.x)DbFunctions.Like(column, pattern).
Applying these rules to your SQL query, you get:
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();
The lambda translation is tricky, but the conversion of LEFT JOIN to GroupJoin...SelectMany is what is needed:
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 });
