【问题标题】:Linq/XML: grouping results properly within XML element - with inner joins!Linq/XML:在 XML 元素中正确分组结果 - 使用内部连接!
【发布时间】:2009-08-26 11:50:03
【问题描述】:

previous question 中,我询问了如何对 XML 元素进行逻辑分组,我得到了答案,即嵌套 Linq 查询。

问题是,这具有左连接嵌套查询的效果。例如,假设我想列出美国所有以字母“Y”开头的城市,按州和县分组:

XElement xml = new XElement("States",
  from s in LinqUtils.GetTable<State>()
  orderby s.Code 
  select new XElement("State",
    new XAttribute("Code", s.Code),
    new XAttribute("Name", s.Name),
    from cy in s.Counties
    orderby cy.Name
    select new XElement("County",
      new XAttribute("Name", cy.Name),
      from c in cy.Cities
      where c.Name.StartsWith("Y")
      orderby c.Name
      select new XElement("City",
        new XAttribute("Name", c.Name)
      )
    )
  )
);

Console.WriteLine(xml);

这个输出:

<States>
  <State Code="AK" Name="Alaska ">
    <County Name="ALEUTIANS EAST" />
    <County Name="ALEUTIANS WEST" />
    <County Name="ANCHORAGE" />
    <County Name="BETHEL" />
    ...
    <County Name="YAKUTAT">
      <City Name="YAKUTAT" />
    </County>
    <County Name="YUKON KOYUKUK" />
  </State>
  <State Code="AL" Name="Alabama ">
    <County Name="AUTAUGA" />
    ...
    etc.

我不想要左连接效果;我只想查看实际包含以字母“Y”开头的城市的州和县。

我可以想到几种方法来做到这一点,但它们都显得笨拙和不雅。你能想到的最简洁的方法是什么来达到预期的效果?

【问题讨论】:

    标签: c# xml linq linq-to-xml


    【解决方案1】:

    有几种方法可以解决这个问题,但没有一种是非常优雅的。几个选项:

    选项1:使用let捕获子查询并过滤掉空值:

    XElement xml = new XElement("States",
      from s in LinqUtils.GetTable<State>()
      let counties = from cy in s.Counties
                     let cities = from c in cy.Cities
                                  where c.Name.StartsWith("Y")
                                  orderby c.Name
                                  select new XElement("City",
                                    new XAttribute("Name", c.Name)
                                  )
                     where cities.Any()
                     orderby cy.Name
                     select new XElement("County",
                       new XAttribute("Name", cy.Name),
                       cities          
                     )
      where counties.Any()
      orderby s.Code 
      select new XElement("State",
        new XAttribute("Code", s.Code),
        new XAttribute("Name", s.Name),
        counties
      )
    );
    

    选项 2:使用 group by 而不是 distinct:

    XElement xml = new XElement("States",
      from s in LinqUtils.GetTable<State>()
      from cy in s.Counties
      from c in cy.Cities
      where c.Name.StartsWith("Y")
      group new { cy, c } by s into gs
      let s = gs.Key
      orderby s.Code 
      select new XElement("State",
        new XAttribute("Code", s.Code),
        new XAttribute("Name", s.Name),
    
        from g in gs
        group g.c by g.cy into gcy
        let cy = gcy.Key
        orderby cy.Name
        select new XElement("County",
          new XAttribute("Name", cy.Name),
    
          from c in gcy
          orderby c.Name
          select new XElement("City",
            new XAttribute("Name", c.Name)
          )
        )
      )
    );
    

    【讨论】:

    • 太棒了!感谢您向我展示如何使用匿名类型进行嵌套分组 - 这让您得到了答案! :)
    【解决方案2】:

    我认为你有一个好的开始。您可以将有关国家和州的信息添加到您的 Cities 列表,然后 group by 它们,避免第二次加入和过滤。
    你甚至可以在一个大的 linq 查询中做到这一点。因为你有自己的类,所以很难准确地写出你需要的东西,但这里有一些与文件和文件夹类似的东西(你需要添加另一个级别):

    dirs = new List<DirectoryInfo>();
    dirs.Add(new DirectoryInfo("c:\\"));
    dirs.Add(new DirectoryInfo("c:\\windows\\"));
    
    var a = from directory in dirs
            from file in directory.GetFiles()
            where file.Name.StartsWith("a")
            group file by directory.Name into fileGroup
            select new XElement("Directory", new XAttribute("path", fileGroup.Key),
                from f in fileGroup
                select new XElement("File", f.Name)
                );
    
    XDocument doc = new XDocument(new XElement("Folders", a));
    

    生成 XML:

    <Folders>
      <Directory path="c:\">
        <File>ActiveDirectoryService.cs</File>
        <File>ApplicationTemplateCore.wsp</File>
        <File>AUTOEXEC.BAT</File>
      </Directory>
      <Directory path="windows">
        <File>adfs.msp</File>
        <File>adminscript2nd.exe</File>
        <File>aspnetocm.log</File>
      </Directory>
    </Folders>
    

    同样,这里的关键是在结果上使用group by

    【讨论】:

    • Toda rabba...但是我在 group-by 的双重嵌套方面遇到了困难。您能否举例说明如何进行双重嵌套,例如 State->County->City?谢谢!
    【解决方案3】:

    这是一种方法:首先使用所有正确的内部连接创建查询,然后使用 Distinct() 过滤器创建外部分组,然后使用 where 子句从分组创建 XML 以连接它们。因此:

    var Cities = from s in LinqUtils.GetTable<State>()
                 from cy in s.Counties
                 from c in cy.Cities
                 where c.Name.StartsWith("Y")
                 select c;
    
    var States = Cities.Select(c => c.County.State).Distinct();
    var Counties = Cities.Select(c => c.County).Distinct();
    
    XElement xml = new XElement("States",
      from s in States
      orderby s.Code
      select new XElement("State",
        new XAttribute("Code", s.Code),
        new XAttribute("Name", s.Name),
        from cy in Counties
        where cy.StateCode == s.Code
        orderby cy.Name
        select new XElement("County",
          new XAttribute("Name", cy.Name),
          from c in Cities
          where c.CountyID == cy.ID
          orderby c.Name
          select new XElement("City",
            new XAttribute("Name", c.Name)
          )
        )
      )
    );
    

    它有效,但不知何故我觉得有更好的方法......

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多