Linq / XML: правильная группировка результатов в элементе XML - с внутренними объединениями! - PullRequest
0 голосов
/ 26 августа 2009

В предыдущем вопросе Я спросил о том, как логически сгруппировать элементы 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 ]

2 голосов
/ 26 августа 2009

Есть несколько способов решить эту проблему, но ни один из них не является изящным. Несколько вариантов:

Вариант 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. Используйте подход внутреннего объединения с группой вместо отдельных:

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 голос
/ 26 августа 2009

Я думаю, у тебя хорошее начало. Вы можете добавить информацию о странах и штатах в свой список 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 для результатов.

0 голосов
/ 26 августа 2009

Вот один из подходов: сначала вы создаете запрос со всеми правильными внутренними объединениями, затем вы создаете внешние группировки с использованием фильтра Distinct(), а затем создаете XML из групп с помощью предложения where, чтобы объединить их. Таким образом:

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

Это работает, но почему-то у меня такое чувство, что есть лучший способ ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...