Linq / XML:在XML元素中正确分组结果 - 使用内部联接!

时间:2009-08-26 11:50:04

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

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”开头的城市的州和县。

我可以想到几种方法来做到这一点,但它们看起来都很蹩脚而且不够优雅。您能想到达到预期效果的最佳方式是什么?

3 个答案:

答案 0 :(得分:2)

有几种方法可以解决这个问题,但没有一种方法可以解决这个问题。一些选择:

选项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)
      )
    )
  )
);

答案 1 :(得分:1)

我认为你有个好的开始。您可以在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

答案 2 :(得分:0)

以下是一种方法:首先使用所有正确的内部联接创建查询,然后使用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)
      )
    )
  )
);

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