Christa Carpentiere
适用范围:
Microsoft® Access
Microsoft® Excel
Microsoft® Office
Microsoft® Visual Basic® .NET
Microsoft® Visual Studio® .NET
摘要:查看如何从 Office 文件中获取 Microsoft Access 和 Excel 数据并将其放入 .NET 应用程序中,并查看 OLE DB 提供程序怎样用于编写使处理 .xls 或 .mdb 文件更容易的通用代码。
在 Microsoft .NET 应用程序中使用 Microsoft Office 数据时所需的大多数任务与使用任何其他数据时的任务相同 - 创建连接、根据使用数据时的需要创建 DataReader 或 DataAdapter、创建一个或多个 DataSet 来封装相关数据的 DataTable,等等。因此,我不打算在此重新编写 ADO.NET 文档。本文的目的在于,提供按 Office 数据源编写基础数据检索代码时所需的缺失或广为分布的信息段。好像那些知道 Jet 的奇特之处的人士却不是很熟悉 ADO.NET,而大部分的 ADO.NET 专业人士不是很了解支持 Office 可编程序性的技术。因此提供一个快速的概述,希望可以解答我所见到的这两方的常见问题。
首先让我们讨论一下连接到 Microsoft Excel 和 Microsoft Access 的数据并从中检索数据的基础。我认为此领域中存在最大的问题 - 通常一旦人们能够解决该问题,Office 特定问题将会得到解决,而且其余问题主要集中于如何使用 ADO.NET。下面我将讨论如何使用 OleDbConnection 对象的 GetOleDbSchemaTable 方法来检索关于运行时 Access 或 Excel 数据的架构信息。这样,您可以避开关于数据源的硬编码信息;数据源非常有用,从中可以获取数据。
如果要连接到 Excel 电子表格或 Access 数据库,则需要创建一个 OleDbConnection 对象,并向其传递一个带有目标数据源详细信息的连接字符串。您需要为 Provider 属性指定 Jet 4.0 OLE DB 提供程序。
让我们看一看用于 Access 数据库的典型连接字符串:
"Provider=Microsoft.Jet.OLEDB.4.0;Data Source= C:\Databases\mymusic.mdb"
这非常简单 - 它仅指定 Provider 和 Data Source 属性。如上所述,Provider 是 Jet 4.0 提供程序,而 Data Source 属性包含数据库的完全限定路径。
当然,您甚至还需要确保 Access 数据库的安全,对不对?这样,您还需要指定工作组信息文件(默认情况下为 system.mdw)的路径,并提供用户 ID 和密码:
"Provider=Microsoft.Jet.OLEDB.4.0;Data " & _ "Source=C:\Databases\mymusic.mdb; " & _ "Jet OLEDB:System database=" & _ "C:\Databases\system.mdw; " & _ "User ID=UserX;Password=UserXPassword"
请注意,指定控制 Access 数据库安全性的 .mdw 文件时需要使用 Jet OLE DB 特定的属性。如果希望更好地控制数据库的行为,则可以浏览 Jet OLE DB 提供程序(该提供程序位于 Appendix B:Microsoft Jet 4.0 OLE DB Properties Reference)所提供的其他属性,来控制锁定行为和如何处理特定类型的失败,等等。请注意,只有那些可以在连接字符串中设置的属性才可以访问;指定提供程序的属性之前需要连接处于打开状态,否则将无法设置这些属性。
可以使用 Microsoft® Visual Studio® .NET,来获取包括所有的 Jet OLE DB 提供程序设置、完整的 Access 连接字符串的模板。在服务器资源管理器中创建一个与 Access 数据库的数据连接,然后使用工具箱的数据部分创建一个 OleDbConnection 对象。得到的对象的 ConnectionString 属性会包含默认的所有 Jet OLE DB 提供程序属性。
现在,让我们看看 Excel 电子表格的典型连接字符串的外观:
"Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=C:\Spreadsheets\calculations.xls;" & _ "Extended Properties=Excel 8.0"
在连接到 Excel 文件的情况下,可以看到我们必须在连接字符串中使用 Extended Properties 属性以及 Provider 和 Data Source。如果是 Excel 8.0 和更高版本,则使用“Excel 8.0”设置。如果希望获取此属性的其他可接受值的更多信息,请参阅 ADO Provider Properties and Settings 的“Extended Properties Property Settings”部分。
您会说:“啊,但是 Excel 中的安全性怎么样?”嗯,恐怕没有激动人心的消息。您无法打开与受密码保护的电子表格的连接,除非已手动地在 Excel 中打开此电子表格(有关详细信息,请参阅 XL2000:"Could Not Decrypt File" Error with Password Protected File)。描述的错误出现于 Excel ODBC 提供程序,但是在 Jet 4.0 OLE DB 提供程序中该行为相同。其他选项为,删除电子表格的密码并使用某些其他安全机制(例如,限制文件所在文件夹的权限)来控制访问。
不幸的是,您也无法使用 Visual Studio .NET 获取 Excel 连接字符串的模板。进行一些小小的尝试,即可创建 Excel 数据连接;但是您会发现其属性不可编辑,而且 ConnectionString 属性将保留空白 - 这是 IDE(集成的开发环境)中的奇特之处。有关详细信息,请参阅 PRB:Cannot Configure Data Connection to Excel Files in Visual Studio .NET。这样,对于 Excel 连接,基本上都需要自己进行工作,但在这种情况下对连接进行编码就像在用户界面 (UI) 中创建连接一样简单。
现在已经明白了如何建立与 Office 数据源的连接,就让我们看看它是怎样检索数据的。出于简单性的考虑,我打算使用 OleDbConnection/OleDbCommand/OleDbDataReader 数据检索方案。相同的方法在稍稍调整之后,可以用于构建 OleDbDataAdapter 并填充 DataSet。如果想要大体上了解一下 ADO.NET,请参阅《.NET Framework Developer's Guide》的 Accessing Data with ADO.NET 部分,它会为您带来阅读的乐趣。
编写 Access 数据检索代码时请切记,指定 SQL 时必须遵循的语法具有某些特性。无法在图形 UI 中创建 Access 查询,无法访问 SQL 视图,无法复制得到的 SQL 语句并将其粘贴到代码中。不,这本应非常简单的。生成的 SQL 代码通常具有一部分但不是全部的所需语法。必须在 Access 开发环境中编写代码的任何人都会知道这一点,但是对于一般的 .NET 客户端应用程序开发人员而言,这算的上是新闻。最让人烦心的是条件表达式,它需要以某种方式分隔 WHERE 字句中的特定类型的数据。日期和时间值必须使用数字记号 (#) 进行分隔。文本值必须使用单引号 (') 进行分隔。例如:
SELECT City, Neighborhood, SalePrice, MonthsOnMarket FROM RealEstate WHERE ListingDate > #1/1/04#
有关这些问题的详细信息,请参阅 Date and Time Criteria Expressions。
需要记住的另一点看上去好像显而易见,但是却使人们感到困惑,所以我将讨论这一点:请确保 Access 表的列名称不使用保留字。可以在 SQL Reserved Words 处查询保留字。如果使用任何保留字,我想说的是如果可能就重命名列。我知道如果您已使用数据库,该操作则不太可能,所以必要时可以创建查询并将其用作有问题列的表的替换查询。仅使用 AS 重命名列,就如:
SELECT Artists.ArtistName, Genres.Genre, Labels.Label, Tracks.Public AS Track, Releases.ReleaseName FROM (Labels INNER JOIN ((Artists...
记住这些要点,让我们来看一个示例:
Imports System Imports System.Data Imports System.Data.OleDb ... Public Function GetAccessData(ByVal UID As String, _ ByVal pwd As String, ByVal artist As String) Dim conn As New OleDbConnection Dim musicReader As OleDbDataReader Dim cmd As New OleDbCommand Dim connString As String Dim i As Integer Try ' 设置连接字符串。 connString = "Jet OLEDB:System database=" & _ "C:\Databases\system.mdw;" & _ "Data Source=C:\Databases\mymusic.mdb;" & _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "User ID=" & UID & ";Password=" & pwd ' 打开连接。 conn.ConnectionString = connString conn.Open() '设置命令属性。 cmd.Connection = conn cmd.CommandText = "SELECT * from music " & _ "WHERE ArtistName = '" & artist & "'" ' 获取 OleDbDataReader ' 并对其进行一些处理。 musicReader = _ cmd.ExecuteReader(CommandBehavior.CloseConnection) Try While (musicReader.Read) '处理数据。 End While Finally musicReader.Close() End Try Catch ex As Exception '错误处理 End Try End Function 'GetAccessData
关于列名称,Excel 和 Access 一样具有相同的保留字限制。一般而言,如果记住 SQL 保留字,并在创建可能用作数据源的任何对象时避开保留字,则会更好。还有一点需要考虑。
Excel 的语法同样也有奇特之处。对代码影响最大的项是用于引用要返回的数据集的语法。
注意:对于最简单的 Excel 数据检索,请使用维护类似表格式的电子表格。
第一个选项是指定工作表和该表中的单元格集(可选)。需要确保工作表名称后跟美元符号和单元格集(可选)。通过使用冒号分隔集合中的起始单元格和终止单元格,来指定此单元格集。然后,使用括号将整个数据标识字符串括起。使用此类型语法的 SELECT 语句可能如下所示:
SELECT SalesMonth, TotalSales, PercentageChange1Year FROM [Sheet1$A1:E24]
另一个选项是在 Excel 中创建一个命名范围,它将起到类似于表的作用。要创建命名范围,请参阅 Create named cell references or ranges。要使用的范围名称就像 SELECT 语句中的表名称:
SELECT SalesMonth, TotalSales, PercentageChange1Year FROM SalesHighlights
记住这些要点,让我们来看一个示例:
Imports System Imports System.Data Imports System.Data.OleDb ... Public Function GetExcelData() Dim conn As New OleDbConnection Dim salesReader As OleDbDataReader Dim connString As String Dim cmd As New OleDbCommand Try ' 设置连接字符串。 connString = "Data Source=" & _ "C:\Spreadsheets\calculations.xls;" & _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Extended Properties=Excel 8.0;" ' 打开连接。 conn.ConnectionString = connString conn.Open() '设置命令属性。 cmd.Connection = conn cmd.CommandText = "SELECT SalesMonth, " & _ "TotalSales, PercentageChange1Year, " & _ "VolumeDiscounts, Profit from [Sheet1$]" ' 获取 OleDbDataReader ' 并对其进行一些处理。 salesReader = _ cmd.ExecuteReader(CommandBehavior.CloseConnection) Try While (salesReader.Read) '处理数据。 End While Finally salesReader.Close() End Try Catch ex As Exception '错误处理 End Try End Function 'GetExcelData
现在可以连接到 Excel 或 Access 数据源并检索数据,让我们再深入一步。让我们看看从其中一个数据源检索元数据时需要执行的操作,随后您可以使用数据源构造数据访问代码。如果希望创建的过程提供某些通用功能,并且不希望将这些功能绑定在特定数据源上,则该进程可能会有用处。
要获取所需的元数据,则需要使用 OleDbConnection.GetOleDbSchemaTable 方法。此方法的构造函数采用一个代表 OLE DB 架构行集的 OleDbSchemaGuid 对象,和一组代表要返回的架构信息的选择条件基本内容的对象。
注意:对于那些不熟悉 OLE DB 架构行集的人而言,它们基本上是由 ANSI SQL-92 定义的数据库构造的标准化架构。每个架构行集具有为指定构造提供定义元数据的一组列(称作 .NET 文档中的“限制列”)。这样,如果请求架构信息(例如,列的架构信息或排序规则的架构信息),则您会明确知道可以得到哪种类型的数据。如果希望了解更多信息,请访问 Appendix B:Schema Rowsets。
Object 数组在文档中定义为“限制值的数组”。它用于确定(即限制)返回的数据集,有些类似于 SQL 中的 WHERE 子句。例如,连接到具有工作表 Alpha、Beta 和 Pi 的工作簿。需要架构信息来确定工作表 Beta 所包含的列。您的代码将如下所示:
schemaTable = conn.GetOleDbSchemaTable(OleDbSchemaGuid.Columns, _ New Object() {Nothing, Nothing, _ "Beta", Nothing})
使用 OleDbSchemaGuid.Columns 字段,来表示因需要返回列信息而应使用 COLUMNS 架构。COLUMNS 架构包含 TABLE_CATALOG、TABLE_SCHEMA、TABLE_NAME 和 COLUMN_NAME 限制列,并且需要提供代表数组中每个限制列的限制值的对象。通过将“Beta”指定为 TABLE_NAME 值,可以将返回的列信息限制为仅来自于“表”的信息。
现在,您非常熟悉我们的朋友 GetOleDbSchemaTable,让我们来看看它是如何实现的。通过使用它遍历数据源中的表和列,可以获取检索数据时所需的所有信息,而无须提前熟悉架构。让我们来看看使用 Excel 的一个示例:
Imports System Imports System.Data Imports System.Data.OleDb ... Public Function GetExcelSchema(ByVal xlsPath As String) As DataSet Dim schemaTable As New DataTable Dim workAdapter As New OleDbDataAdapter Dim workSet As New DataSet Dim conn As New OleDbConnection Dim i As Integer Dim x As Integer Dim charArray As Char() = {",", " "} Dim charArray2 As Char() = {"$"} Dim cmdString As String Dim cmdString2 As String Dim cmd As New OleDbCommand Dim tableName As String workSet.DataSetName = "excelData" Try ' 设置连接字符串。 Dim connString As String = _ "Data Source=" & xlsPath & _ ";Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Extended Properties=Excel 8.0" ' 打开连接。 conn.ConnectionString = connString conn.Open() ' 使用数据源表中的架构信息 ' 填充 DataTable。 schemaTable = _ conn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, _ New Object() {Nothing, Nothing, Nothing, "TABLE"}) ' 使用表名称填充数组。 i = schemaTable.Rows.Count - 1 Dim tablesArray(i) As String For i = 0 To schemaTable.Rows.Count - 1 tablesArray(i) = schemaTable.Rows(i).Item("Table_Name") Next ' 清除 DataTable schemaTable.Clear() ' 使用表名称和列架构 ' 信息来构造 SELECT 语句, ' 并为数据源中的每个表返回数据。 For i = 0 To tablesArray.GetLength(0) - 1 ' 使用数据源列中的架构信息 ' 填充 DataTable。 schemaTable = _ conn.GetOleDbSchemaTable(OleDbSchemaGuid.Columns, _ New Object() {Nothing, Nothing, _ tablesArray(i).ToString(), Nothing}) ' 逐个查看列名称,并将其附加到 ' SELECT 语句中 cmdString = "SELECT " For x = 0 To schemaTable.Rows.Count - 1 cmdString = cmdString & _ schemaTable.Rows(x).Item("Column_Name") & _ ", " Next cmdString2 = cmdString.TrimEnd(charArray) ' 请注意,无须将“$”附加到 ' 表名称中 - 它已包括在内。 cmdString2 = cmdString2 & " FROM [" & _ tablesArray(i).ToString() & "]" ' 使用 SELECT 命令和 ' OleDbDataAdapter 填充 DataSet。 cmd.CommandText = cmdString2 workAdapter.SelectCommand = cmd workAdapter.SelectCommand.Connection = conn tableName = _ tablesArray(i).ToString().TrimEnd(charArray2) workAdapter.Fill(workSet, tableName) schemaTable.Clear() Next Catch ex As Exception '错误处理 Finally conn.Close() End Try Return workSet End Function 'GetExcelSchema
正如您所看到的,涉及的代码相当简单。如果想对 Access 数据库进行同样的操作,唯一真正的不同之处在于连接字符串,而无须将表名称格式化为工作表并使其可在 SELECT 语句中使用。
另外,可以将此方法用于 SQL Server™ 数据库,效果也很好;或者可以对其进行修改,使其根据从数据源中获取的架构信息执行其他操作。如果要尝试执行任何类型的查找或文档管理,这将非常有用。
非常感谢与我一起完成了这个将 Office 数据和 ADO.NET 配合使用的简短介绍。它并不是什么特殊的任务,而且我认为此任务本不应有这么高的难度,只是因为此任务的某些基本信息不容易获取。无论您是只希望从部门的 Access 数据库中拖入信息,还是希望从每个人的每月费用电子表格中收集数据,我都希望可以为您提供一个开始的平台。