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 single 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 with- ANDmust be handled using- whereclauses outside the join, or with cross product (- from...- from...) and then- where. If you are doing- LEFT JOIN, add a lambda- Whereclause between the join range variable and the- DefaultIfEmpty()call.
- LEFT JOINis simulated by using- intojoinvariable and doing another- fromthe 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 with- select 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 with multiple result columns that have no GROUP BYusing a singletonGroupBy: add.GroupBy(r => 1)(orgroup...by 1 into g) and then translate the aggregate functions in theSelect new { }.
- 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 });