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 ...

+3


source to share


2 answers


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.

+14


source


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)
                }

      

+1


source







All Articles