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
, MAX
etc) 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. in groupby
).
- Use
First().field
to get non-key values from the groupby
aggregate range variable.
- When using EF or EF Core, translate
JOIN
clauses into navigation properties possibly using .Include()
.
- Otherwise
JOIN
clauses that are multiple AND
ed equality tests between the two tables should be translated into anonymous objects on each side of equals
.
JOIN
conditions that aren't all equality tests with AND
must be handled using where
clauses outside the join, or with cross product (from
... from
...) and then where
. If you are doing LEFT JOIN
, add a lambda Where
clause between the join range variable and the DefaultIfEmpty()
call.
LEFT JOIN
is simulated by using into
joinvariable and doing another from
the joinvariable followed by .DefaultIfEmpty()
.
- Translate multiple tables in the
FROM
clause into multiple from
clauses.
- Translate
FROM T1 CROSS APPLY T2
into two from
clauses, one for T1
and one for T2
.
- Translate
FROM T1 OUTER APPLY T2
into two from
clauses, one for T1
and one for T2
, but add .DefaultIfEmpty()
to T2
.
- Replace
COALESCE
with the conditional operator (?:
)and a null
test.
- Translate
IN
to .Contains()
and NOT IN
to !
...Contains()
, using literal arrays or array variables for constant lists.
- Translate x
BETWEEN
low AND
high to low <=
x &&
x <=
high.
- Translate
CASE
, ISNULL
and IIF
to the ternary conditional operator ?:
.
SELECT *
must be replaced with select range_variable or for joins, an anonymous object containing all the range variables.
SELECT
columns must be replaced with select new {
... }
creating an anonymous object with all the desired fields or expressions.
- References to computed
SELECT
columns can be translated by repeating the expression or by using let
to name the expression before its first use.
- Proper
FULL OUTER JOIN
must be handled with an extension method.
- Translate
UNION
to Concat
unless both sub-queries are DISTINCT
, in which case you can translate to Union
and leave off the DISTINCT
.
- Translate aggregate queries that have no
GROUP BY
using a singleton GroupBy
: add .GroupBy(r => 1)
and then translate the aggregate functions in the Select
.
- Date Math and some other canonical functions can be accessed using
EF.Functions
to get an instance of the DbFunctions
class (EF Core), EntityFunctions
class (EF < 6) or DbFunctions
to access the static methods (EntityFramework 6.x).
- Translate SQL
LIKE
expressions 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 });