2017-06-30 30 views
3

我试图理解为什么这个LINQ不会编译(fundInvoices不可见):多LINQ“从”和变量的知名度

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

于是我去到这个改变匿名类型的使用和fundInvoices是神奇可见这里:

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

但是,匿名类型似乎是多余的,我不作任何利用这一点。我只需要一个支付对象的平面清单。然而,我的代码只能编译这种方式...

回答

14

我试图理解为什么这个LINQ不会编译

来理解的关键是阅读说明书的部分查询如何降低到正常的代码。

让我们先从您的查询:

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,一步一个。在规范的规则是:

与延续from … into x …查询表达式转换成from x in (from …) …

您的查询已经

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

现在应该很清楚为什么fundInvoices是不在select子句的范围内。 fundInvoices是完全不同的查询的范围变量

但是,如果不清楚,让我们继续前进。下一条规则是:

形式from x in e select v的查询表达式转换成(e) . Select (x => v)

您的查询已经

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

现在,我们可以转换内部查询:

查询表达式的第二个from子句后跟s omething比SELECT子句from x1 in e1 from x2 in e2 …其他被翻译成from * in (e1) . SelectMany(x1 => e2 , (x1 , x2) => new { x1 , x2 }) …

*是“透明的标识符”,我们将看到这意味着什么在一分钟内。

您的查询现在

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

决赛规则:

形式from x in e group v by k的查询表达式转换成(e) . GroupBy (x => k , x => v)

所以这是

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

*的意思是“将select-many中选定的匿名类型的成员变为范围。 Desugar这一点,删除不必要的括号,我们有查询的最终形式:

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

现在应该很清楚为什么fundInvoices不是在延续范围。它GroupBy范围内由于透明标识符desugaring,但它根本不在Select范围内。

更普遍的:在LINQ范围一般从申报流程就留给用途的权利,但也有一些例外:一个into去除作用域范围变量,不是所有的范围变量的范围在一join所有地方条款等等。阅读规范了解更多细节。

1

一旦你做了groupinto你不能再访问原来的from变量。如果您需要访问,把它放在该组中:

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