89 messaggi dal 13 aprile 2004
Buongiorno, ho il problema di dover filtrare una collezione di oggetti figli all'interno di oggetti padre. Mi spiego meglio. I miei dati (in forma sintetica) sono rappresentati mediante un file xml del tipo:
<?xml version="1.0" encoding="utf-8"?>
<rtb> 
<e s="1" t="104" n="Item1" d="2015-03-08-15-00">
     <b id="4" r="2" l="107,72">
        <o id="16" r="2" du="2015-03-03-08-53" u="0" />
        <o id="16" r="1" du="2015-03-03-08-53" u="0" />
        <o id="4" r="2" du="2015-03-03-08-53" u="0" />
        <o id="4" r="1" du="2015-03-03-08-53" u="0" />
      </b>
      <b id="5" r="2" l="107,79">
        <o id="16" r="2" du="2015-03-03-09-45" u="0" />
        <o id="16" r="1" du="2015-03-03-09-53" u="1" />
        <o id="5" r="2" du="2015-03-03-09-45" u="0" />
        <o id="5" r="1" du="2015-03-03-09-53" u="0" />
      </b>
      <b id="7" r="2" l="108,65">
        <o id="16" r="2" du="2015-03-03-08-53" u="0" />
        <o id="16" r="1" du="2015-03-03-08-53" u="1" />
        <o id="4" r="2" du="2015-03-03-08-53" u="0" />
        <o id="4" r="1" du="2015-03-03-08-53" u="0" />
      </b>
</e>
<e s="1" t="13" n="Item2" d="2015-03-08-15-10">
     <b id="4" r="2" l="107,72">
        <o id="4" r="2" du="2015-03-03-08-53" u="0" />
        <o id="4" r="1" du="2015-03-03-08-53" u="0" />
      </b>
      <b id="7" r="2" l="108,65">
        <o id="16" r="2" du="2015-03-03-08-53" u="0" />
        <o id="16" r="1" du="2015-03-03-09-45" u="0" />
        <o id="4" r="2" du="2015-03-03-09-45" u="0" />
        <o id="4" r="1" du="2015-03-03-08-53" u="0" />
        <o id="8" r="2" du="2015-03-03-08-53" u="0" />
        <o id="8" r="1" du="2015-03-03-08-53" u="0" />
      </b>
</e>
 <e s="1" t="104" n="Item3" d="2015-03-08-15-00">
      <b id="5" r="2" l="107,79">
        <o id="6" r="2" du="2015-03-03-09-45" u="0" />
        <o id="6" r="1" du="2015-03-03-09-53" u="0" />
        <o id="5" r="2" du="2015-03-03-09-45" u="0" />
        <o id="5" r="1" du="2015-03-03-09-53" u="0" />
      </b>
      <b id="7" r="2" l="108,65">
        <o id="5" r="2" du="2015-03-03-08-53" u="0" />
        <o id="5" r="1" du="2015-03-03-08-53" u="0" />
        <o id="3" r="2" du="2015-03-03-08-53" u="0" />
        <o id="3" r="1" du="2015-03-03-08-53" u="0" />
      </b>
</e>
</rtb>

ora vorrei filtrare il file in maniera da ottenere tutti gli elementi 'e' (e di conseguenza 'b') che all'loro interno hanno solo figli 'o' con id uguale a 4 e 8.
Ovvero dal file precedente vorrei ottenere il file :
<?xml version="1.0" encoding="utf-8"?>
<rtb> 
<e s="1" t="104" n="Item1" d="2015-03-08-15-00">
     <b id="4" r="2" l="107,72">
        <o id="4" r="2" du="2015-03-03-08-53" u="0" />
        <o id="4" r="1" du="2015-03-03-08-53" u="0" />
      </b>
      <b id="7" r="2" l="108,65">
        <o id="4" r="2" du="2015-03-03-08-53" u="0" />
        <o id="4" r="1" du="2015-03-03-08-53" u="0" />
      </b>
</e>
<e s="1" t="13" n="Item2" d="2015-03-08-15-10">
     <b id="4" r="2" l="107,72">
        <o id="4" r="2" du="2015-03-03-08-53" u="0" />
        <o id="4" r="1" du="2015-03-03-08-53" u="0" />
      </b>
      <b id="7" r="2" l="108,65">
        <o id="4" r="2" du="2015-03-03-09-45" u="0" />
        <o id="4" r="1" du="2015-03-03-08-53" u="0" />
        <o id="8" r="2" du="2015-03-03-08-53" u="0" />
        <o id="8" r="1" du="2015-03-03-08-53" u="0" />
      </b>
</e>
</rtb>

Ho iniziato a lavorare da poco con Linq e con il seguente codice:
string[] _details = {"4", "8"};
  
var filteredItems = (from e in xData.Root.Elements("e")
                                     .SelectMany(l =>         l.Elements("b").Elements("o"),
                                          (parent, child) => new
                                          {
                                              parent_element = parent,
                                              child_element = child
                                          })
                    where _details.Contains(e.child_element.Attribute("id").Value)
              select e.parent_element).Distinct();

riesco ad ottenere tutti gli elementi 'e' che contengono i figli 'o' con id ="4" oppure id="8", ma non riesco ad eliminare negli elementi 'e' risultanti i figli 'o' che non soddisfano le condizioni (e anche i relativi elementi 'b' che risulterebbero vuoti).
E' possibile farlo tramite Linq oppure è necessario ciclare su tutta la gerarchia per poi copiare solo quello che serve?
11.886 messaggi dal 09 febbraio 2002
Contributi
ciao,
sì, con LINQ to XML puoi farlo, ma può darsi che esistano soluzioni più efficienti. Dipende dal risultato che vuoi ottenere: devi solo ciclare i risultati oppure produrre un nuovo documento XML?

Se scegli di usare LINQ to XML, nella select dovresti restituire gli elementi o anziché gli b. Poi eventualmente li raggruppi in base al loro Parent, come in questo esempio:
https://dotnetfiddle.net/0KxpxD


ciao,
Moreno
Modificato da BrightSoul il 05 marzo 2015 23.32 -

Enjoy learning and just keep making
89 messaggi dal 13 aprile 2004
Innanzitutto grazie per la risposta.
In effetti devo generare un nuovo file xml che deve essere come riportato nel primo post.
Ho visto la tua soluzione ma non ho ben capito se è più o meno efficiente. Devo dire che sono alle prime armi con Linq. Ho provato al tua query con LinqPad ma ho capito del tutto il funzionamento. Nel tuo caso infatti devi comunque ciclare per ricreare il file xml, mentre nel mio caso dagli elementi trovati devo ciclare per rimuovere gli elementi 'o' che non soddifano i requisiti.
Non esiste un modo per evitare proprio di ciclare?
Comunque ancora grazie per la risposta
11.886 messaggi dal 09 febbraio 2002
Contributi
ciao, prego!

LittleAnt ha scritto:

Non esiste un modo per evitare proprio di ciclare?

Gli elementi vanno necessariamente esaminati uno ad uno. Anche quando non usi esplicitamente un foreach, ti devi affidare ad una query LINQ che internamente passa comunque in rassegna ogni elemento. Il vantaggio di usare una query LINQ è che resta più concisa e un po' più leggibile nel caso di logiche di filtraggio, ordinamento e proiezione complesse.

LittleAnt ha scritto:

In effetti devo generare un nuovo file xml

Ho modificato il mio precedente esempio in modo da fargli produrre il documento come serve a te. Ecco il link:
https://dotnetfiddle.net/IzhUC1
In questo caso non ho usato il foreach ma una select, che mi consente di emettere degli XElement.

LittleAnt ha scritto:

ma ho capito del tutto il funzionamento

La query seleziona solo gli elementi o il cui id è 4 o 8

from o in root.Descendants("o")
where idAmmessi.Contains(o.Attribute("id").Value)

Da questa query otterresti una lista piatta di o, ma non è quello che vogliamo perché la gerarchia degli elementi e, b, o va preservata. Quindi raggruppo gli o per il loro parent, in modo da ottenere la lista di b in cui sono contenuti. In questo modo tengo fuori automaticamente gli b che non avevano al loro interno degli o rispondenti ai criteri di filtro.

group o by o.Parent into gruppo1

Fin qui ho ottenuto una lista piatta di b, ma abbiamo bisogno di raggrupparli ulteriormente per il loro parent, e.

group gruppo1 by gruppo1.Key.Parent into gruppo2

Infine proietto questi gruppi in nuovi XElement

select new XElement("e",
gruppo2.Key.Attributes(),
from b in gruppo2
select new XElement("b", b.Key.Attributes(), b)
));


LittleAnt ha scritto:

non ho ben capito se è più o meno efficiente

Non so stimare il tempo di esecuzione di questo algoritmo ma se sei alla ricerca del massimo delle prestazioni dovresti usare un XmlReader e un XmlWriter per leggere il documento in maniera forward-only e trascrivere i soli elementi che rispondono al tuo criterio. In questo modo, il documento non deve essere completamente caricato in memoria ed è una soluzione che scala bene anche con file grandi.
http://blogs.msdn.com/b/mfussell/archive/2005/02/12/371546.aspx

Se decidi di continuare ad usare LINQ, un'altra possibile soluzione è quella di selezionare gli elementi che NON rispondono al criterio ed eliminarli dal documento originale. Ovviamente andrai a risalvarlo su un percorso diverso.

Ecco l'esempio:
https://dotnetfiddle.net/Tj8MCg
In questo caso seleziono tutti gli o che NON possiedono un id uguale a 4 o 8 e li elimino. Poi elimino anche gli b e gli e che sono rimaasti senza discendenti. Questo approccio dovrebbe essere leggermente migliore dell'altro, perché di fatto stiamo modificando un unico documento, anziché crearne in memoria uno nuovo.

ciao,
Moreno
Modificato da BrightSoul il 07 marzo 2015 10.29 -

Enjoy learning and just keep making
89 messaggi dal 13 aprile 2004
Moreno,
Sei grande!!! Ora ho compreso perfettamente entrambe le soluzioni. La prima era quella che cercavo di ottenere, anche se comprendo che la seconda è più efficiente.
Entrambe le soluzioni mi hanno fatto comprendere meglio le potenzialità di Linq, che mi sembrano veramente enormi.
Ancora grazie per il prezioso aiuto.
Modificato da LittleAnt il 07 marzo 2015 17.00 -

Torna al forum | Feed RSS

ASPItalia.com non è responsabile per il contenuto dei messaggi presenti su questo servizio, non avendo nessun controllo sui messaggi postati nei propri forum, che rappresentano l'espressione del pensiero degli autori.