2011-04-25 16 views
9

Esta es mi primera publicación en StackOverflow, así que por favor tengan paciencia conmigo. Y me disculpo por adelantado si mi ejemplo de código es un poco largo.¿Cómo extraigo información dentro de XML usando C# y LINQ?

Utilizando C# y LINQ, estoy tratando de identificar una serie de elementos de tercer nivel id (000049 en este caso) en un archivo XML mucho más grande. Cada tercer nivel id es único, y los que quiero se basan en una serie de información descendiente para cada uno. Más específicamente, si type == A y location type(old) == vault y location type(new) == out, entonces quiero seleccionar ese id. A continuación está el código XML y C# que estoy usando.

En general, mi código funciona. Como se indica a continuación, devolverá un id de 000049 dos veces, lo cual es correcto. Sin embargo, he encontrado un problema. Si elimino el primer bloque history que contiene type == A, mi código aún devuelve dos veces id de 000049 cuando solo debería devolverlo una vez. Sé por qué está sucediendo, pero no puedo encontrar una forma mejor de ejecutar la consulta. ¿Hay una forma mejor de ejecutar mi consulta para obtener la salida que quiero y seguir usando LINQ?

Mi XML:

<?xml version="1.0" encoding="ISO8859-1" ?> 
<data type="historylist"> 
    <date type="runtime"> 
     <year>2011</year> 
     <month>04</month> 
     <day>22</day> 
     <dayname>Friday</dayname> 
     <hour>15</hour> 
     <minutes>24</minutes> 
     <seconds>46</seconds> 
    </date> 
    <customer> 
     <id>0001</id> 
     <description>customer</description> 
     <mediatype> 
      <id>kit</id> 
      <description>customer kit</description> 
      <volume> 
       <id>000049</id> 
       <history> 
        <date type="optime"> 
         <year>2011</year> 
         <month>04</month> 
         <day>22</day> 
         <dayname>Friday</dayname> 
         <hour>03</hour> 
         <minutes>00</minutes> 
         <seconds>02</seconds> 
        </date> 
        <userid>batch</userid> 
        <type>OD</type> 
        <location type="old"> 
         <repository>vault</repository> 
         <slot>0</slot> 
        </location> 
        <location type="new"> 
         <repository>out</repository> 
         <slot>0</slot> 
        </location> 
        <container>0001.kit.000049</container> 
        <date type="movedate"> 
         <year>2011</year> 
         <month>04</month> 
         <day>22</day> 
         <dayname>Friday</dayname> 
        </date> 
       </history> 
       <history> 
        <date type="optime"> 
         <year>2011</year> 
         <month>04</month> 
         <day>22</day> 
         <dayname>Friday</dayname> 
         <hour>06</hour> 
         <minutes>43</minutes> 
         <seconds>33</seconds> 
        </date> 
        <userid>vaultred</userid> 
        <type>A</type> 
        <location type="old"> 
         <repository>vault</repository> 
         <slot>0</slot> 
        </location> 
        <location type="new"> 
         <repository>out</repository> 
         <slot>0</slot> 
        </location> 
        <container>0001.kit.000049</container> 
        <date type="movedate"> 
         <year>2011</year> 
         <month>04</month> 
         <day>22</day> 
         <dayname>Friday</dayname> 
        </date> 
       </history> 
       <history> 
        <date type="optime"> 
         <year>2011</year> 
         <month>04</month> 
         <day>22</day> 
         <dayname>Friday</dayname> 
         <hour>06</hour> 
         <minutes>43</minutes> 
         <seconds>33</seconds> 
        </date> 
        <userid>vaultred</userid> 
        <type>S</type> 
        <location type="old"> 
         <repository>vault</repository> 
         <slot>0</slot> 
        </location> 
        <location type="new"> 
         <repository>out</repository> 
         <slot>0</slot> 
        </location> 
        <container>0001.kit.000049</container> 
        <date type="movedate"> 
         <year>2011</year> 
         <month>04</month> 
         <day>22</day> 
         <dayname>Friday</dayname> 
        </date> 
       </history> 
       <history> 
        <date type="optime"> 
         <year>2011</year> 
         <month>04</month> 
         <day>22</day> 
         <dayname>Friday</dayname> 
         <hour>06</hour> 
         <minutes>45</minutes> 
         <seconds>00</seconds> 
        </date> 
        <userid>batch</userid> 
        <type>O</type> 
        <location type="old"> 
         <repository>out</repository> 
         <slot>0</slot> 
        </location> 
        <location type="new"> 
         <repository>site</repository> 
         <slot>0</slot> 
        </location> 
        <container>0001.kit.000049</container> 
        <date type="movedate"> 
         <year>2011</year> 
         <month>04</month> 
         <day>22</day> 
         <dayname>Friday</dayname> 
        </date> 
       </history> 
       <history> 
        <date type="optime"> 
         <year>2011</year> 
         <month>04</month> 
         <day>22</day> 
         <dayname>Friday</dayname> 
         <hour>11</hour> 
         <minutes>25</minutes> 
         <seconds>59</seconds> 
        </date> 
        <userid>ihcmdm</userid> 
        <type>A</type> 
        <location type="old"> 
         <repository>out</repository> 
         <slot>0</slot> 
        </location> 
        <location type="new"> 
         <repository>site</repository> 
         <slot>0</slot> 
        </location> 
        <container>0001.kit.000049</container> 
        <date type="movedate"> 
         <year>2011</year> 
         <month>04</month> 
         <day>22</day> 
         <dayname>Friday</dayname> 
        </date> 
       </history> 
       <history> 
        <date type="optime"> 
         <year>2011</year> 
         <month>04</month> 
         <day>22</day> 
         <dayname>Friday</dayname> 
         <hour>11</hour> 
         <minutes>25</minutes> 
         <seconds>59</seconds> 
        </date> 
        <userid>ihcmdm</userid> 
        <type>S</type> 
        <location type="old"> 
         <repository>out</repository> 
         <slot>0</slot> 
        </location> 
        <location type="new"> 
         <repository>site</repository> 
         <slot>0</slot> 
        </location> 
        <container>0001.kit.000049</container> 
        <date type="movedate"> 
         <year>2011</year> 
         <month>04</month> 
         <day>22</day> 
         <dayname>Friday</dayname> 
        </date> 
       </history> 
      </volume> 
      ... 

Mi código C#:

IEnumerable<XElement> caseIdLeavingVault = 
    from volume in root.Descendants("volume") 
    where 
     (from type in volume.Descendants("type") 
     where type.Value == "A" 
     select type).Any() && 
     (from locationOld in volume.Descendants("location") 
     where 
      ((String)locationOld.Attribute("type") == "old" && 
       (String)locationOld.Element("repository") == "vault") && 
      (from locationNew in volume.Descendants("location") 
       where 
        ((String)locationNew.Attribute("type") == "new" && 
        (String)locationNew.Element("repository") == "out") 
       select locationNew).Any() 
     select locationOld).Any() 
    select volume.Element("id"); 

    ... 

foreach (XElement volume in caseIdLeavingVault) 
{ 
    Console.WriteLine(volume.Value.ToString()); 
} 

Gracias.


OK chicos, estoy perplejo de nuevo. Dada esta misma situación y la siguiente solución de @ Elian (que funciona muy bien), necesito las fechas "optime" y "movedate" para el history utilizado para seleccionar el id. ¿Tiene sentido? Tenía la esperanza de terminar con algo como esto:

select new { 
    id = volume.Element("id").Value, 

    // this is from "optime" 
    opYear = <whaterver>("year").Value, 
    opMonth = <whatever>("month").Value, 
    opDay = <whatever>("day").Value, 

    // this is from "movedate" 
    mvYear = <whaterver>("year").Value, 
    mvMonth = <whatever>("month").Value, 
    mvDay = <whatever>("day").Value 
} 

he probado tantas combinaciones diferentes, pero los Attribute s para <date type="optime"> y <date type="movedate"> guardo en mi camino y me parece que no puede conseguir lo que quiero.


OK. He encontrado una solution que funciona bien:

select new { 
    caseId = volume.Element("id").Value, 

    // this is from "optime" 
    opYear = volume.Descendants("date").Where(t => t.Attribute("type").Value == "optime").First().Element("year").Value, 
    opMonth = volume.Descendants("date").Where(t => t.Attribute("type").Value == "optime").First().Element("month").Value, 
    opDay = volume.Descendants("date").Where(t => t.Attribute("type").Value == "optime").First().Element("day").Value, 

    // this is from "movedate" 
    mvYear = volume.Descendants("date").Where(t => t.Attribute("type").Value == "movedate").First().Element("year").Value, 
    mvMonth = volume.Descendants("date").Where(t => t.Attribute("type").Value == "movedate").First().Element("month").Value, 
    mvDay = volume.Descendants("date").Where(t => t.Attribute("type").Value == "movedate").First().Element("day").Value 
}; 

Sin embargo, no falle cuando se encuentra un id sin "movedate". Algunos de ellos existen, así que ahora estoy trabajando en eso.


Bueno, ayer por la tarde que finalmente descubrió la solución que había estado esperando:

var caseIdLeavingSite = 
    from volume in root.Descendants("volume") 
    where volume.Elements("history").Any(
     h => h.Element("type").Value == "A" && 
     h.Elements("location").Any(l => l.Attribute("type").Value == "old" && ((l.Element("repository").Value == "site") || 
                       (l.Element("repository").Value == "init"))) && 
     h.Elements("location").Any(l => l.Attribute("type").Value == "new" && l.Element("repository").Value == "toVault") 
     ) 
    select new { 
     caseId = volume.Element("id").Value, 
     opYear = volume.Descendants("date").Where(t => t.Attribute("type").Value == "optime").First().Element("year").Value, 
     opMonth = volume.Descendants("date").Where(t => t.Attribute("type").Value == "optime").First().Element("month").Value, 
     opDay = volume.Descendants("date").Where(t => t.Attribute("type").Value == "optime").First().Element("day").Value, 
     mvYear = (volume.Descendants("date").Where(t => t.Attribute("type").Value == "movedate").Any() == true) ? 
       (volume.Descendants("date").Where(t => t.Attribute("type").Value == "movedate").First().Element("year").Value) : "0", 
     mvMonth = (volume.Descendants("date").Where(t => t.Attribute("type").Value == "movedate").Any() == true) ? 
        (volume.Descendants("date").Where(t => t.Attribute("type").Value == "movedate").First().Element("month").Value) : "0", 
     mvDay = (volume.Descendants("date").Where(t => t.Attribute("type").Value == "movedate").Any() == true) ? 
       (volume.Descendants("date").Where(t => t.Attribute("type").Value == "movedate").First().Element("day").Value) : "0" 
    }; 

Esto satisface los requisitos que ayudó con @Elian y agarra la información adicional necesaria la fecha. También representa las pocas instancias en las que no hay ningún elemento para "movedate" mediante el operador ternario ?:.

Ahora, si alguien sabe cómo hacer esto más eficiente, todavía estoy interesado. Gracias.

Respuesta

8

Creo que quieres algo como esto:

IEnumerable<XElement> caseIdLeavingVault = 
    from volume in document.Descendants("volume") 
    where volume.Elements("history").Any(
     h => h.Element("type").Value == "A" && 
      h.Elements("location").Any(l => l.Attribute("type").Value == "old" && l.Element("repository").Value == "vault") && 
      h.Elements("location").Any(l => l.Attribute("type").Value == "new" && l.Element("repository").Value == "out") 
     ) 
    select volume.Element("id"); 

Su código comprueba de forma independiente si un volumen tiene un elemento <history> de tipo A y una (no necesariamente los mismos) elemento <history> que tiene las <location> elementos requeridos.

El código anterior comprueba si existe un elemento <history> que sea del tipo A y contenga los elementos necesarios <location>.

Actualización: Abatishchev sugirió una solución que utiliza una consulta xpath en lugar de LINQ a XML, pero su consulta es demasiado simple y no devuelve exactamente lo que solicitó. La siguiente consulta XPath hará el truco, pero también es un poco más largo:

data/customer/mediatype/volume[history[type = 'A' and location[@type = 'old' and repository = 'vault'] and location[@type = 'new' and repository = 'out']]]/id 
+0

Gracias @Elian para la respuesta. Lo probaré. – meffordm

+0

@Elian Parece que funciona. ¡Gracias de nuevo! – meffordm

+0

@meffordm: No olvides aceptar esta respuesta correcta – abatishchev

1

Para qué utiliza usted como LINQ complejas y costosas de consulta XML cuando se puede usar sencilla consulta XPath:

using System.Xml; 

string xml = @"..."; 
string xpath = "data/customer/mediatype/volume/history/type[text()='A']/../location[@type='old' or @type='new']/../../id"; 

var doc = new XmlDocument(); 
doc.LoadXml(xml); // or use Load(path); 

var nodes = doc.SelectNodes(xpath); 

foreach (XmlNode node in nodes) 
{ 
    Console.WriteLine(node.InnerText); // 000049 
} 

o si no es necesario XML modelo DOM:

using System.Xml.XPath; 

XPathDocument doc = null; 
using (var stream = new StringReader(xml)) 
{ 
    doc = new XPathDocument(stream); // specify just path to file if you have such one 
} 
var nav = doc.CreateNavigator(); 
XPathNodeIterator nodes = (XPathNodeIterator)nav.Evaluate(xpath); 
foreach (XPathNavigator node in nodes) 
{ 
    Console.WriteLine(node.Value); 
} 
+0

+1; a veces las consultas nativas son la respuesta. –

+0

Su consulta xpath no hace lo mismo, aunque creo que tiene razón en que una consulta xpath será más corta en este caso. –

+0

@Elian: Probablemente, no, no soy tan bueno en XPath, pero acabo de mostrar la idea en general. – abatishchev

Cuestiones relacionadas