在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”开头的城市的州和县。
我可以想到几种方法来做到这一点,但它们看起来都很蹩脚而且不够优雅。您能想到达到预期效果的最佳方式是什么?
答案 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)
)
)
)
);
它有效,但不知怎的,我觉得有更好的方法......