2009-08-26 5 views
0

Dans un previous question j'ai demandé comment grouper les éléments XML logiquement, et j'ai eu la réponse, qui était d'imbriquer la requête Linq.Linq/XML: regrouper les résultats correctement dans l'élément XML - avec des jointures internes!

Le problème est que ceci a pour effet de joindre à gauche les requêtes imbriquées. Par exemple, disons que je veux énumérer toutes les villes aux Etats-Unis qui commencent par la lettre « Y », regroupées par État et comté:

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

Ce sorties:

<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. 

Je ne Je veux l'effet de jointure à gauche; Je veux seulement voir les états et les comtés qui contiennent réellement des villes commençant par la lettre "Y".

Je peux penser à quelques façons de le faire, mais ils semblent tous kludgy et inélégant. Quel est le meilleur moyen de parvenir à l'effet désiré?

Répondre

2

Il existe plusieurs façons de résoudre ce problème, mais aucune n'est extrêmement élégante. Quelques possibilités:

Option 1: Utilisez let pour capturer les sous-requêtes et filtrer les valeurs vides:

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

Option 2: Utilisez votre approche intérieure se joindre à un groupe par lieu de distincts:

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

Excellent! Merci de m'avoir montré comment faire le groupement imbriqué en utilisant le type anonyme - cela vous donne le crédit de la réponse! :) –

0

Voici une approche: Vous créez d'abord la requête avec toutes les jointures internes correctes, puis vous créez les groupements externes à l'aide d'un filtre Distinct(), puis créez le code XML à partir des regroupements en utilisant une clause where pour les joindre. Ainsi:

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

Il fonctionne, mais de toute façon j'ai le sentiment qu'il ya une meilleure façon ...

1

Je pense que vous avez un bon début. Vous pouvez ajouter des informations sur les pays et les états à votre liste Cities, puis sur group by eux, et en évitant la seconde jointure et le filtre.
Vous pouvez même le faire dans une grande requête linq. Il est difficile d'écrire exactement ce dont vous avez besoin parce que vous avez vos propres classes, mais voici quelque chose de similaire avec des fichiers et des dossiers (vous aurez besoin d'ajouter un autre niveau):

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

résultant dans le fichier 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> 

Encore une fois, la clé ici est d'utiliser group by sur les résultats.

+0

Toda rabba ... mais j'ai du mal avec la double imbrication du groupe par. Pourriez-vous s'il vous plaît donner un exemple de comment faire le double-nesting, comme avec State-> County-> City? Merci! –

Questions connexes