Read section 7.16.2 in the C# 4 specification. All the rules are there.
Let's go through it. You have:
from r in this.bdd.Rights
join f in this.bdd.Folders on r.RightFolderId equals f.FolderId
join rs in this.bdd.RightSpecs on r.RightSpecId equals rs.SpecIdRight
where r.RightUserId == userId
where rs.SpecRead == true
where rs.SpecWrite == true
select f;
The spec says
A query expression with a join clause without an into followed by something other than a select clause
from x1 in e1
join x2 in e2 on k1 equals k2
...
is translated into
from * in ( e1 )
. Join(e2 , x1 => k1 , x2 => k2 , ( x1 , x2 ) => new { x1 , x2 })
...
OK, so we start by translating your query into
from * in
(this.bdd.Rights)
.Join(
this.bdd.Folders,
r=>r.RightFolderId,
f=>f.FolderId,
(*, f)=> new {r, f})
join rs in this.bdd.RightSpecs on r.RightSpecId equals rs.SpecIdRight
where r.RightUserId == userId
where rs.SpecRead == true
where rs.SpecWrite == true
select f;
Now the same rules applies again; we have a query expression with a join clause without an into followed by something other than a select. So that becomes:
from ** in
((this.bdd.Rights)
.Join(
this.bdd.Folders,
r=>r.RightFolderId,
f=>f.FolderId,
(r, f)=> new {r, f}))
.Join(
this.bdd.RightSpecs,
*=>r.RightSpecId,
rs=>rs.SpecIdRight,
(*, rs)=> new {*, rs})
where r.RightUserId == userId
where rs.SpecRead == true
where rs.SpecWrite == true
select f;
What's the next rule that applies? Consult the spec:
A query expression with a where clause
from x in e
where f
...
is translated into
from x in ( e ) . Where ( x => f )
...
OK, so we apply that transformation three times and get
from ** in
(((((this.bdd.Rights)
.Join(
this.bdd.Folders,
r=>r.RightFolderId,
f=>f.FolderId,
(r, f)=> new {r, f}))
.Join(
this.bdd.RightSpecs,
*=>r.RightSpecId,
rs=>rs.SpecIdRight,
(*, rs)=> new {*, rs}))
.Where(**=>r.RightUserId == userId ))
.Where(**=>rs.SpecRead == true))
.Where(**=>rs.SpecWrite == true)
select f;
Now what? Consult the specification:
from x in e select v
is translated into
( e ) . Select ( x => v )
So the code above is translated into
((((((this.bdd.Rights)
.Join(
this.bdd.Folders,
r=>r.RightFolderId,
f=>f.FolderId,
(r, f)=> new {r, f}))
.Join(
this.bdd.RightSpecs,
*=>r.RightSpecId,
rs=>rs.SpecIdRight,
(*, rs)=> new {*, rs}))
.Where(**=>r.RightUserId == userId ))
.Where(**=>rs.SpecRead == true))
.Where(**=>rs.SpecWrite == true))
.Select(**=>f);
where *
and **
are transparent identifiers. So this is then further transformed into
((((((this.bdd.Rights)
.Join(
this.bdd.Folders,
r=>r.RightFolderId,
f=>f.FolderId,
(r, f)=> new {r, f}))
.Join(
this.bdd.RightSpecs,
t1=>t1.r.RightSpecId,
rs=>rs.SpecIdRight,
(t1, rs)=> new {t1, rs}))
.Where(t2=>t2.t1.r.RightUserId == userId ))
.Where(t2=>t2.rs.SpecRead == true))
.Where(t2=>t2.rs.SpecWrite == true))
.Select(t2=>t2.t1.f);
We introduced a whole lot of unnecessary parenthesis in there. We could take them out and say that this is equivalent to
this.bdd.Rights
.Join(
this.bdd.Folders,
r=>r.RightFolderId,
f=>f.FolderId,
(r, f)=> new {r, f})
.Join(
this.bdd.RightSpecs,
t1=>t1.r.RightSpecId,
rs=>rs.SpecIdRight,
(t1, rs)=> new {t1, rs})
.Where(t2=>t2.t1.r.RightUserId == userId )
.Where(t2=>t2.rs.SpecRead == true)
.Where(t2=>t2.rs.SpecWrite == true)
.Select(t2=>t2.t1.f);
Easy peasy. It's just a straightforward syntactic rewrite, except for a bit of analysis of the transparent identifiers.