【问题标题】:Linq to XML Simple QueryLinq to XML 简单查询
【发布时间】:2010-02-05 04:40:39
【问题描述】:

我没有得到这个 Linq 的东西。我可以编写复杂的 SQL 查询并编写了一些 xpath。我正在尝试学习 Linq to XML,尽管我在谷歌上仔细研究了每一个简洁的例子,但我还是无法通过我的第一次尝试。

给定 XML:

<Manufacturer ManufacturerName="Acme">
 <Model ModelName="RobotOne">
  <CommandText CommandName="MoveForward">MVFW</CommandText>
  <CommandText CommandName="MoveBack">MVBK</CommandText>

查询输入是“Acme”、“RobotOne”、“MoveBack”,我要输出“MVBK”

不确定这是否是构建 XML 的最佳方式,我将如何使用元素而不是属性来实现它?厂家少,型号少,代码多

【问题讨论】:

    标签: .net linq linq-to-xml


    【解决方案1】:

    如果您在整个 LINQ to XML 方面遇到困难,我建议您稍微退后一步,大致了解一下 linq。我相信 linq 非常值得付出努力,并且会回报你很多倍——尤其是当我们迁移到多核世界时。

    我建议您暂时忘记 XML 部分,只考虑 linq 在做什么——在您了解 linq 是什么之后,它将使 XML 的专门内容更容易理解。考虑到这两点有助于我考虑 linq:

    1. 它在实现接口 IEnumerable 的任何东西上表达标准的“运算符”(扩展方法)。换句话说,linq 只是一个方法调用。有些方法采用代码 (Func),有些方法 (First()) 没有。
    2. 这使您不必担心这些运算符如何为您提供结果,而更多地只是声明您的意图

    我们命令式语言的人很难放弃如何获得结果的分钟(过度)规范。 linq 基本上给我们的是一种声明我们想要一个结果而不具体说明如何得到它的方法。这就是为什么,IMO,linq 学习如此重要,因为正是这种将意图与确切的“如何”完全“解耦”才是 linq 真正酷的部分。基本上,它将函数式语言概念移植到了陈旧的命令式 C# 中。

    一个有用的 LINQ 学习辅助工具是编写可以做同样事情的 C# 命令式代码。这样做几次,你会突然看到 linq 为你做的模式。因此,例如,考虑一个“linq-to-object”声明。

    string[] names= new string[] { "kevin", "tristan", jen" };
    var result = names.where(n => n.length() > 3);
    

    C# 中用于 linq 查询的类比是:

    List<string> list = new List<string>();
    foreach(string n in names)
    {
        if (n.length > 3) list.add(n);
    }
    

    我认为在这个例子中很容易看出 linq 是如何与 foreach 做同样的事情(在现实中它实际上非常接近这一点)——但你没有所有的绒毛编译器咕。在 linq 版本中有更少的粘性和更多的意图。另一种说法是 linq 隐式地为你做无聊的样板文件 让你的代码只显示有趣的部分。

    在函数式上,where() 方法是一个高阶函数。这只是意味着它是一个本身接受或返回函数的函数。 Where() 采用 lambda——循环中有趣的部分。 lambda 是 C# 中的匿名函数——因此 Where() 接受一个函数,因此它是高阶。我之所以提到这一点,是因为这个概念非常强大,是理解 linq 的关键,它让我们深入了解 linq 实际上是如何嵌入到 C# 中的全新编程模型(函数式编程)。获得这种“高阶”对于理解 linq 非常有帮助。

    我认为,使用上述的直接 List 查询是了解 linq 工作原理的最佳方式。在您发现自己对严格的 L-2-O 查询感到满意之后,再看看 XML 的东西——我想您会发现它们会更有意义。

    随着 .NET 4.0 中即将推出的响应式框架,我们将看到 linq 不仅扩展到“拉”查询,还扩展到“推送”场景。即:集合将在更改时触发代码。我相信函数式编程的概念(其中 linq 是 C# 的通道)是我们处理并发和并行问题最有可能的基本方法,因此学习它们非常重要——不仅仅是为了让我们的代码更简洁。

    【讨论】:

    • 我想我需要这样做。我以为 Linq 只是猪拉丁 SQL,但事实并非如此。你可以用两种方式编写东西,查询或点表示法或两者的混合,这无济于事。我通过查看已制定的示例来学习,但因为 Linq 是新的,所以周围并不多。我被一个简单的 Linq 查询弄糊涂了,我可以用 SQL 或 xpath 写在一行上。
    【解决方案2】:

    假设您正在使用此初始化:

    string xml = @"
    <Manufacturer ManufacturerName='Acme'>
     <Model ModelName='RobotOne'>
      <CommandText CommandName='MoveForward'>MVFW</CommandText>
      <CommandText CommandName='MoveBack'>MVBK</CommandText>
    </Model>
    </Manufacturer>";
    
    XElement topElement = XElement.Parse(xml);
    

    您可以通过以下 LINQ 查询获得结果:

    string commandText = topElement.Elements("Model")
        .Where(element => (string)element.Attribute("ModelName") == "RobotOne")
        .Elements("CommandText")
        .Where(element => (string)element.Attribute("CommandName") == "MoveBack")
        .Select(element => element.Value)
        .FirstOrDefault();
    

    如果此 XML 嵌套得更深,您将需要更多 Select/Where 组合。

    【讨论】:

    • 我按照您的操作进行操作,但是当我将其加载到 LINQPad 中时,它返回 null。如果我取出 OrDefault,它会显示“InvalidOperationException:序列不包含元素”典型的简洁和误导性 Microsoft 错误消息,序列确实包含元素。还有其他问题,但它并没有告诉我什么。我希望学习如何将其编写为查询,但现在我只想让任何东西工作。我想我会改用 Java(开个玩笑)
    • FirstOrDefault 调用选择 IEnumerable 的第一个值,如果没有值,则选择 IEnumerable 的类型的默认值(在本例中为字符串)。您很可能会遇到问题 b/c topElement 未正确初始化。我会将我的初始化代码添加到示例中。
    • 就是这样!我下车了。我还是有兴趣写一个查询,不会像下面的回复那么难吧?
    • 查询语法是同一枚硬币的另一面。见msdn.microsoft.com/en-us/library/bb397947.aspx。将一个转换为另一个通常很简单。话虽如此,我来自 XPath 背景,LINQ 的主要好处之一是能够使用相同的语法针对不同的源(对象/XML)编写查询。如果您只是使用 XML,则可能需要考虑使用 XPath,除非您发现 LINQ 的性能更好,或者您只是想学习它...
    【解决方案3】:

    不完全是答案,但 XPath 不会让代码更简单一些吗?

      var result1 = XDocument
                .Load("test.xml")
                .XPathSelectElements("/Manufacturer[@ManufacturerName='Acme']/Model[@ModelName='RobotOne']/CommandText[@CommandName='MoveBack']")
                .FirstOrDefault().Value;
    

    【讨论】:

    • 我已经知道怎么写xpath了。我正在尝试学习 Linq to XML。我发现的所有示例都有一个 where 子句,并专注于返回一个列表。我需要多个 where 子句并希望返回一个值。
    【解决方案4】:

    我已经扩展了您的 XML 并演示了如何获取 MoveBack 命令文本。我还添加了一个示例来获取特定制造商的机器人模型并列出每个机器人的命令。第一个示例被分解以演示如何遍历 XML 结构以一次获取一个元素。第二个示例在一个查询中完成。当然,这取决于您对数据的了解程度。如果您希望结果不存在,您应该使用SingleOrDefault 并在使用结果之前检查是否为空。此外,如果属性不存在,检查属性也很重要。此代码假定 XML 是完整的。

    关于 XML 的结构,它看起来不错。保持 CommandText 通用允许支持不同的命令。如果命令始终相同,则它们可能是它们自己的元素。您可以将模型命名为自己的元素,但保留原样(作为属性)是有意义的。

    string input = @"<root>
        <Manufacturer ManufacturerName=""Acme"">
            <Model ModelName=""RobotOne"">
                <CommandText CommandName=""MoveForward"">MVFW</CommandText>
                <CommandText CommandName=""MoveBack"">MVBK</CommandText>
            </Model>
            <Model ModelName=""RobotTwo"">
                <CommandText CommandName=""MoveRight"">MVRT</CommandText>
                <CommandText CommandName=""MoveLeft"">MVLT</CommandText>
            </Model>
        </Manufacturer>
        <Manufacturer ManufacturerName=""FooBar Inc."">
            <Model ModelName=""Johnny5"">
                <CommandText CommandName=""FireLaser"">FL</CommandText>
                <CommandText CommandName=""FlipTVChannels"">FTVC</CommandText>
            </Model>
            <Model ModelName=""Optimus"">
                <CommandText CommandName=""FirePlasmaCannon"">FPC</CommandText>
                <CommandText CommandName=""TransformAndRollout"">TAL</CommandText>
            </Model>
        </Manufacturer>
    </root>";
    var xml = XElement.Parse(input);
    
    // get the Manufacturer elements, then filter on the one named "Acme".
    XElement acme = xml.Elements("Manufacturer")
                       .Where(element => element.Attribute("ManufacturerName").Value == "Acme")
                       .Single(); // assuming there's only one Acme occurrence.
    
    // get Model elements, filter on RobotOne name, get CommandText elements, filter on MoveBack, select single element
    var command = acme.Elements("Model")
                      .Where(element => element.Attribute("ModelName").Value == "RobotOne")
                      .Elements("CommandText")
                      .Where(c => c.Attribute("CommandName").Value == "MoveBack")
                      .Single();
    
    // command text value
    string result = command.Value;
    Console.WriteLine("MoveBack command: " + result);
    
    // one unbroken query to list each FooBar Inc. robot and their commands
    var query = xml.Elements("Manufacturer")
                   .Where(element => element.Attribute("ManufacturerName").Value == "FooBar Inc.")
                   .Elements("Model")
                   .Select(model => new {
                        Name = model.Attribute("ModelName").Value,
                        Commands = model.Elements("CommandText")
                                        .Select(c => new {
                                            CommandName = c.Attribute("CommandName").Value,
                                            CommandText = c.Value
                                        })
                   });
    
    foreach (var robot in query)
    {
        Console.WriteLine("{0} commands:", robot.Name);
        foreach (var c in robot.Commands)
        {
            Console.WriteLine("{0}: {1}", c.CommandName, c.CommandText);
        }
        Console.WriteLine();
    }
    

    如果您决定改用 XDocument,则需要使用 Root:xml.Root.Elements(...)

    【讨论】:

    • 与单行 xpath 语句相比,这相当冗长。 Linq to XML 真的那么复杂吗?
    • @Phil06 虽然当 XML 不一致并且必须进行空值检查时确实会出现问题,但它并不太复杂。我可以同意它很冗长,有些人也有同样的看法:stackoverflow.com/questions/1442585/… 但是,.NET 只支持 XPath 1.0,因此 LINQ 可以弥补 XPath 2.0 功能在不依赖安装某些 3rd 方的情况下有用的场景提供这些功能的库。
    • 谢谢。我现在已经能够运行一些查询了。 LinqPad 非常适合学习,它只是拒绝了我写的所有东西,因为我没有得到它。现在我开始有了感觉。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-26
    • 1970-01-01
    • 2023-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多