Используя LINQ для запроса XDocument, как получить конкретные значения?

Я пытаюсь реорганизовать следующее - это работает, но если я начну получать больше элементов в XML, это станет неуправляемым:

HttpResponseMessage response = await httpClient.GetAsync("https://uri/products.xml");

string responseAsString = await response.Content.ReadAsStringAsync();

List<Product> productList = new List<Product>();

XDocument xdocument = XDocument.Parse(responseAsString);
var products = xdocument.Descendants().Where(p => p.Name.LocalName == "item");

foreach(var product in products)
{
    var thisProduct = new Product();
    foreach (XElement el in product.Nodes())
    {
        if(el.Name.LocalName == "id")
        {
            thisProduct.SKU = el.Value.Replace("-master", "");
        }
        if (el.Name.LocalName == "availability")
        {
            thisProduct.Availability = el.Value == "in stock";
        }
    }
    productList.Add(thisProduct);
}

Учитывая следующий URL-адрес XML

<rss xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    xmlns="http://base.google.com/ns/1.0" version="0">
    <channel>
        <title>Product Feed</title>
        <link></link>
        <description>Products</description>
        <item>
            <availability>in stock</availability>
            <id>01234-master</id>
            ...
        </item>
        <item>
            <availability>in stock</availability>
            <id>abcde-master</id>
            ...
        </item>
    </channel>
</rss>

В идеале я хотел бы удалить циклы и операторы if и иметь запрос LINQ, который возвращает из поля XML только нужные мне поля (идентификатор, доступность и т. Д.) В хорошем чистом виде и заполнить простой класс этими данными.

Кто-нибудь может помочь?

2 ответа

Решение

Иногда вы должны быть счастливы за код, который вы написали. Иногда нет более "умного" способа написать это... Вы можете написать это немного "лучше":

List<Product> productList = new List<Product>();

XDocument xdocument = XDocument.Parse(responseAsString);

XNamespace ns = "http://base.google.com/ns/1.0";

var products = from x in xdocument.Elements(ns + "rss")
               from y in x.Elements(ns + "channel")
               from z in y.Elements(ns + "item")
               select z;

foreach (var product in products)
{
    var prod = new Product();
    productList.Add(prod);

    foreach (XElement el in product.Elements())
    {
        if (el.Name == ns + "id")
        {
            prod.SKU = el.Value.Replace("-master", string.Empty);
        }
        else if (el.Name == ns + "availability")
        {
            prod.Availability = el.Value == "in stock";
        }
    }
}

Заметки:

  • Descendants() морально неправильно. Существует фиксированная позиция, где item будет, /rss/channel/itemи вы это прекрасно знаете. Это не //item, Потому что завтра может быть rss/foo/item что сегодня не существует. Вы пытаетесь написать свой код так, чтобы он был совместим с дополнительной информацией, которая может быть добавлена ​​в xml.
  • Я ненавижу пространства имен XML... И есть XML с несколькими вложенными пространствами имен. Как сильно я ненавижу их. Но кто-то умнее меня решил, что они существуют. Я принимаю это. Я кодирую их используя. В LINQ-to-XML это довольно просто. E сть XNamespace что даже перегружено + оператор.

    Обратите внимание, что если вы микрооптимизатор (я стараюсь этого не делать, но должен признать, что у меня немного чешется руки), вы можете предварительно рассчитать различные ns + "xxx" которые используются внутри for цикл, потому что это не ясно, отсюда, но все они перестраиваются каждый цикл. Как XName построен внутри... о... это увлекательная вещь, поверь мне.

    private static readonly XNamespace googleNs = "http://base.google.com/ns/1.0";
    private static readonly XName idName = googleNs + "id";
    private static readonly XName availabilityName = googleNs + "availability";
    

    а потом

    if (el.Name == idName)
    {
        prod.SKU = el.Value.Replace("-master", string.Empty);
    }
    else if (el.Name == availabilityName)
    {
        prod.Availability = el.Value == "in stock";
    }
    

Попробуйте следующее:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        const string FILENAME = @"c:\temp\test.xml";
        static void Main(string[] args)
        {

            new Item(FILENAME);

        }
    }
    public class Item
    {
        public static List<Item> items { get; set; }

        public string availability { get; set; }
        public string id { get; set; }

        public Item() { }
        public Item(string filename)
        {
            string xml = File.ReadAllText(filename);

            XDocument doc = XDocument.Parse(xml);
            XElement root = doc.Root;
            XNamespace ns = root.GetDefaultNamespace();

            Item.items = doc.Descendants(ns + "item").Select(x => new Item() {
                availability = (string)x.Element(ns + "availability"),
                id = (string)x.Element(ns + "id")
            }).ToList();
        }
    }
}
Другие вопросы по тегам