科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道在Microsoft Office中使用ADO.NET

在Microsoft Office中使用ADO.NET

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

    XML Web Service!XML Web Service!XML Web Service 无处不在!当然,您早就知道这一点。但是,如果您没有加入使用 XML Web Service 的行列。

作者:中国IT实验室 来源:中国IT实验室 2007年9月29日

关键字: ADO 编程

  • 评论
  • 分享微博
  • 分享邮件
    XML Web Service!XML Web Service!XML Web Service 无处不在!当然,您早就知道这一点。但是,如果您没有加入使用 XML Web Service 的行列,就无法读懂有关 XML Web Service 的文章。亲自用过后,我发现 XML Web Service 是极其有用的。

  给我留下最深印象的是什么?主要有三点:

  • 创建 XML Web Service 非常容易。
  • 我的客户和第三方可以使用 XML Web Service 迅速找到有用信息。
  • XML Web Service 配合 Microsoft? Office 一起使用非常容易。

  但是,我再次发现对构造集成的 Microsoft Office 和 XML Web Service 解决方案的需求尚未得到满足。我发现自己经常需要将已被序列化为 XML 并已通过 XML Web Service 传输的 ADO.NET 数据集应用到使用 Microsoft Office 创建的解决方案中。有这种需求的不止我一个,我在新闻组中看到有很多人都要求有完成这一任务的便捷方法。由于 Microsoft .net 刚刚问世,因此 Microsoft Office 的当前版本不能提供现成的解决方案以使用 ADO.NET 数据。但是这也并非完全没有可能。

  在本文中,我将介绍如何通过可以重复使用的方法将 XML Web Service 提供的 ADO.NET 数据集应用到 Microsoft Office 中。这里介绍的解决方案可以与 Microsoft Office XP 和 Microsoft Office 2000 配合使用。Microsoft Visual Basic? for Applications (VBA) 源代码还要求具备 MSDN SOAP Developer Center中提供的 Microsoft SOAP Toolkit 3.0 和 Microsoft XML (MSXML) Core Services 4.0 库。

  首先,我们需要查看已被序列化为 XML 的数据集的结构。然后,在了解了 XML 的结构之后,我们将讨论两种在 Microsoft Office 中使用这些数据的方法。

  获得数据集

  首先,让我们来看看我在文章一开始的评价中提到的第一点:创建 XML Web Service 的便捷性。在 Microsoft Visual Studio? .NET 中,我介绍了一个可以返回 ADO.NET 数据集的简单函数。仅需添加 <WebMethod()> 属性,我就可以立即将同样的代码转换为 XML Web Service:

Private sConnNwind As String = "Data Source=localhost;" & _
"Initial Catalog=northwind;" & _
"User ID={UserID};" & _
"password={Password}"
<WebMethod()> Public Function RunQuery() As DataSet
Dim cn As SqlConnection = New SqlConnection(sConnNwind)
Dim cmd As SqlCommand = New SqlCommand("SELECT * from Orders", cn)
Dim da As SqlDataAdapter = New SqlDataAdapter()
da.SelectCommand = cmd
cn.Open()
Dim dsAuthors As DataSet = New DataSet()
da.Fill(dsAuthors)
cn.Close()
Return dsAuthors
End Function

  Visual Studio .NET 使所有这一切变得如此便捷!我简直无法想像如果编写代码先将其转换为 XML 文档,然后再转换为 SOAP 消息以作为 XML Web Service 传输会多么繁琐。不仅如此,我根本不想在 Visual Basic .NET 代码上花费太多时间;这些概念在 MSDN 上随处可见。对于这一代码节选,您应该注意主要两点:

  • 该函数可以运行来自罗斯文数据库的查询并返回数据集。
  • <WebMethod()> 属性对该函数进行标记,使其作为 XML Web Service 可见。

  还需要顺便提一下,数据集类似于 ADO Recordset 对象,但功能要强大得多。它可以将查询的多个结果及其关系存储在 DataSet 对象中,最重要的是,它可以将包含的数据序列化为 XML 流。

  同样,当 RunQuery 函数被调用时,ADO.NET 会连接到罗斯文 Microsoft SQL Server? 数据库,并将查询结果检索至 DataSet 对象。此时,函数即可返回数据集。.NET 能够序列化数据集,并且 XML 流会被发送回调用者。已序列化的数据集类似于如下所示:

<?xml version="1.0" encoding="utf-8"?>
<DataSet xmlns="http://tempuri.org/WS_XML">
<xs:schema id="NewDataSet" xmlns=""
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element name="NewDataSet" msdata:IsDataSet="true">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="Table">
<xs:complexType>
<xs:sequence>
<xs:element name="OrderID" type="xs:int" minOccurs="0" />
<xs:element name="CustomerID"
type="xs:string" minOccurs="0" />
<xs:element name="EmployeeID"
type="xs:int" minOccurs="0" />
<xs:element name="OrderDate"
type="xs:dateTime" minOccurs="0" />
<xs:element name="RequiredDate"
type="xs:dateTime" minOccurs="0" />
<xs:element name="ShippedDate"
type="xs:dateTime" minOccurs="0" />
<xs:element name="ShipVia" type="xs:int" minOccurs="0" />
<xs:element name="Freight"
type="xs:decimal" minOccurs="0" />
<xs:element name="ShipName"
type="xs:string" minOccurs="0" />
<xs:element name="ShipAddress"
type="xs:string" minOccurs="0" />
<xs:element name="ShipCity"
type="xs:string" minOccurs="0" />
<xs:element name="ShipRegion"
type="xs:string" minOccurs="0" />
<xs:element name="ShipPostalCode"
type="xs:string" minOccurs="0" />
<xs:element name="ShipCountry"
type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
<diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">
<NewDataSet xmlns="">
<Table diffgr:id="Table1" msdata:rowOrder="0">
<OrderID>10248</OrderID>
<CustomerID>VINET</CustomerID>
<EmployeeID>5</EmployeeID>
<OrderDate>1996-07-04T00:00:00.0000000-05:00</OrderDate>
<RequiredDate>1996-08-01T00:00:00.0000000-
05:00</RequiredDate>
<ShippedDate>1996-07-16T00:00:00.0000000-
05:00</ShippedDate>
<ShipVia>3</ShipVia>
<Freight>32.38</Freight>
<ShipName>Vins et alcools Chevalier</ShipName>
<ShipAddress>59 rue de l'Abbaye</ShipAddress>
<ShipCity>Reims</ShipCity>
<ShipPostalCode>51100</ShipPostalCode>
<ShipCountry>France</ShipCountry>
</Table>
<Table diffgr:id="Table2" msdata:rowOrder="1">
<OrderID>10249</OrderID>
<CustomerID>TOMSP</CustomerID>
<EmployeeID>6</EmployeeID>
<OrderDate>1996-07-05T00:00:00.0000000-05:00</OrderDate>
<RequiredDate>1996-08-16T00:00:00.0000000-
05:00</RequiredDate>
<ShippedDate>1996-07-10T00:00:00.0000000-
05:00</ShippedDate>
<ShipVia>1</ShipVia>
<Freight>11.61</Freight>
<ShipName>Toms Spezialit?ten</ShipName>
<ShipAddress>Luisenstr. 48</ShipAddress>
<ShipCity>Münster</ShipCity>
<ShipPostalCode>44087</ShipPostalCode>
<ShipCountry>Germany</ShipCountry>
</Table>
</NewDataSet>
</diffgr:diffgram>
</DataSet>

  该 XML 序列化数据集包括两个主要部分:

  • XML 架构(常缩写为 XSD)部分,其中包含有关 XML 文档中数据的结构信息。
  • 第二部分实际上包含来自查询的数据。正如您所看到的,本文档中讲述的 XML 序列化数据集是来自我们的 SQL Server 数据库的自包含、可移植的数据表示形式。它包括表格结构、数据类型和元素中的数据。

  该信息对 Microsoft Office 用户有用吗?当然有用。Microsoft Office 完全是为了使数据和信息有用、相关并可操作。随着 XML Web Service 在 Internet 和企业内部网络上的不断扩展,Microsoft Office 用户会希望利用 XML Web Service 所提供的信息。我对 Microsoft Office 用户真正钦佩的原因之一便是他们能够在获得原始数据后对其进行研究、压缩、改变,或采取他们所需的任何方式对其进行处理,以运用这些数据来完成自己的工作。Microsoft Office 为用户提供了对数据进行此类处理时所需的工具,而且最重要的是,我不用再编写更多的代码来解决问题了,因为这些工具可以更好地解决他们。

  其实,真正的原因是我想偷懒。我想用友好的 Microsoft Office 方式为用户提供数据,然后让用户自己处理这些数据。

  了解该信息后,是否就可以在 Microsoft Office 的桌面上根据自己的需要重组这些数据呢?是的。我将在本文的后半部分向您介绍如何利用 Microsoft Excel 的内置方法来使用 XML 序列化数据集,以及如何完全灵活地运用使用 VBA 代码的自定义解决方案通过任一 Office 应用程序做到这一点。

   不要做无谓的重复

  在本文开始,我曾提到过 Microsoft Office 没有提供可以使用 XML 序列化数据集的现成解决方案。但是,Microsoft Excel 确实提供了一个与此非常相似的功能。从创建 Microsoft Office 解决方案时所积累的经验中,您应该明白一件事情,那就是不要做无谓的重复。作为程序员,我们都有一种倾向:为要做的每一件事情编写代码。在使用 Microsoft Office 时,重要的是要学会问自己一个简单的问题,“有没有可以利用的内置功能,使我不用编写代码就可以更迅速地达到我的目的?”对于我们试图解决的问题,Microsoft Excel 提供了一个我们可以利用的功能,称为 Web 查询。Web 查询有其局限性,但是在处理 XML Web Service 的很多情况下都非常有用,并且在我看来,它是经常被忽略的一种重要功能。我们来快速回顾一下 Microsoft Excel Web 查询功能的优点和缺点。

  在本文后半部分中,我们将利用可以公开使用的 Web 服务。请花几分钟的时间用您的浏览器运行一下该 XML Web Service。它会为 Fabrikam.com(Fabrikam.com 是建立在 Microsoft Office 之上的示例应用程序;返回对当前打开的订单的查询。您在浏览器中看到的 XML 是序列化数据集,形式类似于以前 SQL Server 罗斯文数据库中的数据集。

  让我们在 Microsoft Excel Web 查询中运用这种 Web 服务。要进行此操作,请执行以下步骤:

  • 启动 Microsoft Excel。
  • 在“数据”菜单上,指向“导入外部数据”并单击“新建 Web 查询”。
  • 显示“新建 Web 查询”对话框。
  • 在“地址”列表中,键入 http://services.fabrikam.com/OWSISample/Order.asmx/OpenOrders 并单击“转到”。图 1 显示了您将看到的内容。



    图 1:预览 Fabrikam OpenOrders XML Web Service 的“新建 Web 查询”对话框。

  • 单击“导入”。
  • 然后系统会询问您在何处插入 Web 查询数据。在空白工作表上选择任一单元格,然后单击“确定”。
  • Web 查询将运行并插入结果,如图 2 所示。

图 2:插入工作表中的 Web 查询结果。

  正如您所看到的,我们无需任何代码便可轻松地将 XML Web Service 的结果导入 Excel。我已经多次使用过这种方法,发现它是一种相当可靠的技术。Excel 中的 Web 查询技术出奇地灵活;要了解可用的配置选项,请参见图 3。我比较喜欢的功能是它能够基于某些参数更新单元格。例如,您可以让 Web 查询按照您指定的时间间隔(分钟)或在首次打开工作簿时自我更新。这样可以确保您总能具备最新的信息。

图 3:“Web 查询属性”对话框。

  但是,Web 查询也有一些缺点。首先,您可能会注意到 Excel 中的列标题不是很明确。Excel 配有内置的 XML 转换器,专门用于导入任意 XML 并将其转换为电子表格。Excel 采用的方法可以通用,因为它没有关于所加载 XML 的结构和用途的任何线索。在我们的例子中,我们所使用的 XML 不是任意的;其格式或架构都是大家所熟悉的。这意味着,我们使用基于通用布局的 Web 查询时,XML 的所有格式和其他元素在 Excel 中对于我们来说都是有用的。

  此外,Excel 目前无法处理 XSD 数据类型。因此,当我们运行 Web 查询时,很多数据类型都无法按照我们所希望的方式被转换或处理。例如在图 2 中,您可以注意到数据栏中包含类似 2002-07-19T10:53:36.000000004:00 的数据。XSD 以 ISO 8601 数据格式显示数据。正如您所看到的,Excel 无法识别此数据格式,并将其作为字符串来处理。

  最后,对于能够处理 XML 的 Web 查询来说,其最大的缺陷便是仅在 Microsoft Excel 2002 中可用。因此,可以消除这些局限性并可以在任意 Microsoft Office 应用程序中使用的技术将是非常有用的。在我讲述如何通过 VBA 代码做到这一点之前,请采纳我的建议:如果您还不了解 Web 查询,最好先花一点时间来进一步了解它。对于无需编写代码就可以使用 XML Web Service 来说,Web 查询的确很有帮助。有关 Web 查询的详细信息,请参阅 Charles Maxson 的文章“Well-kept Secret: Excel's Web Queries Enable You to Populate Worksheets from Web Sites”。

  扩展您自己的解决方案

  很明显,我们要再次提及需要将来自 XML Web Service 并已序列化的 ADO.NET 数据集作为 XML 应用到任意 Microsoft Office 应用程序中的问题。我们希望通过可以轻松处理这些数据的方法使用这些数据,我们还希望得到源代码以便完全控制处理过程。

  以下解决方案可以用于任意启用 VBA 的应用程序,也可以通过 Microsoft Visual Basic 6.0 使用。要使用此解决方案,需要执行以下步骤:

  • 从 XML Web Service 中检索 XML。
  • 将 XML 分析到 VBA Array 对象中。
  • 将数据插入 Office 应用程序。

  步骤 1:检索 XML

  首先,我们需要从 XML Web Service 中检索序列化为 XML 的 ADO.net 数据集。这在我们的解决方案中是最容易的一个步骤。从 Microsoft Office 应用程序(包括 Word、Excel 和 Microsoft PowERPoint? 等)之一的 VBA 集成开发环境 (IDE) 中,我们需要为以下两个库设置引用:

  • Microsoft Soap Type Library v3.0
  • Microsoft XML v4.0

  如果您还没有上面这两个库,可以去免费下载。以下代码将运行 XML Web Service,并将结果检索到 MSXML IXMLDOMNodeList 对象中:

Public Function ParseDataSet(ByRef xdlXSDFromSoapClient As Dim sc As MSSOAPLib30.SoapClient30
Dim xdl As MSXML2.IXMLDOMNodeList
Set sc = New MSSOAPLib30.SoapClient30
sc.MSSoapInit "http://services.fabrikam.com/OWSISample/Order.asmx?wsdl"
Set xdl = sc.OpenOrders

  首先,我们创建一个 SoapClient30 对象。该 SoapClient30 对象是包含在 Microsoft SOAP Toolkit 3.0 中的实用程序对象。它封装了所有连接到远程 XML Web Service 所必须的逻辑和通道,执行 Web 方法,并向客户返回 XML 信息。要做到这一点,必须将 XML Web Service 的 Web 服务说明语言 (WSDL) 文件的引用传递给 MSSoapInit 属性。这能够使 SoapClient30 对象知道可以使用何种服务和方法。一旦 SoapClient30 对象被初始化,我们便可以调用由 XML Web Service 提供的任意方法:

  Set xdl = sc.OpenOrders

  在我们的例子中,我们调用了 OpenOrders 方法,它可以返回在本文前半部分中看到的 XML 结果。Microsoft SOAP Toolkit 3.0 使这些变得很容易;它仅用了四行代码。请注意,XML Web Service 所回应的是 IXMLDOMNodeList 对象。IXMLDOMNodeList 对象是 MSXML 4 库中的对象,含有 XML 的片段,并被显示为 IXMLDOMNode 对象。这便是令人感兴趣的地方。IXMLDOMNodeList 对象包含两个 XML 片段的节点。第一个节点包含 XML 架构(定义数据结构和数据类型),第二个节点包含我们的实际数据。这对我们来说非常方便,现在我们只需两个节点对象便可以先分析 XML 的结构,然后从第二个节点中分析出数据。

  步骤 2:将 XML 分析到 VBA Array 对象中

  在讲述如何将 XML Web Service 返回的 XML 分析到数组中之前,我需要解释一下为什么要将数据分析到数组中,而不分析到某些其他类似 ADO Recordset 对象的内存内表示形式或 VBA 类型的结构中。最开始时,我打算支持其他内存内表示形式。我首先从数组开始,因为数组是支持 VBA 的应用程序中的共同特性。数组简单、快速,并且没有 ADO 之类的外部相关性。我推断在数组逻辑可以运行后,应该能够很容易地修改代码以满足更高的要求。但是事实是,数组原来非常灵活,并且可以游刃有余地满足我的所有需要。不仅如此,您可以清楚地发现通过修改这些代码还可以轻松地创建 ADO 记录集或其他东西。

  以下代码用于将 XML 分析到数组:

IXMLDOMNodeList, _
ByVal sTableName As String, _
ByVal bReturnFieldHeaders As Boolean) As Variant
Dim xdd As MSXML2.DOMDocument40
Dim xdlStructure As MSXML2.IXMLDOMNodeList
Dim xdlRows As MSXML2.IXMLDOMNodeList
Dim lcntRows As Long
Dim lctrRow As Long
Dim lcntFields As Long
Dim saFieldDefinitions() As String ' 用于存储字段名和字段 _
' 数据类型的数组。
Dim lctrFieldDef As Long
Dim iRowHeader As Long
Dim vArray() As Variant ' 由函数返回的表格数据的数组。
Dim iCntUbound As Integer
Dim iCtr As Integer
Dim sNodeName As String
Dim xdlFields As IXMLDOMNodeList
Dim xdnField As IXMLDOMNode
Dim iNode As Integer
Dim sDataSetNameSpace As String

Set xdd = New MSXML2.DOMDocument40
With xdd
' 加载字段定义 XML。
.async = False
.preserveWhiteSpace = True
.setProperty "SelectionNamespaces", _
"xmlns:xs='http://www.w3.org/2001/XMLSchema'"
.loadXML xdlXSDFromSoapClient.Item(0).XML
Set xdlStructure = xdd.selectNodes("//xs:element[@name='" & _
sTableName & _
"']/xs:complexType/xs:sequence/xs:element")
' 导入字段定义数组。
lcntFields = xdlStructure.Length - 1 ' 基于零。
If lcntFields = -1 Then
MsgBox "指定的表格不存在。"
Exit Function
End If
End With

ReDim saFieldDefinitions(lcntFields, 1) As String
For lctrFieldDef = 0 To lcntFields
With xdlStructure.Item(lctrFieldDef).Attributes
saFieldDefinitions(lctrFieldDef, 0) = .getNamedItem("name").Text
saFieldDefinitions(lctrFieldDef, 1) = .getNamedItem("type").Text
End With
Next

' 开始建立数组。
With xdlXSDFromSoapClient.Item(1).FirstChild.Attributes
sDataSetNameSpace = .getNamedItem("xmlns").Text
End With

Set xdd = New MSXML2.DOMDocument40
With xdd
.async = False
.preserveWhiteSpace = True
.loadXML xdlXSDFromSoapClient.Item(1).XML
If sDataSetNameSpace = "" Then
Set xdlRows = xdd.selectNodes("//" & sTableName)
Else
.setProperty "SelectionNamespaces", "xmlns:df='" & _
sDataSetNameSpace & "'"
Set xdlRows = xdd.selectNodes("//df:" & sTableName)
End If
End With

lcntRows = xdlRows.Length - 1 ' 从 0 开始索引
If lcntRows = -1 Then
MsgBox "XSD Web Service returned no records", vbCritical, "No Data"
Exit Function
End If

' 添加行标题(可选)。
If bReturnFieldHeaders = True Then
' 添加标题行。
ReDim vArray(lcntRows + 1, lcntFields) As Variant
iRowHeader = 1 ' 添加行标题。
For lctrFieldDef = 0 To lcntFields
vArray(0, lctrFieldDef) = _
Replace(saFieldDefinitions(lctrFieldDef, 0), _
"_x0020_", " ")
Next
Else
' 无标题行。
ReDim vArray(lcntRows, lcntFields) As Variant
iRowHeader = 0 ' 无行标题
End If

' 向数组添加行数据。
iCntUbound = UBound(saFieldDefinitions)
For lctrRow = 0 To lcntRows

Set xdlFields = xdlRows.Item(lctrRow).childNodes

For Each xdnField In xdlFields
' 循环将索引放到字段名和数据类型中。
sNodeName = xdnField.nodeName
For iCtr = 0 To iCntUbound
If saFieldDefinitions(iCtr, 0) = sNodeName Then
iNode = iCtr
Exit For
End If
Next

Select Case saFieldDefinitions(iNode, 1)
Case "xs:int"
vArray(lctrRow + iRowHeader, iNode) = CLng(xdnField.Text)
Case "xs:integer"
vArray(lctrRow + iRowHeader, iNode) = CVar(xdnField.Text)
Case "xs:long"
vArray(lctrRow + iRowHeader, iNode) = CVar(xdnField.Text)
Case "xs:date"
vArray(lctrRow + iRowHeader, iNode) = _
ConvertISO8601DateFormatToVBDateTime(xdnField.Text)
Case "xs:dateTime"
vArray(lctrRow + iRowHeader, iNode) = _
ConvertISO8601DateFormatToVBDateTime(xdnField.Text)
Case "xs:double"
vArray(lctrRow + iRowHeader, iNode) = CDbl(xdnField.Text)
Case "xs:short"
vArray(lctrRow + iRowHeader, iNode) = CInt(xdnField.Text)
Case "xs:float"
vArray(lctrRow + iRowHeader, iNode) = CSng(xdnField.Text)
Case "xs:boolean"
vArray(lctrRow + iRowHeader, iNode) = CBool(xdnField.Text)
Case "xs:byte"
vArray(lctrRow + iRowHeader, iNode) = CInt(xdnField.Text)
Case "xs:time"
vArray(lctrRow + iRowHeader, iNode) = _
ConvertISO8601DateFormatToVBDateTime(xdnField.Text)
Case Else
vArray(lctrRow + iRowHeader, iNode) = xdnField.Text
End Select
Next
Next
ParseDataSet = vArray
End Function

该 ParseDataSet 函数要求三个参数:

参数 说明
xdlXSDFromSoapClientIXMLDOMNodeList 对象) SoapClient30 对象中返回的 IXMLDOMNodeList 对象如本文前面章节中所述。
sTableNameString 值) 从 XML Web Service 返回的 XML 中使用的唯一表名称。
注意:此参数(例如 XML)区分大小写。
bReturnFieldHeadersBoolean 值) 如果字段名作为数组中的第一行被返回则为 True

  此例程相当长,但是有一定的原因。最初,我曾把此函数拆分为三个独立的函数。但是我发现,由于读取整个 XML 文档对象模型 (DOM) 需要循环很多次,因此从函数外部进行调用时性能会受到影响。基于这一原因,将内联的所有代码集中在一个函数中会更加合理。这与使用独立的函数相比,可以将性能提高大约三倍。

  如果您曾经分析过 XML,那么当您查看 ParseDataSet 函数的代码时便会发现,这些代码类似于 MSXML 分析器。例程的第一部分会通过 XML 架构,以获取 ADO.NET 数据集的字段名和数据类型。如果程序已选择在数组的第一行中返回字段名,此信息将非常重要。它还可以用于将 XML 字符串数据转换为相应的 VBA 数据类型。

  代码的第二部分实际将按记录逐个读取 XML DOM,向数组中提取数据。在代码的最后部分,您会发现一个很长的 Select Case 语句。这个 Select Case 语句将从 XML 架构检查字段的数据类型,并强制执行从 XML 字符串型数据向相应 VBA 数据类型的转换。如果我们不进行此操作,XML 中的所有整数都将在数组中显示为字符串;但是我们希望这些数据真正显示为整数。这同样适用于所有其他数据类型。

  该 Select Case 语句还会为 ConvertISO8601DateFormatToVBDateTime 函数添加标注。这是例外情况,其中 ParseDataSet 函数将调用外部例程。转换 ISO 8601 数据格式的逻辑相当繁琐,因此将它分离出来更为合理。如果 XML 含有数据字段,该例程确实会对处理时间产生一些影响,但对于整体来说是可以忽略不计的。下面是 ConvertISO8601DateFormatToVBDateTime 函数:

Public Function ConvertISO8601DateFormatToVBDateTime(ByRef vData As Variant) As Variant
Dim lMonthSep As Long ' 月份分隔符位置。
Dim lDaySep As Long ' 日期分隔符位置。
Dim lMinutesSep As Long ' 分钟分隔符位置。
Dim sDt As String
Dim sTm As String

lMonthSep = InStr(1, vData, "-", vbBinaryCompare)
lDaySep = InStr(lMonthSep + 1, vData, "-", vbBinaryCompare)

If lMonthSep > 0 Then
sDt = Mid(vData, lMonthSep + 1, 2) & "/" & _
Mid(vData, lDaySep + 1, 2) & "/" & _
Left(vData, lMonthSep - 1)
End If

' 提取时间。
lMinutesSep = InStr(1, vData, ":", vbBinaryCompare)

If lMinutesSep > 0 Then
sTm = Mid(vData, lMinutesSep - 2, 8)
End If

ConvertISO8601DateFormatToVBDateTime = CDate(sDt & " " & sTm)

End Function

  ConvertISO8601DateFormatToVBDateTime 函数将 ISO 8601 数据格式分解为它的组成部分,然后再将这些数据重组为适当的 VBA 数据。例如,我们前面提到过的数据 2002-07-19T10:53:36.000000004:00 在转换后会显示为 07/19/2002 10:53 AM。

  对于绝大部分来说,将 XML 分析到数据中是此解决方案中最难的一部分。ParseDataSet 函数在将已序列化为 XML 的 ADO.NET 数据分析到数组的过程中负责进行判断。我们如何使用该函数?现在,让我们对用来调用 XML Web Service 的代码进行扩展以分析数据:

Dim sc As MSSOAPLib30.SoapClient30
Dim xdl As MSXML2.IXMLDOMNodeList
Dim vDataSet As Variant

Set sc = New MSSOAPLib30.SoapClient30
sc.MSSoapInit "http://services.fabrikam.com/OWSISample/Order.asmx?wsdl"
Set xdl = sc.OpenOrders
vDataSet = ParseDataSet(xdl, "Table", True)

  我们仅添加了两行代码:

dim vDataSet As Variant
vDataSet = ParseDataSet(xdl, "Table", True)

  我们首先创建了 Variant 类型的变量。然后,调用在 IXMLDOMNodeList 对象中传递的 ParseDataSet 函数以及我们希望从数据集返回的表名称,同时 True 表示字段标题名应该被放入数组的第一行。此时,您可能会有疑问:ParseDataSet 函数不返回数组吗?我们为什么要把 ParseDataSet 函数的返回值指定为 VariantVariant 数据类型确实有很多缺陷,但是它是一个非常有用的数据类型。我们在此处使用的这种方法称为 Variant 数组。实际上您可以创建一个数组并把它分配给 Variant 变量。这样我们便具备了拥有该数组的变量,而且无需知道数组所需的大小,因为直到返回真正的数据集时我们才能了解这一信息。Variant 数组的使用方法与任何其他数组相同。您可以使用 vDataSet(0,0) 表示形式以及任何其他可以识别数组的函数(例如 UPPER 和 LOWER)从数组中检索元素。

  具备可重复使用的例程 ParseDataSet 和 ConvertISO8601DateFormatToVBDateTime 后,我们便可以仅通过几行代码来分析返回 XML 序列化数据集的 XML Web Service 的结果。我曾经把这两个函数捆绑在一个称为 basWebServices.bas 的模块中,您可以将其导入到您的 VBA 项目中。basWebServices.bas 模块包含在下载的文章中。

  作为本步骤的结束,我再谈几个有关这些例程的问题。您可能会认为这些例程是不完美的。我已经在很多实例中测试过代码,发现在如何将数据集序列化为 XML 这一点上,.NET 是非常灵活的。我曾经通过包含单个表和多个表的 XML 序列化数据集测试此代码。我当然知道此代码目前尚不支持嵌套表,因为我还没有发现在 Microsoft Office 解决方案中有需要使用嵌套表的情况。因此,当 .NET 开发者偏离了从 XML Web Service 中返回数据集的默认机制,或数据集中包含复杂的表层次结构时,您就需要修改源代码。但是拥有源代码非常有益,因为它给您提供了更大的灵活性。

  步骤 3:用数组进行某些操作

  我曾说过步骤 1 很容易;步骤 3 则更加容易。现在我们已将 ADO.NET 数据集分析到数组中,可以利用它在 Office 中进行任何操作。以下代码是对我们一直在使用的示例的扩展。它从 XML Web Service 中检索 XML 并对其进行分析,然后将其插入 Excel 的当前电子表格中:

Dim rngData As Range
Dim sc As MSSOAPLib30.SoapClient30
Dim xdl As MSXML2.IXMLDOMNodeList
Dim vDataSet As Variant

' 运行 XML Web Service 并将响应分析到数组中。
Set sc = New MSSOAPLib30.SoapClient30
sc.MSSoapInit "http://services.fabrikam.com/OWSISample/Order.asmx?wsdl"
Set xdl = sc.OpenOrders
vDataSet = ParseDataSet(xdl, "Table", True)

' 将数组作为格式化的网格插入。
Set rngData = ActiveCell.Resize(UBound(vDataSet, 1) + 1, UBound(vDataSet, 2) + 1)
rngData = vDataSet
ActiveCell.AutoFilter
Selection.AutoFormat Format:=xlRangeAutoFormatList2, Alignment:=True

  这是 Excel 最吸引我的原因之一。您可以将已为同样大小的数组分配的 Range 对象直接分配到电子表格中。请注意 rngData = vDataSet 一行。就是这么简单!为达到这个目的,我利用 AutoFilter 功能进行了一些改进以使它看起来更好,这样用户便可以深化数据并对数据应用自动格式。图 4 显示了结果。请将此结果与图 2 中 Web 查询所获得的结果进行比较。

图 4:通过自定义 VBA 代码插入到 Excel 中的 XML Web Service 数据。

  在 Word 中达到同样目的会稍微复杂一点,因为 Word 中没有可以将数组插入文档的装置。同时,遍历数组的功能也尚未完善。以下代码可以在 Word 中完成与在 Excel 中执行的相同任务:

Private Sub InsertArrayAsWordTable()

Dim sc As MSSOAPLib30.SoapClient30
Dim xdl As MSXML2.IXMLDOMNodeList
Dim vDataSet As Variant
Dim nRows As Integer
Dim nRowCount As Integer
Dim nColumns As Integer
Dim nColumnCount As Integer
Dim sRow As String

' 运行 XML Web Service 并将响应分析到数组中。
Set sc = New MSSOAPLib30.SoapClient30
sc.MSSoapInit "http://services.fabrikam.com/OWSISample/Order.asmx?wsdl"
Set xdl = sc.OpenOrders
vDataSet = ParseDataSet(xdl, "Table", True)

' 将数组作为格式化的表格插入。
nRows = UBound(vDataSet, 1)
nColumns = UBound(vDataSet, 2)

Selection.Collapse wdCollapseStart

Options.Pagination = False
For nRowCount = 1 To nRows
For nColumnCount = 1 To nColumns
sRow = sRow & vDataSet(nRowCount - 1, nColumnCount - 1)
If nColumns <> nColumnCount Then
sRow = sRow & vbTab
End If
Next
sRow = sRow & vbCrLf
Next

With Selection
.InsertAfter sRow
.ConvertToTable Separator:=vbTab
With .Tables(1)
.AutoFitBehavior (wdAutoFitContent)
.AutoFormat Format:=wdTableFormatList2
End With
.Collapse wdCollapseStart
End With
Options.Pagination = True

End Sub

  InsertArrayAsWordTable 函数可以检索 XML Web Service 数据并对其进行分析,然后循环读取数组,以将文本作为制表符分隔字符串插入 Word。最后,使用 ConvertToTable 方法将字符串转换为表格。就象使用 Excel 一样,我们需要应用少量格式来改进其显示形式。图 5 显示了结果。

图 5:通过自定义 VBA 代码插入到 Word 中的 XML Web Service 数据。

  小结

  我所介绍的解决方案非常简单,并可以有效地在 Microsoft Office 中顺利显示 ADO.net 数据集。因此,用户就可以随心所欲地使用数据。

  最后要注意的是,不要向客户端返回过多数据。我们发现,XML Web Service 通常很少会返回上百条记录,这些记录可以被迅速分析并插入 Microsoft Office。但是,当返回上千条记录时,性能会受到影响。您肯定会想到通过线路传输大量数据时就会发生这种情况。Microsoft Office 处理数据所需的处理时间是容易忽视的问题,也是 Microsoft Office 文档的实际局限性。例如,Microsoft Excel 的每张工作表中可以包含 65,000 行。我们确实曾经看到过用户想向工作表中返回更多数据的情况。这使我们想起一句著名的格言:我们永远不需要超过 640 KB 的 RAM。谁又曾想到我们可能会在工作表中需要超过 65,000 行呢?我肯定不会!

查看本文来源

    • 评论
    • 分享微博
    • 分享邮件