65.9K
CodeProject 正在变化。 阅读更多。
Home

将 .NET DataSet 转换为 ADODB Recordset

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.63/5 (13投票s)

2006年3月8日

9分钟阅读

viewsIcon

152107

downloadIcon

1161

描述如何将 .NET DataSet 转换为 ADODB Recordset。

目录

引言

我被指派实现一个 .NET Web Service,以便外部实体可以访问我们的应用程序数据。该 Web Service 必须支持用 .NET 语言、Visual Basic 6.0 编写的客户端以及非 Microsoft 语言。所有对我们数据库的访问都必须通过存储过程进行,因此我们不需要支持运行直接 SQL 查询。设计是使用一个单一的方法,该方法接受一个 XML 字符串,该字符串描述要运行的存储过程、其参数以及返回数据的任何数据(DataSet XML 或 Recordset XML)。实现使用了 .NET 数据访问对象来返回 DataSet,并使用 ADODB 对象来返回 Recordset。然后将返回的数据序列化为 XML 字符串并返回给调用者。起初,这运行得非常好,但是,一旦负载测试开始,就很明显了,我遇到了一个问题。在 Web 服务器上,单个用户返回大量数据占用了 70% 的 CPU;两个并发用户将 CPU 占用率提升到 100%;五个或更多用户——有些会超时!这非常糟糕!

我一开始就知道在 .NET 代码中使用 ADODB 会有一些性能损失,但我从未想过会如此明显。看起来,从 .NET 使用 COM 互操作的成本可能相当高。回到原点。

现在是问题所在,我从一开始就知道,如果我必须提供 DataSet 持久化的一种形式的 XML 节点列表,以及 XML 架构,任何 .NET 或非 Microsoft 客户端都应该能够轻松利用这些数据,那么将持久化的 Recordset 格式的 XML 返回给 VB 6.0 客户端的计划就陷入了严重的危险之中。据我所知,在 VB.NET 中没有原生方法可以将 DataSet 转换为 ADODB Recordset。知道某人,在某个地方,肯定遇到过这个问题,我开始搜索互联网,看看是否有人提出了我可以使用的解决方案。我找到了几个包含 VB 6.0 代码的文章,这些代码使用 .NET 节点列表和架构来构建 ADODB Recordset,但它们是客户端解决方案,我需要一个可以在服务器上工作的解决方案。我找到了一篇 Microsoft 支持文章:如何将 ADO.NET DataSet 转换为 Visual Basic .NET 中的 ADO Recordset,这似乎正是我一直在寻找的。

我按照文章中的说明操作,设置了代码,并且奏效了!我现在遇到的唯一问题是,Microsoft 的示例代码将 XML 写入文件并读取 XSL 文件。由于我需要这是一个服务器解决方案,因此需要消除文件 IO。没问题,我修改了代码,使其能够即时生成字符串中的 XSL(它非常小),并使用 MemoryStream 来包含 XML 而不是文件。我试了一下,它奏效了。现在我必须用实际数据进行测试。我设置了一些测试代码来从我们的测试数据库中提取数据,并将其通过转换函数,看看在另一端会得到什么。

我看到的第一个问题是,只有整数和字符串字段被赋予了数据类型,对于其他所有字段,数据类型都是空白。这导致在尝试从 ADODB Stream 加载 Recordset 时出错。我追踪了代码确定数据类型的位置,果然,它们只捕获了整数和字符串。我添加了几种数据类型和一个 Case Else,它将任何我没有专门捕获的数据类型设置为字符串类型。以为这应该奏效,我又试了一次,加载 XML 到 Recordset 时仍然出现错误。我注释掉了存储过程中返回的所有字段,只留下了一个,再次运行,奏效了。我重复了这个过程,每次取消注释一个字段,试图找出我遇到问题的字段类型。结果发现是日期时间字段。经过一些研究,我发现在 DataSet 中,日期时间格式包含时区信息。为了让 Recordset 接受带有这种附加时区信息的日期时间,XML 中的数据类型必须设置为 dateTime.iso8601tz。我做了这个更改,又试运行了一次,奏效了。

现在,我将代码封装在一个类中,并将其集成到 Web Service 中,开始单元测试。我运行了几次调用,然后又遇到了一个问题——二进制字段。这个花了很长时间研究,但我发现 DataSet 中的二进制字段被持久化为 base64 编码的字符串;ADODB Recordset 期望二进制字段被持久化为二进制十六进制编码的字符串。由于 Microsoft 用于转换 DataSet 数据部分的 XML 代码是使用 XSL 转换的部分,因此没有机会重新编码二进制字段中的数据。首先,我尝试将 XML 中的数据类型设置为 bin.base64。这使得 Recordset 在没有错误的情况下加载了数据,但是,二进制数据最终被存储在 Recordset 中作为一个字符串字段,其中包含 base64 编码的字符串。为了让 Recordset 在加载时将字段转换为二进制(应该如此),它必须以二进制十六进制编码,并且 XML 中的数据类型设置为 bin.hex。为了解决这个问题,我重写了转换代码以遍历 DataSet,并使用 XmlTextWriter 将数据添加到 XML 中,就像代码中的其他部分添加头部和架构信息一样。这给了我机会检测二进制字段并对其进行二进制十六进制编码。

现在我需要找到或编写一个函数来执行二进制十六进制编码。我在互联网上找不到任何现成的代码,但我确实找到了关于二进制十六进制编码的信息。BinaryHex 编码只需将二进制流的每个八位字节(字节)分成两个 4 位半字节,并将表示该半字节值的十六进制字符放在输出字符串中。例如,如果您有 32 位二进制数据

10010010111100011010110011010100

分成八位字节(字节)

10010010  11110001  10101100  11010100

将八位字节分成 4 位半字节

1001  0010  1111  0001  1010  1100  1101   0100

半字节的十进制值

9  2  15  1  10  12  13  4

十六进制值

9  2  F  1  A  C  D  4

表示原始 32 位二进制值的编码字符串

"92F1ACD4

有了以上信息,编写一个小函数来二进制十六进制编码字节数组并返回结果字符串就很简单了。我将它添加到了类中,修改了数据转换代码以检测二进制字段,并将这个新函数应用于数据。现在我有一个可以使用的转换类。您可能需要更新的是确定要放入 XML 的数据类型的函数。如果您需要我未捕获的数据类型,将其返回为字符串是不够的,只需为您的数据类型在函数中添加一个 Case 语句即可。

现在,开始写代码!

背景

要求

  • Microsoft Windows 2003、Windows XP、Windows 2000 或 Windows NT 4.0 Service Pack 6a
  • Microsoft Data Access Components (MDAC) 2.6 或更高版本
  • Microsoft Visual Studio .NET

本文假设您熟悉以下主题

  • Microsoft Visual Basic .NET 语法
  • ADO.NET 和早期版本的 ADO
  • DataSet 和 ADO Recordset 的 XML 格式

使用代码

需要在此说明的是,我最初使用的是上述 Microsoft 支持文章中的代码。然而,本文提供的代码是对该代码进行了大量的更新。

注意:您必须调用 DataAdapterFillSchema 方法以获取 DataSet 的架构信息。如果不这样做,所有字段都将被创建为字符串数据类型。

GetADORS 函数提供了类的入口点和逻辑流程。它还创建了 MemoryStreamXmlTextReader,供其余函数使用来构建输出 XML 字符串。

Public Function GetADORS(ByVal DS As DataSet, _
                ByVal dbName As String) As String

    Try
        'Create a MemoryStream to contain the XML
        Dim mStream As New MemoryStream
        'Create an XmlWriter object, to write 
        'the formatted XML to the MemoryStream
        Dim xWriter As New XmlTextWriter(mStream, Nothing)

        'Additional formatting for XML
        xWriter.Indentation = 8
        xWriter.Formatting = Formatting.Indented
        'call this Sub to write the ADONamespaces
        WriteADONamespaces(xWriter)
        'call this Sub to write the ADO Recordset Schema
        WriteSchemaElement(DS, dbName, xWriter)
        'Call this sub to transform 
        'the data portion of the Dataset
        TransformData(DS, xWriter)
        'Flush all input to XmlWriter
        xWriter.Flush()

        'Prepare the return value
        mStream.Position = 0
        Dim Buffer As Array
        Buffer = Array.CreateInstance(GetType(Byte), mStream.Length)
        mStream.Read(Buffer, 0, mStream.Length)
        Dim TextConverter As New UTF8Encoding
        Return TextConverter.GetString(Buffer)

    Catch ex As Exception
        'Returns error message to the calling function.
        Err.Raise(100, ex.Source, ex.ToString)
    End Try

End Function

首先,我添加了两行,指示 XmlTextWriter 我希望 XML 进行缩进。这一切的目的是使输出 XML 人类可读。如果您愿意,可以省略这些行。使输出 XML 易于阅读使调试类更加容易。接下来,调用 WriteADONamespaces 来添加 Recordset 架构到输出 XML。然后调用 WriteSchemaElement 来添加架构元素。调用 TransformData 以正确格式化数据并将其添加到输出 XML。最后,MemoryStream 的内容准备好作为字符串返回。

Private Sub WriteADONamespaces(ByRef xWriter As XmlTextWriter)
    'Uncomment the following line to change 
    'the encoding if special characters are required
    'writer.WriteProcessingInstruction("xml", 
    '      "version='1.0' encoding='ISO-8859-1'")

    'Add XML start element
    xWriter.WriteStartElement("", "xml", "")

    'Append the ADO Recordset namespaces
    xWriter.WriteAttributeString("xmlns", "s", Nothing, _
            "uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882")
    xWriter.WriteAttributeString("xmlns", "dt", Nothing, _
            "uuid:C2F41010-65B3-11d1-A29F-00AA00C14882")
    xWriter.WriteAttributeString("xmlns", "rs", Nothing, _
            "urn:schemas-microsoft-com:rowset")
    xWriter.WriteAttributeString("xmlns", "z", _
            Nothing, "#RowsetSchema")
    xWriter.Flush()
End Sub

WriteADONamespaces 中的代码与原始 Microsoft 文章中的代码基本相同。我已删除描述此 XML 部分格式的注释。

Private Sub WriteSchemaElement(ByVal DS As DataSet, _
        ByVal dbName As String, ByRef xWriter As _
        XmlTextWriter)

    'write element Schema
    xWriter.WriteStartElement("s", "Schema", _
            "uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882")
    xWriter.WriteAttributeString("id", "RowsetSchema")

    'write element ElementType
    xWriter.WriteStartElement("s", "ElementType", _
            "uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882")

    'write the attributes for ElementType
    xWriter.WriteAttributeString("name", "", "row")
    xWriter.WriteAttributeString("content", "", "eltOnly")
    xWriter.WriteAttributeString("rs", "updatable", _
            "urn:schemas-microsoft-com:rowset", "true")

    WriteSchema(DS, dbName, xWriter)
    'write the end element for ElementType
    xWriter.WriteFullEndElement()

    'write the end element for Schema
    xWriter.WriteFullEndElement()
    xWriter.Flush()
End Sub

WriteSchemaElement 中的代码,同样,与原始 Microsoft 文章中的代码基本相同。我已删除描述此 XML 部分格式的注释。

Private Sub WriteSchema(ByVal DS As DataSet, ByVal dbName _
            As String, ByRef xWriter As XmlTextWriter)

    Dim i As Int32 = 1
    Dim DC As DataColumn

    For Each DC In DS.Tables(0).Columns

        DC.ColumnMapping = MappingType.Attribute

        xWriter.WriteStartElement("s", "AttributeType", _
                "uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882")
        'write all the attributes
        xWriter.WriteAttributeString("name", "", DC.ToString)
        xWriter.WriteAttributeString("rs", "number", _
                 "urn:schemas-microsoft-com:rowset", i.ToString)
        xWriter.WriteAttributeString("rs", "baseCatalog", _
                 "urn:schemas-microsoft-com:rowset", dbName)
        xWriter.WriteAttributeString("rs", "baseTable", _
                 "urn:schemas-microsoft-com:rowset", _
                 DC.Table.TableName.ToString)
        xWriter.WriteAttributeString("rs", "keycolumn", _
                 "urn:schemas-microsoft-com:rowset", _
                 DC.Unique.ToString)
        xWriter.WriteAttributeString("rs", "autoincrement", _
                 "urn:schemas-microsoft-com:rowset", _
                 DC.AutoIncrement.ToString)
        'write child element
        xWriter.WriteStartElement("s", "datatype", _
                "uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882")
        'write attributes
        xWriter.WriteAttributeString("dt", "type", _
                 "uuid:C2F41010-65B3-11d1-A29F-00AA00C14882", _
                 GetDatatype(DC.DataType.ToString))
        xWriter.WriteAttributeString("dt", "maxlength", _
                 "uuid:C2F41010-65B3-11d1-A29F-00AA00C14882", _
                 DC.MaxLength.ToString)
        xWriter.WriteAttributeString("rs", "maybenull", _
                "urn:schemas-microsoft-com:rowset", _
                 DC.AllowDBNull.ToString)
        'write end element for datatype
        xWriter.WriteEndElement()
        'end element for AttributeType
        xWriter.WriteEndElement()
        xWriter.Flush()
        i = i + 1
    Next
    DC = Nothing

End Sub

WriteSchema 中的代码,也与原始 Microsoft 文章中的代码基本相同。我已删除描述此 XML 部分格式的注释。

Private Function GetDatatype(ByVal DType As String) As String
    Select Case (DType)
        Case "System.Int32", "System.Int16", "System.Integer"
            Return "int"
        Case "System.DateTime"
            Return "dateTime.iso8601tz"
        Case "System.String"
            Return "string"
        Case "System.Byte[]"
            Return "bin.hex"
        Case "System.Boolean"
            Return "boolean"
        Case "System.Guid"
            Return "guid"
        Case Else
            Return "string"
    End Select
End Function

GetDatatype 函数已扩展,以处理比原始函数更多的数据类型。原始函数只识别 System.Int32System.DateTime。此外,还添加了 Case Else,为 Case 语句中未列出的所有数据类型返回字符串类型。

Private Sub TransformData(ByVal DS As DataSet, _
            ByRef xWriter As XmlTextWriter)

    'Loop through DataSet and add data to XML
    xWriter.WriteStartElement("", "rs:data", "")
    Dim i As Long
    Dim j As Integer
    'For each row...
    For i = 0 To DS.Tables(0).Rows.Count - 1
        'Write the start element for the row
        xWriter.WriteStartElement("", "z:row", "")
        'For each field in the row...
        For j = 0 To DS.Tables(0).Columns.Count - 1
            'Write the attribute that describes 
            'this field and it's value
            If DS.Tables(0).Columns(j).DataType.ToString_
                      = "System.Byte[]" Then
                'Binary data must be properly encoded (bin.hex)
                If Not IsDBNull(DS.Tables(0).Rows(i).Item(
                       DS.Tables(0).Columns(j).ColumnName)) Then
                    xWriter.WriteAttributeString(DS.Tables(0).
                       Columns(j).ColumnName, _
                       DataToBinHex(DS.Tables(0).Rows(i).Item(
                       DS.Tables(0).Columns(j).ColumnName)))
                End If
            Else
                If Not IsDBNull(DS.Tables(0).Rows(i).Item(
                       DS.Tables(0).Columns(j).ColumnName)) Then
                    xWriter.WriteAttributeString(
                            DS.Tables(0).Columns(j).ColumnName, _
           CType( _
              DS.Tables(0).Rows(i).Item(DS.Tables(0).
                           Columns(j).ColumnName), String))
                End If
            End If
        Next
        'End the row element
        xWriter.WriteEndElement()
    Next
    'Write the end element for rs:data
    xWriter.WriteEndElement()
    'Write the end element for xml
    xWriter.WriteEndElement()
    xWriter.Flush()

End Sub

TransformData 添加 XML 的 "rs:data" 部分。该函数遍历 DataSet,为每个数据行添加 "z:row" 元素。此函数还为根(XML)元素添加结束标记。

Private Function DataToBinHex(ByVal thisData As Byte()) As String
    Dim sb As New StringBuilder
    Dim i As Integer = 0
    For i = 0 To thisData.Length - 1
        'First nibble of byte (4 most significant bits)
        sb.Append(Hex((thisData(i) And &HF0) / 2 ^ 4))
        'Second nibble of byte (4 least significant bits)
        sb.Append(Hex(thisData(i) And &HF))
    Next
    Return sb.ToString
End Function

DataToBinHex 函数执行二进制数据的编码。

结论

只要开发人员需要集成 .NET 和 VB 6.0,就必须具备将数据从一个传递到另一个的能力。在 VB 6.0 是客户端的情况下,本文提供的代码应该有助于缓解这个问题。虽然 Microsoft 文章中的代码是一个很好的起点,但它存在一些缺点,使其无法在许多实际情况下正确运行。我相信我已经解决了主要问题和不足之处,并提供了可以在大多数情况下直接用于项目中的代码。

历史

  • 首次提交 - 2006/03/08。
© . All rights reserved.