通过可能丢失的子节点在LINQ中排序XML

本文关键字:LINQ 排序 XML 子节点 | 更新日期: 2023-09-27 18:17:54

我正在处理一个结构如下的XML文件:

<vco:ItemDetail>
    <cac:Item>
      ...
      <cac:RecommendedRetailPrice>
        <cbc:PriceAmount amountCurrencyID="EUR">4.95</cbc:PriceAmount>
        <cbc:BaseQuantity quantityUnitCode="EA">1</cbc:BaseQuantity>
      </cac:RecommendedRetailPrice>
      ...
    </cac:Item>
    ...
</vco:ItemDetail>

RecommendedRetailPrice和/或PriceAmount可能不存在于每个Item。尽管如此,我还是想按RecommendedRetailPrices对所有Items进行排序。所以我最终得到了下面的代码,它工作得很好,但必须在一些可怕的嵌套if语句的每个阶段检查null:

//full_xml is of type XDocument
var most_expensive = full_xml
    .Element("ItemDetails")
    .Elements(vco + "ItemDetail")
    .Elements(cac + "Item")
    .OrderBy(el => 
        {
            var rrp = el.Element(cac + "RecommendedRetailPrice");
            string val = null;
            if(rrp != null) {
                var pa = rrp.Element(cbc + "PriceAmount");
                if(pa != null) {
                    val = rrp.Value;
                }
            }
            if (val != null) {
                return Convert.ToDouble(val);
            }
            else {
                return 0.0;
            }
        }                 
    )
    .Last();

在这种情况下,这可能不是那么糟糕,但我想一定有一种更习惯的方法来做到这一点。考虑按向下8级的可选子节点排序。

感谢您的帮助。

通过可能丢失的子节点在LINQ中排序XML

如果零或一个元素可以存在,那么我会使用Elements()而不是Element(),然后选择FirstOrDefault。然后使用XElement的显式强制转换操作符来获得您的值。

像这样

var most_expensive = full_xml
  .Element("ItemDetails")
  .Elements(vco + "ItemDetail")
  .Elements(cac + "Item")
  .OrderBy(el =>        
        el.Elements(cac + "RecommendedRetailPrice")
          .Select(rrp => (double?) rrp.Elements(cbc + "PriceAmount"))
          .FirstOrDefault() ?? 0.0
   ).Last();

创建一个方法来执行您的操作(我知道我知道我们都想要漂亮的Lambda表达式,而不是创建新的方法)

private static double GetPrice(XElement retail, string priceElement)
{
    // short circuting will stop as soon as the statement is false
    if (retail != null && retail.Element(priceElement) != null)
    {
        return Convert.ToDouble(retail.Element(priceElement).Value);
    }
    return 0.0;
}

你的链接现在看起来像这样棒…

var most_expensive = full_xml
                    .Element("ItemDetails")
                    .Elements(vco + "ItemDetail")
                    .Elements(cac + "Item")
                    .OrderBy(el => GetPrice(el.Element(cac + "RecommendedRetailPrice"), cbc + "PriceAmount"))
                    .Last();

我相信这个方法可以在其他需要获取价格的地方被重用,所以它将是一段有用的代码。

如果需要,还可以使用三元操作符使方法更短

private static double GetPrice(XElement retail, string priceElement)
{
    return (retail != null && retail.Element(priceElement) != null) ?
            Convert.ToDouble(retail.Element(priceElement).Value) : 0.0;
}

删除命名空间:

var doc = XDocument.Parse(@"
    <ItemDetails>
        <ItemDetail>
            <Item>
                <RecommendedRetailPrice>
                    <PriceAmount>4.95</PriceAmount>
                </RecommendedRetailPrice>
            </Item>
        </ItemDetail>
    </ItemDetails>
");
var result = doc
    .Element("ItemDetails")
    .Elements("ItemDetail")
    .Elements("Item")
    .OrderBy(item =>
        item.Elements("RecommendedRetailPrice")
            .Elements("PriceAmount")
            .Select(pa => Convert.ToDouble(pa.Value,
                              CultureInfo.InvariantCulture))
            .FirstOrDefault())
    .Last();
Console.WriteLine(result);