XLINQ 简介 第 3 部分(共 3 部分)






4.91/5 (63投票s)
LINQ 简介
目录
引言
.NET 3.0 现已发布,所以我们现在应该都知道它了吧。天啊,感觉 .NET 2.0 推出没多久。那些还没意识到的人,.NET 3.0 实际上包含了相当多的新东西,例如:
- Windows Workflow Foundation (WWF):管理对象生命周期/持久对象存储
- Windows Communication Foundation (WCF):新的通信层
- Windows Presentation Foundation (WPF):新的演示层 (XAML)
- Windows Cardspace:提供基于标准的解决方案,用于处理和管理各种数字身份
正如你所看到的,有很多东西需要学习。我正在学习 WPF/WCF,但我对一个叫做 LINQ 的小玩意也很感兴趣,我认为它将成为 .NET 3.5 和 Visual Studio "Orcas"(目前的名称)的一部分。LINQ 将为 C# 和 VB.NET 添加新功能。LINQ 有三种“口味”:
- LINQ:用于内存中对象的语言集成查询(在 2007 年 3 月的 Orcas CTP 中更名为 LINQ to Objects)
- DLINQ:用于数据库的语言集成查询(在 2007 年 3 月的 Orcas CTP 中更名为 LINQ to SQL)
- XLINQ:用于 XML 的语言集成查询(在 2007 年 3 月的 Orcas CTP 中更名为 LINQ to XML)
LINQ 非常酷,我最近一直在研究它,所以我想写一篇文章,介绍我在 LINQ/DLINQ/XLINQ 领域学到的东西,希望它能对一些好心人有所帮助。本文将重点介绍 XLINQ,是三篇文章系列中的第三篇。
文章系列内容如下:
- 第一部分:将全部关于标准 LINQ,它用于查询内存中的数据对象,如 List、数组等等
- 第二部分:将关于使用 DLINQ,它是用于数据库数据的 LINQ
- 第三部分(本文):将关于使用 XLINQ,它是用于 XML 数据的 LINQ
关于演示应用程序的注意事项
在我开始向大家灌输更多他们可能无法处理的信息之前,让我先简单谈谈本文附带的文件。
本文包含两个独立的 zip 文件。这些 zip 文件是两个演示应用程序。
演示应用程序 1
基于使用 Visual Studio 2005 和 .NET 2.0 框架,以及 2006 年 5 月的 LINQ CTP,可从此处获取。
看到 XML 语法高亮了吗?这都归功于免费且出色的 Fireball 代码高亮工具,可从 Code Project 此处获取。
主窗口允许用户尝试各种 XLINQ 活动,例如:
- 创建新的 XML 树部分
- 创建新的 XML 文档
- 打开现有 XML 文档
- 查找根节点属性
- 使用本系列第一部分中描述的标准 LINQ 查询运算符查询 XML
- 更新 XML 部分
- 删除 XML 部分
第一个演示应用程序中的第二个表单(可从“关于 Web 查询”菜单中获取)使用 XLINQ 通过 Flickr API 字符串从 Flickr 获取 RSS feed。您无需下载任何额外内容即可使其工作,因为它全部包含在代码中。然后使用 XLINQ 查询 Flickr RSS feed(这就是 LINQ 的全部意义,语言集成查询语言,请记住),并将结果通过标准 LINQ 投影放入新结构中,然后使用这些结果填充一个包含表示所收集图像的 `PictureBox` 控件的 `FlowLayoutPanel`。用户还可以自由更改正在执行的 Flickr 搜索类型,并可以翻阅获得的结果。
演示应用程序 2
基于使用安装了WPF 的 Visual Studio 设计器的 Visual Studio 2005,或使用 Expression BLEND 和 Visual Studio 2005 组合,或者如果您更喜欢在 WordPad 中编写,也可以使用 WordPad。
显然,由于它是 WPF,您还需要 .NET 3.0 框架,以及 2006 年 5 月的 LINQ CTP,可从此处获取。其目的是让那些想了解 WPF/XAML 如何与 LINQ 配合使用的人可以查看此项目。如果不懂这段代码也没关系,因为此应用程序将成为我下一篇文章的主题。目前将其包含在此处只是为了引起兴趣,以表明 LINQ 和 WPF 可以很好地协同工作。
这基本上与演示应用程序 1 的第二个表单做的是相同的事情,它只是看起来更好看,并展示了一些很酷的 WPF 功能,例如:
- 动画
- 数据绑定
- 模板化
- 样式
- 资源
- 当然,还有 XLINQ 的使用
这个应用程序实际上并非专门针对 XLINQ,但这只是我想尝试的东西,所以我想,既然我做了,为什么不把它包含在这里呢。正如我所说,如果你不理解演示应用程序 2,不用担心,我会在另一篇文章中详细讲解它。它在这里只是为了引起兴趣。
解释此 WPF 应用程序的完整文章可从此处找到
必备组件
- 要运行本文随附的代码,您需要安装 2006 年 5 月的 LINQ CTP,可从此处获取。目前有一个新的 2007 年 3 月 CTP 可用,但完整安装需要大约 4GB(因为它不仅包含 LINQ,还包含代号为“Orcas”的整个下一代 Visual Studio),而且相当复杂,并且可能还会改变,因此 2006 年 5 月的 LINQ CTP 对于本文试图演示的目的来说就足够了。
- 演示应用程序 1 使用了出色的 Fireball 代码高亮器,可从 Code Project 此处获取。
- 演示应用程序 2 使用了 .NET 3.0 框架,可从此处下载。
那么,让我们深入了解 XLINQ
那么,XLINQ 是什么呢?嗯,它是 LINQ(语言集成查询)的一种“口味”,将成为 .NET 3.5 的一部分,并且肯定会成为下一版 Visual Studio(目前称为 Orcas)的一部分。
回顾一下,LINQ 处理内存中的对象,如数组和 `List` 和 `Dictionary` 对象,而 DLINQ 或 LINQ over SQL 处理实体和数据库交互。那么,你猜 XLINQ 是关于什么的呢?嗯,它实际上是 XML over LINQ。
那么,我们还能谈谈 XLINQ 些什么呢?我们来问问微软关于 XLINQ 的营销宣传语是什么。我问了他们,他们是这样说的:
“XLinq 从一开始就是围绕 XML 的语言集成查询开发的。它利用了标准查询运算符,并添加了特定于 XML 的查询扩展。从 XML 的角度来看,XLinq 提供了 XQuery 和 XPath 的查询和转换能力,并将其集成到实现了 LINQ 模式的 .NET Framework 语言(例如 C#、VB 等)中。这为支持 LINQ 的 API 提供了统一的查询体验,并允许您将 XML 查询和转换与其他数据源的查询结合起来。我们将在第 3 节“使用 XLinq 查询 XML”中更深入地探讨 XLinq 的查询能力。”
与 XLinq 的语言集成查询功能同样重要的是,XLinq 代表了一个全新的、现代化的内存中 XML 编程 API。XLinq 被设计成一个更清晰、更现代化的 API,同时快速且轻量。XLinq 使用现代语言特性(例如泛型和可空类型),并通过各种创新与 DOM 编程模型区分开来,以简化针对 XML 的编程。即使没有语言集成查询功能,XLinq 也代表着 XML 编程向前迈出了重要一步。本文档的下一节“编程 XML”将提供有关 XLinq 内存中 XML 编程 API 方面的更多详细信息。
(摘自 XLINQ overview.doc,可在 LINQ Project [1] 网站上获取)
我认为这是对 XLINQ 是什么以及它有望成为什么的很好的描述。当然,它应该是一个好的销售宣传语,因为 XLINQ 是微软的发明。但这一切对普通开发者意味着什么呢?
本文将通过文字注释、代码片段和真实工作示例来尝试演示这一点。
除非另有说明,本文中所示的示例代码仅供讨论之用。但请放心,我已尽力仔细查阅 XLINQ 文档,并制作了一个不错的小型演示应用程序,其中包含真实的工作示例,展示了如何使用 XLINQ 完成一些最常见的 XML 相关任务。因此,此工作代码已包含在演示应用程序 1 中,当我专门使用演示应用程序 1 中的代码时,我将显示以下图像,让您知道可以在代码中查找工作示例。
当您看到此图像时,表示我已在演示应用程序 1 中为您创建了一个工作示例。
显然,我没有提供 XLINQ 所有功能的示例,因为我也有自己的生活(虽然不多,但总还是有的)。所以,我不得不将进一步的探索留给读者作为练习。
本文的结构是按照以下项目进行探讨的,我认为这些项目是每个使用 XML 的开发者都应该知道的非常重要的问题。
- XLINQ 类结构
- 创建新的 XML
- 保存 XML
- 加载 XML
- 遍历 XML
- 使用根节点
- 追加新元素
- 更新现有元素
- 删除现有元素
- 查询 XML
- 使用 XLINQ 访问第三方 XML 数据
我希望通过这一系列项目的讲解,读者最终能对 XLINQ 能做什么以及如何取代 DOM 和 XPath 有基本的了解。
那么,我们继续吧?
XLINQ 类
XLINQ 整体类层次结构如下图所示。
- 尽管 XElement 在类层次结构中位置较低,但它是 XLinq 中的基本类。XML 树通常由 XElement 树组成。XAttribute 是与 XElement 关联的名称/值对。仅在必要时才创建 XDocument,例如用于保存 DTD 或顶级 XML 处理指令 (XProcessingInstruction)。所有其他 XNode 只能是 XElement 下的叶节点,或者可能是 XDocument(如果它们存在于根级别)。
- XAttribute 和 XNode 是同级,不是派生自共同基类(除了对象)。这反映了 XML 属性实际上是与 XML 元素关联的名称值对,而不是 XML 树中的节点。这与 W3C DOM 形成对比。
- XText 在此版本的 XLinq 中公开,但如上所述,最好将其视为半隐藏的实现细节,除非需要公开文本节点。作为用户,您可以将元素或属性中的文本值作为字符串或其他简单值获取。
- 唯一可以有子节点的 XNode 是 XContainer,这意味着 XDocument 或 XElement。一个 XDocument 可以包含一个 XElement(根元素)、一个 XDeclaration、一个 XDocumentType 或一个 XProcessingInstruction。一个 XElement 可以包含另一个 XElement、一个 XComment、一个 XProcessingInstruction 和文本(可以以各种格式传递,但在 XML 树中将表示为文本)。
摘自 XLINQ overview.doc,可在 LINQ Project [1] 网站上获取。
创建新的 XML
为了理解 XLINQ 与现有 XML 文档创建实践的不同之处,让我们考虑以下传统 DOM(文档对象模型)代码片段,它创建一个小的 XML 文档。
XmlDocument doc = new XmlDocument();
XmlElement name = doc.CreateElement("name");
name.InnerText = "Patrick Hines";
XmlElement phone1 = doc.CreateElement("phone");
phone1.SetAttribute("type", "home");
phone1.InnerText = "206-555-0144";
XmlElement phone2 = doc.CreateElement("phone");
phone2.SetAttribute("type", "work");
phone2.InnerText = "425-555-0145";
XmlElement street1 = doc.CreateElement("street1");
street1.InnerText = "123 Main St";
XmlElement city = doc.CreateElement("city");
city.InnerText = "Mercer Island";
XmlElement state = doc.CreateElement("state");
state.InnerText = "WA";
XmlElement postal = doc.CreateElement("postal");
postal.InnerText = "68042";
XmlElement address = doc.CreateElement("address");
address.AppendChild(street1);
address.AppendChild(city);
address.AppendChild(state);
address.AppendChild(postal);
XmlElement contact = doc.CreateElement("contact");
contact.AppendChild(name);
contact.AppendChild(phone1);
contact.AppendChild(phone2);
contact.AppendChild(address);
XmlElement contacts = doc.CreateElement("contacts");
contacts.AppendChild(contact);
doc.AppendChild(contacts);
虽然这很容易做到,但实际输出到 XML 文档的结构却不是很清晰。对于这个微不足道的例子,可以推断出来,但如果这是一个大型 XML 文档,就不会那么清晰了。我们来看看 XLINQ 是如何完成同样的工作的呢?
XElement contacts =
new XElement("contacts",
new XElement("contact",
new XElement("name", "Patrick Hines"),
new XElement("phone", "206-555-0144",
new XAttribute("type", "home")),
new XElement("phone", "425-555-0145",
new XAttribute("type", "work")),
new XElement("address",
new XElement("street1", "123 Main St"),
new XElement("city", "Mercer Island"),
new XElement("state", "WA"),
new XElement("postal", "68042")
)
)
);
首先要说的是,这代码量少了很多,而且树结构几乎是显而易见的——它几乎像 XML 文档一样容易阅读。另一件需要注意的是,在实际语法中,我们没有看到 `createElement()` 或 `AppendChild()` 方法。相反,我们看到 `new XElement`,这某种程度上更有意义,至少在实际阅读时是这样。我个人认为,懂 XML 但以前没用过 DOM 的人可能会更好地理解这种新语法,因为它在实际术语的使用上似乎更接近。一个 XML 专家知道新元素就是新元素,而不是 `AppendChild()`。我想最终,这 really 归结为个人偏好。
演示应用程序 1 包含三个菜单,以进一步演示此概念,请查看:
- 编程 LINQ\创建元素菜单
- 编程 LINQ\创建整个文档菜单
- 编程 LINQ\创建带命名空间的元素菜单
保存 XML
`XDocument` 和 `XElement` 都公开了一个重载的 Save() 方法,选项如下:
XDocument.Save(string fileName)
此选项仅将 XElements 内容保存到 fileName 参数指定的路径。
XDocument.Save(System.Xml.XmlWriter writer)
此选项仅将 XElements 内容保存到 XmlWriter
XDocument.Save(TextWriter textWriter)
此选项仅将 XElements 内容保存到 TextWriter
XDocument.Save(string fileName, bool preserveWhitespace)
此选项仅将 XElements 内容保存到 fileName 参数指定的路径,并保留任何空白。
对于 `XElement` 存在相同的方法
加载 XML
`XDocument` 和 `XElement` 都公开了一个重载的 Load() 方法,选项如下:
XDocument.Load(string uri)
此选项只是将 XML 元素从 uri 指定的位置加载到一个新的 `XDocument` 中
XDocument.Save(System.Xml.XmlReader reader)
此选项仅将 `XmlReader` 的内容读入新的 `XDocument`
XDocument.Save(TextReader textReader)
此选项仅将 `TextReader` 的内容读入新的 `XDocument`
XDocument.Save(uri fileName, bool preserveWhitespace)
此选项仅将 fileName 参数指定的文件内容读入新的 `XDocument`,并保留任何空白。
还有另一种加载 XML 的可能性。我们可以通过调用 `XDocument.Parse` 方法来解析字符串,所以我们来看看这个例子吧?
XElement contacts = XElement.Parse(
@"<contacts>
<contact>
<name>Patrick Hines</name>
<phone type=""home"">206-555-0144</phone>
<phone type=""work"">425-555-0145</phone>
<address>
<street1>123 Main St</street1>
<city>Mercer Island</city>
<state>WA</state>
<postal>68042</postal>
</address>
<netWorth>10</netWorth>
</contact>
</contacts>");
一旦我们加载或解析了一个字符串,XLINQ 的全部功能就可以使用了。我们可以遍历节点,检查属性,创建新内容,删除现有内容。
对于 `XElement` 存在相同的方法
演示应用程序 1 包含 2 个区域来进一步演示此概念,请查看:
- 编程 LINQ\打开现有文件菜单(显示 `Load()` 方法)
- 查看 `createQuerySource()` 方法(显示 `Parse()` 方法)
遍历 XML
XLINQ 提供了获取 `XElement` 子节点的方法。要获取 `XElement`(或 `XDocument`)的所有子节点,可以使用 `Nodes()` 方法。
例如,如果我们有以下 XML
并运行以下遍历
foreach (XElement c in contacts.Nodes())
{
....
}
我们实际上会得到原始 XML。
那么如何访问子元素呢?实际上,语法非常相似。我们来看一个例子吧?
foreach (XElement c in contacts.Elements("contact").Elements("address"))
{
....
}
如果我们使用的是 `XDocument`,它会返回 `IEnumerable<object>`,因为您可能会有文本与 `XDeclaration`、`XComment` 和 `XProcessingInstruction` 以及 `XElement` 等其他 XLINQ 类型混合在一起
那么我们如何处理这个问题呢?我们的 `foreach (XElement in ....` 将在非 `XElement` 的情况下失败。我们该怎么办呢?嗯,很简单,我们这样做:
foreach (XElement c in contactsDoc.Nodes().OfType<XElement>())
{
....
}
演示应用程序 1 包含三个菜单,以进一步演示此概念,请查看:
- 遍历\显示所有菜单(显示所有元素)
- 遍历\仅显示元素菜单(仅显示 `XElement` 类型)
- 遍历\显示子元素菜单(显示 Contact 元素的地址子元素)
使用根节点
假设我们有以下 XML 结构
<!--XLinq Contacts XML Example-->
<?MyApp 123-44-4444?>
<contacts numContacts="1">
<contact>
<name>sacha barber</name>
<phone type="home">01273 45426</phone>
<phone type="work">01903 205557</phone>
<address>
<street1>palmeira square</street1>
<city>brighton</city>
<county>east sussex</county>
<postcode>BN3 2FA</postcode>
</address>
</contact>
</contacts>
我们希望处理根节点属性。通常(如果使用 DOM),我们需要获取对文档节点的引用,然后使用它来获取属性值。XLINQ 的处理方式略有不同。
XElement root = contactsDoc.Root;
sb.Append(
"Examining root element [" + root.Name + "] for attributes\r\n\r\n");
if (root.HasAttributes)
{
foreach (XAttribute xa in root.Attributes())
{
sb.Append(
"Found Attibute [" + xa.Name.ToString() + "] Value=" + xa.Value);
}
}
txtResults.Document.Text = sb.ToString();
这足以获取根节点的属性。这假设之前的 XML 结构已加载到名为 contactsDoc 的 `XDocument` 中。结果可以从演示应用程序 1 的此屏幕截图中看到。
演示应用程序 1 包含 1 个菜单,以进一步演示此概念,请查看:
- 编程 LINQ\获取根节点属性值菜单(显示如何获取根节点属性)
追加新元素
使用 XLINQ 追加新内容相当简单。关键在于获取对您希望添加新内容的特定 `XDocument` 或 `XElement` 的引用。之后的工作就相当容易了。我们来看一个例子吧?
此示例假定存在一个名为 contactsDoc 的预先存在的 `XDocument`。
//obtain the root
XElement root = contactsDoc.Root;
//add new contact
root.Add(new XElement("contact",
new XElement("name", "melissa george"),
new XElement("phone", "01273 999999",
new XAttribute("type", "office")),
new XElement("phone", "01903 888888",
new XAttribute("type", "work")),
new XElement("address",
new XElement("street1", "churchill square"),
new XElement("city", "brighton"),
new XElement("county", "east sussex"),
new XElement("postcode", "BN3 4RG")
)
)
);
这段代码创建了一个新的 contact `XElement`,然后将其追加到现有 `XDocument` contactsDoc 的根元素中。您只需这样做,获取要添加内容的对象,然后使用 `Add()` 方法添加内容。
演示应用程序 1 包含 1 个菜单,以进一步演示此概念,请查看:
- 编程 LINQ\追加新联系人菜单(显示如何向当前 `XDocument` 的根元素添加新联系人)
更新现有元素
更新现有内容是任何处理 XML 的人都迟早需要做的事情。这可能包括更新整个元素的内容,或者仅仅是更新现有元素的一个子元素。因为我很好,我会向你展示两种情况。
我们假设有以下要更新的源 XML。
更新整个元素
对于我们想要用新内容更新整个元素的情况,我们只需像之前一样获取要应用替换内容的对象,然后使用 `ReplaceContent()` 方法替换它。
//obtain a single contact
IEnumerable<XElement> singleContact = (from c in contactsDoc.Root.Elements(
"contact")
where ((string) c.Element(
"name")).Equals("sarah dudee")
select c);
//update contact, should only be 1
foreach (XElement xe in singleContact)
{
//use the ReplaceContent method to do the replacement
xe.ReplaceContent(new XElement("name", "sam weasel"),
new XElement("phone", "01273 111111",
new XAttribute("type", "office")),
new XElement("phone", "01903 33333",
new XAttribute("type", "work")),
new XElement("address",
new XElement("street1", "the drive"),
new XElement("city", "brighton"),
new XElement("county", "east sussex"),
new XElement("postcode", "BN3 4RG")
)
);
}
这足以替换整个 Contact 元素,其中现有的 Contact 元素(名为“sarah dudee”)被替换。
演示应用程序 1 包含 1 个菜单,以进一步演示此概念,请查看:
- 编程 LINQ\更新整个联系人元素菜单(显示如何更新当前 `XDocument` 的整个联系人元素)
仅更新子元素
对于我们只想用新内容更新现有元素一部分的情况,我们只需像之前一样获取要应用替换内容的对象,但这次我们必须直接操作元素数据。
//obtain the firstContact
IEnumerable<XElement> firstContact = ( from c in contactsDoc.Root.Elements(
"contact")
select c).Take(1);
//update contact, should only be 1
foreach (XElement xe in firstContact)
{
//UPDATE METHOD 1
xe.Element("address").SetElement("city", "MANCHESTER");
//UPDATE METHOD 2
xe.Element("address").Element("postcode").ReplaceContent("MN1");
}
这足以替换 Contact 元素的子元素。
演示应用程序 1 包含 1 个菜单,以进一步演示此概念,请查看:
编程 LINQ\仅更新 1 个联系人详情菜单(显示如何更新当前 `XDocument` 中现有元素的子元素)
删除现有元素
删除现有内容也是任何处理 XML 的人都迟早需要做的事情。
我们假设有以下源 XML,我们将从中删除内容
因此,像以前一样,我们只需获取要删除的对象,然后使用 `Remove()` 方法将其删除。
//obtain the firstContact
IEnumerable<XElement> firstContact = ( from c in contactsDoc.Root.Elements(
"contact")
select c).Take(1);
//update contact, should only be 1
foreach (XElement xe in firstContact)
{
xe.Element("address").Element("county").Remove();
}
这足以删除整个 Contact 元素,其中第一个现有 Contact 元素被删除(请记住,我们可以使用任何标准 LINQ 查询运算符,所以我只使用了 Take(1),它给我们第一个 Contact 元素)。
演示应用程序 1 包含 1 个菜单,以进一步演示此概念,请查看:
- 编程 LINQ\删除联系人菜单(显示如何从当前 `XDocument` 中删除整个联系人元素)
查询 XML
回想第一部分,我介绍了 LINQ 标准查询运算符,你猜怎么着,XLINQ 使用这些相同的标准查询运算符,允许程序员查询 XML 树,以便提取树的某些部分进行操作。这与 XPath 的作用非常相似。
这里唯一的限制是你的标准查询运算符技能有多好。那么我们看一些例子吧?我给你准备了四个小例子。
以下所有查询都基于相同的基本 XML,该 XML 在演示应用程序 1 的 `createQuerySource()` 方法中创建。
获取第一个联系人查询
XElement contactsElements = createQuerySource();
XElement res = new XElement("contacts",
(from c in contactsElements.Elements("contact")
select new XElement("contact",
c.Element("name"),
new XElement("phoneNumbers", c.Elements("phone"))
)).Take(1)
);
获取所有电话号码查询
XElement contactsElements = createQuerySource();
foreach (XElement phone in contactsElements.Elements("contact").Elements(
"phone"))
{
//do something with the phone XElement
}
获取特定客户查询
XElement contactsElements = createQuerySource();
....
....
//look for this Element with the correct name requested
IEnumerable<XElement> cont = (from c in contactsElements.Elements("contact")
where (string) c.Element("name") == userName
select c);
....
....
仅获取拥有家庭电话号码的客户查询
XElement contactsElements = createQuerySource();
....
....
//look for this Element with the correct name requested
IEnumerable<XElement> res = (from c in contactsElements.Elements("contact")
where (string) c.Element(
"phone").Attribute("type") == "home"
select c);
....
....
演示应用程序 1 包含 4 个菜单,以进一步演示此概念,请查看:
- 查询\获取第一个联系人菜单(获取第一个联系人元素)
- 查询\获取电话号码菜单(获取所有联系人元素的电话号码)
- 查询\获取特定客户菜单(提示用户输入联系人姓名,并获取具有该姓名的联系人,如果存在)
- 查询\获取拥有家庭电话号码的客户菜单(获取所有拥有家庭电话号码属性的联系人)
使用 XLINQ 访问第三方 XML 数据
那么,我们让 XLINQ 从 RSS Feeds 中抓取一些数据来显示图片怎么样?毕竟,它应该能够处理任何 XML 数据,即使它不是本地的。我决定使用 Flickr 作为数据源,因为我知道它有一个 API,可以用于获取带有图片 URL 的 RSS Feeds。图片相当漂亮,也适合在 Windows Form 应用程序中显示。这个概念基于我在 Omar Al Zabir 的精彩文章中看到的内容,该文章可在此处获取。
那么我们来看看实现这个的代码吧?类图如下:
如图所示,有一个表单,上面有一些 `Button`(btnGo/btnPrev/btnNext)和一个 `ComboBox`(cmbSearchType),用于从 Flickr 创建正确的参数化 RSS feed 请求。Flickr 请求由 `RSSImageFeed` 类发出,该类返回一个 `IEnumerable<PhotoInfo>` 对象,该对象表示当前 RSS Feed 请求找到的照片。
真的就是这样了。
现在是一些代码。说实话,它非常简单。
FlickrRSSGrabber
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Query;
using System.Xml.XLinq;
using System.Data.DLinq;
namespace Linq_3
{
public partial class FlickrRSSGrabber : Form
{
private RSSImageFeed RSSImages = new RSSImageFeed();
public FlickrRSSGrabber()
{
InitializeComponent();
}
private void btnGo_Click(object sender, EventArgs e)
{
RSSImages.PageIndex = 0;
doFlickrSearch();
}
private void doFlickrSearch()
{
string searchType = cmbSearchType.SelectedItem.ToString();
string searchWord = searchType.Equals(
"By Search Word") ? this.txtSearchWord.Text : "";
if (searchType.Equals("By Search Word") && string.IsNullOrEmpty(
searchWord))
{
MessageBox.Show(
"You MUST enter a search word, to search by keyword");
}
else
{
getFlickrData(searchType, searchWord);
}
}
private void getFlickrData(string searchType, string searchWord)
{
IEnumerable<PhotoInfo> photos = (
IEnumerable<PhotoInfo>)RSSImages.LoadPictures(
searchType, searchWord);
if (photos.Count() > 0)
{
lblPageIngex.Visible = true;
int pIndex = RSSImages.PageIndex;
lblPageIngex.Text = "Page " + (++pIndex);
//show the images, clearing old ones 1st
pnlFlowPhotos.Controls.Clear();
foreach (PhotoInfo pi in photos)
{
PictureBox pb = new PictureBox();
pb.ImageLocation = @pi.PhotoUrl(true);
pb.SizeMode = PictureBoxSizeMode.AutoSize;
toolTip1.SetToolTip(pb, pi.Title);
pnlFlowPhotos.Controls.Add(pb);
}
setNextPrevStates(RSSImages.IsPrevAvail,
RSSImages.IsNextAvail);
}
else
{
lblPageIngex.Visible = false;
setNextPrevStates(false, false);
}
}
private void setNextPrevStates(bool prevEnabled, bool nextEnabled)
{
btnPrev.Enabled = prevEnabled;
string prevTT = btnPrev.Enabled ? "Click to go back a page" :
"There are no more pages";
toolTip1.SetToolTip(btnPrev, prevTT);
btnNext.Enabled = nextEnabled;
string nextTT = btnNext.Enabled ? "Click to go forward a page" :
"There are no more pages";
toolTip1.SetToolTip(btnNext, nextTT);
}
private void cmbSearchType_SelectedValueChanged(object sender,
EventArgs e)
{
string searchType = cmbSearchType.SelectedItem.ToString();
txtSearchWord.Visible = searchType.Equals(
"By Search Word") ? true : false;
lblSearchWord.Visible = txtSearchWord.Visible;
}
private void btnNext_Click(object sender, EventArgs e)
{
RSSImages.PageIndex++;
doFlickrSearch();
}
private void btnPrev_Click(object sender, EventArgs e)
{
RSSImages.PageIndex--;
doFlickrSearch();
}
private void FlickrRSSGrabber_Shown(object sender, EventArgs e)
{
cmbSearchType.SelectedItem = "Most Recent";
//getData from Flickr
getFlickrData("MOST_RECENT", "");
}
}
}
RSSImageFeed
要理解这个类如何使用 XLINQ 获取 Flickr 数据,首先需要了解 Flickr 提供的原始 XML 数据是什么。可以看出,下面列出了三个可能的 URL,分别用于:
- 最新
- 有趣
- 输入标签
这三个不同的 URL 中的一个会根据用户在上面所示的 `FlickrRSSGrabber` 类中从组合框 (cmbSearchType) 中选择的值而触发。
那么,我们来看看 Flickr 为其中一个请求提供的一小部分内容吧。我们以 MOST_RECENT URL 为例:
http://www.flickr.com/services/rest/?method=flickr.photos.getRecent&api_key=c705bfbf75e8d40f584c8a946cf0834c
我们最终会得到这样的结果。在 FireFox 或您最喜欢的浏览器中自己尝试一下。
<rsp stat="ok">
<photos page="1" pages="10" perpage="100" total="1000">
<photo id="493415381" owner="8201860@N03" secret="dba0cae590" server="225"
farm="1" title="setting up 6" ispublic="1" isfriend="0" isfamily="0"/>
<photo id="493415375" owner="42802631@N00" secret="596a13de8e" server="226"
farm="1" title="DSC01713" ispublic="1" isfriend="0" isfamily="0"/>
<photo id="493392504" owner="59616645@N00" secret="49459bb023" server="191"
farm="1" title="IMG_0841" ispublic="1" isfriend="0" isfamily="0"/>
<photo id="493392488" owner="28532182@N00" secret="ebcdc8d2d8" server="220"
farm="1" title="img_1438" ispublic="1" isfriend="0" isfamily="0"/>
查看此结构,很容易看出这小段 XLINQ 语法(取自下面显示的完整代码列表)是如何工作的。
//select the RSS data from Flickr, and use standard LINQ projection
//to store it within a new PhotoInfo which is an object of my own making
IEnumerable<PhotoInfo> photos = (from photo in xroot.Element(
"photos").Elements("photo")
select new PhotoInfo
{
Id = (string)photo.Attribute("id"),
Owner = (string)photo.Attribute("owner"),
Title = (string)photo.Attribute("title"),
Secret = (string)photo.Attribute("secret"),
Server = (string)photo.Attribute("server"),
Farm = (string)photo.Attribute("Farm"),
}).Skip(pageIndex * columns * rows).Take(columns * rows);
这里几乎有一个直接的映射,而且非常容易阅读。将其与使用 DOM 时必须进行的迭代过程进行比较,或将其与等效的 XPath 进行比较。我个人来说,我更喜欢 XPath 而不是迭代的 DOM 过程,但我更喜欢 XLINQ 过程而不是 XPath 过程。它就是如此容易阅读。
总之,这是为那些想要了解全貌的人提供的完整代码清单。
using System;
using System.Collections.Generic;
using System.Text;
using System.Query;
using System.Xml.XLinq;
using System.Data.DLinq;
namespace Linq_3
{
public class RSSImageFeed
{
private const string FLICKR_API_KEY =
"c705bfbf75e8d40f584c8a946cf0834c";
private const string MOST_RECENT =
"http://www.flickr.com/services/rest/?method=flickr.photos.">http://www.flickr.com/services/rest/?method=flickr.photos. +
"getRecent&api_key=" + FLICKR_API_KEY;
private const string INTERESTING =
"http://www.flickr.com/services/rest/?method=flickr.">http://www.flickr.com/services/rest/?method=flickr. +
"interestingness.getList&api_key=" + FLICKR_API_KEY;
private const string ENTER_TAG = "http://www.flickr.com/services">http://www.flickr.com/services +
"/rest/?method=flickr.photos.search&api_key=" +
FLICKR_API_KEY + "&tags=";
private string url = MOST_RECENT;
private int pageIndex = 0;
private int columns = 5;
private int rows = 2;
private bool prevAvail = false;
private bool nextAvail = false;
public RSSImageFeed()
{
}
public bool IsPrevAvail
{
get { return prevAvail; }
}
public bool IsNextAvail
{
get { return nextAvail; }
}
public int PageIndex
{
set { pageIndex = value; }
get { return pageIndex; }
}
public IEnumerable<PhotoInfo> LoadPictures(string searchType,
string searchWord)
{
switch (searchType)
{
case "Most Recent":
this.url = MOST_RECENT;
break;
case "Interesting":
this.url = INTERESTING;
break;
case "By Search Word":
this.url = ENTER_TAG + searchWord;
break;
default:
this.url = MOST_RECENT;
break;
}
try
{
var xraw = XElement.Load(url);
var xroot = XElement.Parse(xraw.Xml);
//select the RSS data from Flickr, and use standard LINQ
//projection to store it within a new PhotoInfo which is
//an object of my own making
IEnumerable<PhotoInfo> photos = (
from photo in xroot.Element("photos").Elements("photo")
select new PhotoInfo
{
Id = (string)photo.Attribute("id"),
Owner = (string)photo.Attribute("owner"),
Title = (string)photo.Attribute("title"),
Secret = (string)photo.Attribute("secret"),
Server = (string)photo.Attribute("server"),
Farm = (string)photo.Attribute("Farm"),
}).Skip(pageIndex * columns * rows).Take(columns * rows);
//set the allowable next/prev states
int count = photos.Count();
if (pageIndex == 0)
{
this.prevAvail = false;
this.nextAvail = true;
}
else
{
this.prevAvail = true;
}
//see if there are less photos than sum(Columns * Rows)
//if there are less cant allow next operation
if (count < columns * rows)
{
this.nextAvail = false;
}
return photos;
}
catch (Exception ex)
{
return null;
}
}
}
}
PhotoInfo
using System;
using System.Collections.Generic;
using System.Text;
namespace Linq_3
{
public class PhotoInfo
{
private const string FLICKR_SERVER_URL = "http://static.flickr.com/";
private const string FLICKR_PHOTO_URL =
"http://www.flickr.com/photos/";
public string Id;
public string Owner;
public string Title;
public string Secret;
public string Server;
public string Farm;
public bool IsPublic;
public bool IsFriend;
public bool IsFamily;
public string PhotoUrl(bool small)
{
return FLICKR_SERVER_URL + this.Server + '/' +
this.Id + '_' + this.Secret + (small ? "_s.jpg" : "_m.jpg");
}
public string PhotoPageUrl
{
get { return FLICKR_PHOTO_URL + this.Owner + '/' + this.Id; }
}
}
}
所以将这三个类放在一起,我们就能够制作出一个小型的 Flickr 查看器,允许用户搜索最新/有趣或按关键词搜索,并允许用户分页浏览结果,如果照片超过一页的话。不错吧?
正如最初所述,我还包含了这个 XLINQ 实验的 WPF 版本,仅仅是因为我也在学习 WPF,而且我认为它看起来很酷。有些人可能也想看看 WPF 如何与 XML 绑定数据协同工作,就像这个 WPF 应用程序的情况一样。就像我之前说的,如果你不理解 WPF 版本,不用担心,那将是另一篇文章的重点。
就这些
我希望有一些读者已经阅读了本系列文章的第一部分和第二部分,并且能够看到 LINQ 不同“风味”之间的相似之处。
我也希望这篇文章已经表明 XLINQ(或者将来会称之为 LINQ to XML)并不可怕,而且它实际上非常易于使用,即使在使用 RSS 和第三方 XML 数据源时也是如此。
作为本系列的结束语,我只想告诉大家,当我开始时我对 LINQ 了解不多,我只是埋头苦干。其中一些有点令人沮丧,但说实话,我在一天之内就让所有演示应用程序(WPF 那个除外,那是另一回事)完成了我想要的功能,对于一项新技术来说,我认为这相当不错。所以,不管你怎么说(或想)LINQ,我认为它将彻底改变我们处理各个级别数据的方式。你可能会选择只使用 LINQ,或只使用 XLINQ,或使用 LINQ 提供的一切。我知道我可能会在某个阶段在实际系统上尝试所有这三种。祝你 LINQ 愉快。
那么你觉得呢?
我只想问,如果您喜欢这篇文章,请投票并留下一些评论,因为这让我知道文章是否达到了合适的水平,以及它是否包含了人们需要了解的内容。
结论
我非常享受撰写本文的过程,并对 XLINQ 的实际使用之简便感到耳目一新。事实上,我希望我能早点完成这篇 XLINQ 文章,因为我最近完成了一份短期合同,我们做了很多 XML 解析工作,如果使用 XLINQ 会容易得多。我还认为 XLINQ 中 XML 数据的更新方式远优于传统方法,在 XLINQ 中完成的方式似乎更具逻辑性。不再有迭代过程,万岁!
历史
v1.0 10/05/07:首次发布