通过可能缺少的子节点在LINQ中对XML进行排序

时间:2013-06-11 09:20:53

标签: c# xml linq-to-xml

我正在处理以下结构的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。 不过,我想按各自Items对所有RecommendedRetailPrices进行排序。 所以我最终得到了以下代码可以正常工作,但必须在一些可怕的嵌套if语句的每个阶段检查空值:

//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级向下的可选子节点对事物进行排序。

任何帮助表示感谢。

3 个答案:

答案 0 :(得分:0)

如果可以存在零个或一个元素,那么我将使用Elements()而不是Element(),然后选择FirstOrDefault。然后使用XElement的{​​{3}}来获取您的价值。

像这样的东西

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

答案 1 :(得分:0)

创建一个执行操作的方法(我知道我知道我们都想制作漂亮的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;
}

你的linq现在看起来很棒......

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

我确信这个方法可以在其他需要付出代价的地方重复使用,以便以后可以使用这段代码。

如果需要,也可以使用Ternary运算符缩短方法

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

答案 2 :(得分: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);