Multiple "from" links and variable visibility
I'm trying to figure out why this linq won't compile ( fundInvoices don't show up):
Dictionary<Fund, IEnumerable<Invoice>> paidfundInvoices;
...
from fundInvoices in paidfundInvoices
from p in fundInvoices.Value
group p by p.VendorId into ps
select new Payment
{
FundId = fundInvoices.Key.FundId, // ERROR here
Value = ps.Sum(p => p.Amount)
}
So, I kept changing this to use an anonymous type, and the fundInvoices are magically visible here:
from fundInvoices in paidfundInvoices
select new
{
Fund = fundInvoices.Key,
Payments = from p in fundInvoices.Value
group p by p.VendorId into ps
select new Payment
{
FundId = fundInvoices.Key.FundId, // NO ERROR
Value = ps.Sum(p => p.Amount)
}
};
But this anonymous type seems to be redundant, I don't use it. I just need a flat list of Payment objects . However, my code only compiles this way ...
source to share
I am trying to understand why this linq is not compiling
The key to understanding is reading the section of the spec on how queries are dropped into regular code.
Start with your request:
from fundInvoices in paidfundInvoices
from p in fundInvoices.Value
group p by p.VendorId into ps
select new Payment {
FundId = fundInvoices.Key.FundId, // ERROR here
Value = ps.Sum(p => p.Amount)
}
OK, step 1. The rule in the spec is as follows:
The continuation query expression is
from … into x …
converted tofrom x in ( from … ) …
Now your request
from ps in (
from fundInvoices in paidfundInvoices
from p in fundInvoices.Value
group p by p.VendorId)
select new Payment {
FundId = fundInvoices.Key.FundId, // ERROR here
Value = ps.Sum(p => p.Amount)
}
And now it should be clear why fundInvoices
it is out of scope in the select clause. fundInvoices
is a range variable from a completely different request.
But if it's not clear, keep walking. The next rule:
The form request expression is
from x in e select v
converted to( e ) . Select ( x => v )
Now your request
((from fundInvoices in paidfundInvoices
from p in fundInvoices.Value
group p by p.VendorId))
.Select(ps =>
new Payment {
FundId = fundInvoices.Key.FundId,
Value = ps.Sum(p => p.Amount)
})
Now we can translate the inner query:
A query expression with a second clause followed by something other than a select clause
from x1 in e1 from x2 in e2 …
is converted tofrom * in ( e1 ) . SelectMany( x1 => e2 , ( x1 , x2 ) => new { x1 , x2 } ) …
*
is a "transparent identifier" and we'll see what that means in a minute.
Now your request
((from * in (paidfundInvoices).SelectMany(
fundInvoices => fundInvoices.Value,
(fundInvoices, p) => new {fundInvoices, p})
group p by p.VendorId))
.Select(ps =>
new Payment {
FundId = fundInvoices.Key.FundId,
Value = ps.Sum(p => p.Amount)
})
End rule:
The form request expression is
from x in e group v by k
converted to( e ) . GroupBy ( x => k , x => v )
So this is
((((paidfundInvoices).SelectMany(
fundInvoices => fundInvoices.Value,
(fundInvoices, p) => new {fundInvoices, p}))
.GroupBy(* => p.VendorId, * => p)))
.Select(ps =>
new Payment {
FundId = fundInvoices.Key.FundId,
Value = ps.Sum(p => p.Amount)
})
*
means "cast a pair of members of an anonymous type selected in select-many to scope. Desugar this and remove unnecessary parsers, and we have the final form of the query:
paidfundInvoices
.SelectMany(
fundInvoices => fundInvoices.Value,
(fundInvoices, p) => new {fundInvoices, p})
.GroupBy(pair => pair.p.VendorId, pair => pair.p)))
.Select(ps =>
new Payment {
FundId = fundInvoices.Key.FundId,
Value = ps.Sum(p => p.Amount)
})
And now it should be very clear why fundInvoices
it is not in scope in the sequel. This would be in scope GroupBy
thanks to the transparent desurage identifier, but in scope Select
it is not entirely possible.
More generally: LINQ scopes usually go from declarations on the left to customs on the right, but there are some exceptions: a into
removes range variables from scope, not all range variables are in scope in all places in join
, etc. Read more in the specification.
source to share
Once you do group
into
, you will no longer be able to access the original variables from
. If you need access, put it in a group:
from fundInvoices in paidfundInvoices
from p in fundInvoices.Value
group new { fundInvoices, p } by p.VendorId into ps
select new Payment
{
FundId = ps.fundInvoices.FundId,
Value = ps.Sum(p => p.Amount)
}
source to share