使用 Microsoft Access 2010 进行数据编程

摘要:  了解如何使用 Microsoft Office Access 2007 或 Microsoft Access 2010 开发本机(C、C++、Java、VBA)或托管的(C#、Visual Basic.NET)数据访问代码。了解 Access 体系结构、ACE 引擎和数据提供程序、32 位和 64 位平台,以及为新的或旧的数据库项目选择最佳的数据访问技术时要注意的事项。

上次修改时间: 2015年4月24日

适用范围: Access 2007 | Access 2010 | Office 2010

本文内容
概述
Microsoft Access 引擎的体系结构
数据访问技术的概述
性能数据
使用 32 位和 64 位 ACE 提供程序
选择数据访问技术时的注意事项
弃用的数据访问方法
结论

Applies to:   Microsoft Access 2010 | Microsoft Office Access 2007

**发布时间:**2010 年 9 月

**供稿人:**Aleksandar Jakšić,Microsoft Corporation

目录

  • 概述

  • Microsoft Access 引擎的体系结构

  • 数据访问技术的概述

  • 性能数据

  • 使用 32 位和 64 位 ACE 提供程序

  • 选择数据访问技术时的注意事项

  • 弃用的数据访问方法

  • 结论

单击以获取代码  下载代码

概述

本技术文章探讨 Access 2007 和 Access 2010 的许多数据访问编程功能要点。它讨论 Access 引擎,也称为 Microsoft Access 数据库引擎(ACE 引擎)。如果您是一名数据库开发人员,需要了解更多有关开发 Access 数据库的数据访问解决方案时可用选项的信息,那么这篇文章非常适合您。本文并不尝试向您介绍此处所提及的每个数据访问接口的详细信息,而是旨在从体系结构方面向您概括介绍 ACE 引擎,以便您能更好地了解基于该引擎构建并在该引擎中运行的编程模型。随附的示例代码同时采用本机(C、C++、Java 和 VBA)语言和 .NET Framework(C# 和 Visual Basic.NET)语言编写,旨在帮助您快速入门,即使您以前从未用其中任何语言编程也无妨。

本技术文章可解答类似以下问题:

  • 我的旧式 32 位应用程序是否可以使用 Access 64 位软件?

  • 通常以编程方式使用 Access 数据库的最快方式是什么?

  • 是否存在一种可以使用 C 编程语言 (ANSI/ISO C) 或者 Java 连接至 ACE 引擎的方式?

  • 能否使用我的旧式 Microsoft 基础类 (MFC) 数据访问对象 (DAO) 代码处理 .accdb 数据库?

代码示例

本技术文章提供了使用 Microsoft Visual Studio 2008 创建并可供下载的九个完整的程序。

每个代码示例使用不同的编程语言和各种数据访问技术(例如 DAO、OLE DB、ADO.NET、ADO、ODBC 或 JDBC)执行相同的数据访问算法。每个程序的控制台输出几乎都一致。示例演示了核心数据访问编程功能,例如使用用于解密数据库的强密码构建连接字符串、连接至加密的数据库、构建和执行 SQL 查询、使用架构和记录集,以及检索排序的数据。

由于 Access 引擎不属于 Windows 操作系统的一部分,因此您必须在本地计算机上安装 ACE 引擎和其提供程序。有关详细信息,请参阅可从何处获得 ACE 引擎?

本技术文章还提供包括在代码示例中的所有数据提供程序的性能数据。

本技术文章面向的对象

数据访问技术主题的范围很广,由于本指南面向广大的开发人员受众,因此任何针对业务逻辑或应用程序级别使用 Access 数据的开发人员都可以从本文受益。而且,本技术文章在编写时还考虑到将受众扩展至 IT 工作者、学生或数据库编程发烧友。

假定您已了解 Visual Studio 环境、(结构和 .NET Framework)编程的基础知识,并熟悉关系数据库和 SQL 的概念。还假定您可以熟练地在 Microsoft Access 之前的任何版本中创建表和查询。

备注

本文中谈及 Access 时,如果未指明 Microsoft Access 的特定版本(例如 Microsoft Access 2003),则假定此信息同时适用于 Microsoft Office Access 2007 和 Microsoft Access 2010。

Microsoft Access 引擎的体系结构

图 1 显示 Access (UI) 和 ACE(引擎)如何组成完整的数据库管理系统 (DBMS)。

图 1. Access 2010 的概念图

Access 2010 的概括性概念关系图

Access UI 决定着用户界面和用户通过窗体、报表、查询、宏、向导等查看、编辑和使用数据的所有方式。另一方面,Microsoft Access 引擎(ACE 引擎)提供诸如以下的核心数据库管理服务:

  • 数据存储:将数据存储在文件系统中。

  • 数据定义:创建、编辑或删除用于存储诸如表和字段等数据的结构。

  • 数据完整性:强制防止数据损坏的关系规则。

  • 数据操作:添加、编辑、删除或排序现有数据。

  • 数据检索:使用 SQL 从系统检索数据。

  • 数据加密:保护数据以免遭受未经授权的使用。

  • 数据共享:在多用户网络环境中共享数据。

  • 数据发布:在客户端或服务器 Web 环境中工作。

  • 数据导入、导出和链接:处理来自不同源的数据。

从数据访问的角度来讲,您可以将 Access 视为以图形方式将 ACE 引擎公开给用户的方式。

可从何处获得 ACE 引擎?

若要运行本技术文章中提供的代码示例,计算机上必须具有 ACE 引擎,这意味着您必须安装以下 Access 2010(或 Office Access 2007)产品之一:

备注

Microsoft Access Database Engine 2007 和 2010 驱动程序已发布,以便解决方案开发人员可以生成工具来读取和写入 Office 文件(例如 .accdb, .xlsx 和 .xlsb)。它允许解决方案构建者拥有一台服务器(例如 SQL)来读取和编写 Office 文件格式,而无需让他们在服务器上安装 Office。但是,未授权此驱动程序用作独立的数据存储。

Microsoft JET 引擎有何变化?

在 Access 2007 之前,Access 使用 Microsoft 连接性引擎技术 (JET) 引擎。尽管 JET 通常被视为 Access 的一部分,但是 JET 引擎却被用作一个单独的产品。自从 Microsoft Windows 2000 发布之后,JET 已成为 Windows 操作系统的一部分,然后通过 Microsoft 数据访问组件 (MDAC) 分发或更新。但在 Access 2007 版本之后,JET 引擎已被弃用并不再通过 MDAC 进行分发。现在,Access 改为使用集成和改进的 ACE 引擎,通过拍摄原始 JET 基本代码的代码快照来开始对该引擎进行开发。

ACE 引擎与以前版本的 JET 引擎完全向后兼容,以便从早期 Access 版本读取和写入 (.mdb) 文件。由于 Access 团队现在拥有引擎,因此开发人员可以相信他们的 Access 解决方案不仅可以在未来继续使用,而且具有更快的速度、更强的可靠性和更丰富的功能。例如,对于 Access 2010 版本,除了其他改进,ACE 引擎还进行了升级,可以支持 64 位的版本,并从整体上增强与 SharePoint 相关技术和 Web 服务的集成。Microsoft 努力将 Access 作为一个开发人员平台进行开发。

数据访问技术的概述

Microsoft 提供多种方式使用 Access 数据库。以下数据访问 API 和数据访问层均用于 Access 编程:

  • 数据访问对象 (DAO)

  • 对象链接和嵌入,数据库 (OLE DB)

  • ADO.NET

  • ActiveX 数据对象 (ADO)

  • 开放式数据库连接 (ODBC)

ACE 引擎实现以上所提及的三种技术的提供程序:DAO、OLE DB 和 ODBC。ACE DAO 提供程序、ACE OLE DB 提供程序和 ACE ODBC 提供程序通过 Access 产品(不包括 ADO,其仍为 Microsoft Windows DAC 的一部分)分发。许多其他数据访问编程接口、提供程序和系统级别的框架(包括 ADO 和 ADO.NET)均构建于这三个 ACE 提供程序之上。图 2 显示了表示 Access 组件概况的图。

图 2. 数据访问编程环境中 ACE 引擎的体系结构

ACE 体系结构关系图

本技术文章介绍了下表中列出的数据访问方法。

表 1. 数据访问方法

提供程序名称

数据访问方法

连接信息

支持的语言

ACE DAO

Direct DAO 示例

Acedao.tlh(从 acedao.dll 生成);

acedao.dll

C++

VBA DAO 示例

Set db = CurrentDb()

在 VBE 环境中运行

VBA

ACE OLE DB

ATL OLE DB 示例

Microsoft.ACE.OLEDB.12.0

<Atldbcli.h> 和 <Atldbsch.h>;Aceoledb.dll

C++

ADO.NET

C# ADO.NET 示例

Microsoft.ACE.OLEDB.12.0

使用 System.Data.OleDb;

C#

Visual Basic.NET ADO.NET 示例

Visual Basic.NET ADO.NET 示例

Microsoft.ACE.OLEDB.12.0

导入 System.Data.OleDb

导入 System.Console

Visual Basic.NET

ADO

ADO 示例

Msado15.tlh(从 Msado15.dll 生成);

通过 MDAC 2.8 或 Windows DAC 6.0 安装。

C++

ACE ODBC

Direct ODBC 示例

Driver={Microsoft Access Driver (*.mdb, *.accdb)};DBQ= mdb/accdb 文件的路径

<Sqlext.h>;

Aceodbc.dll;

C/C++

MFC ODBC 示例

Driver={Microsoft Access Driver (*.mdb, *.accdb)};DBQ= mdb/accdb 文件的路径

<Afxdb.h>;

Aceodbc.dll;

C++

JDBC-ODBC 示例

jdbc:odbc:DRIVER=Microsoft Access Driver (*.mdb, *.accdb);DBQ= mdb/accdb 文件的路径

Java

有关 Access 2007 版本弃用的数据访问方法列表的详细信息,请参阅弃用的数据访问方法。

DAO

最初,DAO 为 Access 开发人员的专用数据访问方法。

Direct DAO 示例

此访问方法提供对 Access 2007 中引入的新功能的最佳支持,这是因为它公开大多数 ACE 引擎功能。Direct DAO 使用 Acedao.dll。若要编译此代码,请使用 #import 宏通过指定类似于下面代码示例中所显示的指令来生成 .tlh 标头。

#import <C:\\Program Files (x86)\\Common Files\\Microsoft Shared\\OFFICE14\\ACEDAO.dll>  \
    rename( "EOF", "AdoNSEOF" )

根据您是在 32 位还是 64 位操作系统上编译代码,您可能需要更新此路径以删除"(x86)"部分。确认此情况的一种方法是检查以下任一注册表路径下的类型为 REG_SZ、名为 Path 的注册表项:

  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\14.0\Access Connectivity Engine\InstallRoot

  • HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Office\14.0\Access Connectivity Engine\InstallRoot

因为其直接连接至引擎,所以连接信息只需数据库位置即可。

_bstr_t bstrConnect = "C:\\Northwind.accdb";

下面的代码示例获取架构和数据。

备注

若要访问完整的示例应用程序,请下载示例代码。

// Create an instance of the engine
DAO::_DBEngine* pEngine = NULL;

// The CoCreateInstance helper function provides a convenient shortcut by connecting 
// to the class object associated with the specified CLSID, creating an 
// uninitialized instance, and releasing the class object. 
hr = CoCreateInstance(
    __uuidof(DAO::DBEngine),
    NULL,
    CLSCTX_ALL,
    IID_IDispatch,
    (LPVOID*)&pEngine);
if (SUCCEEDED(hr) && pEngine)
{
    // COM errors are handled by C++ try/catch block
    try
    {
        DAO::DatabasePtr pDbPtr = NULL;
        pDbPtr = pEngine->OpenDatabase(bstrConnect, false, false, ";PWD=1L0v3Acce55;");
        if (pDbPtr)
        {
            cout<<DAM<<": Successfully connected to database. Data source name:\n  "
                <<pDbPtr->GetName()<<endl;

            // Prepare SQL query.
            _bstr_t query = "SELECT Customers.[Company], Customers.[First Name] FROM Customers ORDER BY Customers.[Company] ASC;";
            cout<<DAM<<": SQL query:\n  "<<query<<endl;

            // Run the query and create a record set
            DAO::RecordsetPtr pRS = NULL;
            pRS = pDbPtr->OpenRecordset(query, _variant_t(DAO::dbOpenDynaset));
            if (pRS && 0 < pRS->RecordCount)
            {
                cout<<DAM<<": Retrieve schema info for the given result set: "<<endl;
                DAO::FieldsPtr pFields = NULL;
                pFields = pRS->GetFields();
                if (pFields && pFields->Count > 0)
                {
                    for (short column = 0; column < pFields->Count; column++)
                    {
                        cout<<" | "<<pFields->GetItem(column)->GetName();
                    }
                    cout<<endl;
                }
                else
                {
                    cout<<DAM<<": Error: Number of fields in the result set is 0."<<endl;
                }

                cout<<DAM<<": Fetch the actual data: "<<endl;
                // Loop through the rows in the result set
                while (!pRS->AdoNSEOF)
                {
                    for (short column = 0; column < pFields->Count; column++)
                    {
                        cout<<" | "<<_bstr_t(pFields->GetItem(column)->GetValue());
                    }
                    cout<<endl;
                    pRS->MoveNext();
                }
                cout<<DAM<<": Total Row Count: "<<pRS->RecordCount<<endl;
            }

            // Close record set and database
            pRS->Close();
            pDbPtr->Close();
            pDbPtr = NULL;
        }
        else
        {
            cout<<DAM<<": Unable to connect to data source: "<<bstrConnect<<endl;
        }
    }
    catch(_com_error& e)
    {
        cout<<DAM<<": _com_error: "<<e.ErrorMessage()<<endl;
    }
    
    pEngine->Release();
    pEngine = NULL;
    cout<<DAM<<": Cleanup. Done."<<endl;
}
else
{
    cout<<DAM<<": Cannot instantiate DBEngine object."<<endl;
}

下面的示例显示程序输出。

Direct DAO: Successfully connected to database. Data source name:
  C:\Northwind.accdb
Direct DAO: SQL query:
  SELECT Customers.[Company], Customers.[First Name] FROM Customers ORDER BY Customers.[Company] ASC;
Direct DAO: Retrieve schema info for the given result set:
 | Company | First Name
Direct DAO: Fetch the actual data:
 | Company A | Anna
 | Company AA | Karen
 | Company B | Antonio
 | Company BB | Amritansh
 | Company C | Thomas
 | Company CC | Soo Jung
 | Company D | Christina
 | Company E | Martin
 | Company F | Francisco
 | Company G | Ming-Yang
 | Company H | Elizabeth
 | Company I | Sven
 | Company J | Roland
 | Company K | Peter
 | Company L | John
 | Company M | Andre
 | Company N | Carlos
 | Company O | Helena
 | Company P | Daniel
 | Company Q | Jean Philippe
 | Company R | Catherine
 | Company S | Alexander
 | Company T | George
 | Company U | Bernard
 | Company V | Luciana
 | Company W | Michael
 | Company X | Jonas
 | Company Y | John
 | Company Z | Run
Direct DAO: Total Row Count: 29
Direct DAO: Cleanup. Done.

VBA DAO 示例

下面的代码示例在 Access VBA/VBE 环境中当前的数据库上运行。

    Public Sub VBADAO()
        Dim DAM As String
        Dim db As DAO.Database
        Dim rst As DAO.Recordset
        Dim query As String
            
        DAM = "VBA DAO"
    
        ' Open pointer to current database
        Set db = CurrentDb()
       
        Debug.Print DAM & ": Successfully connected to database. Data source name: " & _
            vbNewLine & "  " & db.Name
        
        ' Prepare SQL query
        query = "SELECT Customers.[Company], Customers.[First Name] " & _
            "FROM Customers " & _
            "ORDER BY Customers.[Company] ASC"
            
        Debug.Print DAM & ": SQL Query: " & _
            vbNewLine & "  " & query
    
        ' Run the query and create a record set
        Set rst = db.OpenRecordset(query)
        
        Debug.Print DAM & ": Retrieve schema info for the given result set: "
        For i = 0 To rst.Fields.Count - 1
            Debug.Print " | " & rst.Fields(i).Name
        Next i
            
        Debug.Print DAM & ": Fetch the actual data: "
        Do While Not rst.EOF
            Debug.Print " | " & rst![Company] & " | " & rst![First Name]
            rst.MoveNext
        Loop
        
        Debug.Print DAM & ": Total Row Count: " & rst.RecordCount
        Debug.Print DAM & ": Cleanup. Done. "
    
        rst.Close
        db.Close
    End Sub

OLE DB

OLE DB 是用于访问数据的 Microsoft 系统级别编程接口。它是一个规范而不是一组组件或文件。它是 ADO 的基本技术和 ADO.NET 的数据源。OLE DB 指定一组封装各种数据库管理系统服务的 COM 接口来为使用者提供服务。OLE DB 是用于访问各种数据(包括 Access 数据库)的开放式标准。它使用关系数据库和其他存储中数据的即时连接来支持包括创建前端数据库客户端和中间层业务对象的开发要求。

ATL OLE DB 示例

本示例通过包括 <Atldbcli.h> 和 <Atldbsch.h> 来使用活动模板库 (ATL)。连接信息使用 Aceoledb.dll 中实现的 Microsoft.ACE.OLEDB.12.0 数据提供程序。

LPCOLESTR lpcOleConnect = 
    L"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\\Northwind.accdb;Jet OLEDB:Database Password=1L0v3Acce55;";

下面的代码示例获取架构和数据。

// To initialize the connection to a database using an OLE DB provider, 
// two ATL classes are needed: CDataSource and CSession;
CDataSource dbDataSource;
CSession dbSession;

// Uses the ATL string conversion macros to convert between character encodings
USES_CONVERSION;

// Open the connection and initialize the data source specified by the passed 
// initialization string.
hr = dbDataSource.OpenFromInitializationString(lpcOleConnect);
if (FAILED(hr))
{
    cout<<DAM<<": Unable to connect to data source "<<OLE2T(lpcOleConnect)<<endl;
}
else
{
    hr = dbSession.Open(dbDataSource);
    if (FAILED(hr))
    {
        cout<<DAM<<": Couldn't create session on data source "<<OLE2T(lpcOleConnect)<<endl;
    }
    else
    {
        CComVariant var;
        hr = dbDataSource.GetProperty(DBPROPSET_DATASOURCEINFO, DBPROP_DATASOURCENAME, &var);
        if (FAILED(hr) || (var.vt == VT_EMPTY))
        {
            cout<<DAM<<": No Data Source Name Specified."<<endl;
        }
        else
        {
            cout<<DAM<<": Successfully connected to database. Data source name:\n  "
                <<COLE2T(var.bstrVal)<<endl;
            
            // Prepare SQL query.
            LPCOLESTR query = L"SELECT Customers.[Company], Customers.[First Name] FROM Customers ORDER BY Customers.[Company] ASC;";
            cout<<DAM<<": SQL query:\n  "<<OLE2T(query)<<endl;

            // Run the query and create a record set
            CCommand<CDynamicStringAccessor> cmd;
            hr = cmd.Open(dbSession, query);
            DBORDINAL colCount = cmd.GetColumnCount();
            if (SUCCEEDED(hr) && 0 < colCount)
            {
                cout<<DAM<<": Retrieve schema info for the given result set: "<<endl;
                DBORDINAL cColumns;
                DBCOLUMNINFO* rgInfo = NULL;
                OLECHAR* pStringsBuffer = NULL;
                cmd.GetColumnInfo(&cColumns, &rgInfo, &pStringsBuffer);
                for (int col=0; col < (int)colCount; col++)
                {
                    cout<<" | "<<OLE2T(rgInfo[col].pwszName);
                }
                cout<<endl;

                cout<<DAM<<": Fetch the actual data: "<<endl;
                int rowCount = 0;
                CRowset<CDynamicStringAccessor>* pRS = (CRowset<CDynamicStringAccessor>*)&cmd;
                // Loop through the rows in the result set
                while (pRS->MoveNext() == S_OK)
                {
                    for (int col=1; col <= (int)colCount; col++)
                    {
                        CHAR* szValue = cmd.GetString(col);
                        cout<<" | "<<szValue;
                    }
                    cout<<endl;
                    rowCount++;
                }
                cout<<DAM<<": Total Row Count: "<<rowCount<<endl;
            }                   
            else
            {
                cout<<DAM<<": Error: Number of fields in the result set is 0."<<endl;
            }
        }  
    }
}

dbDataSource.Close();
dbSession.Close();

cout<<DAM<<": Cleanup. Done."<<endl;

ADO.NET

ADO.NET 提供针对 .NET(托管)环境内各种数据源的统一和综合的数据访问。ADO.NET 使用 .NET 托管提供程序,而此托管程序又使用基础的 API(例如 OLE DB 和 ODBC)。本技术文章提供了两个 ADO.NET 示例(C# 和 Visual Basic.NET),都使用基础的 OLE DB 作为数据访问层。

C# ADO.NET 示例

以下为 C# ADO.NET 代码示例。

// Connection string for ADO.NET via OleDB
OleDbConnection cn = 
    new OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\\Northwind.accdb;Jet OLEDB:Database Password=1L0v3Acce55;");

// Prepare SQL query
string query = "SELECT Customers.[Company], Customers.[First Name] FROM Customers ORDER BY Customers.[Company] ASC;";
OleDbCommand cmd = new OleDbCommand(query, cn);

try
{
    cn.Open();
    Console.WriteLine("{0}: Successfully connected to database. Data source name:\n {1}", 
        DAM, cn.DataSource);
    Console.WriteLine("{0}: SQL query:\n {1}", DAM, query);

    // Run the query and create a record set
    OleDbDataReader dr = cmd.ExecuteReader();
    Console.WriteLine("{0}: Retrieve schema info for the given result set:", DAM);
    for (int column = 0; column < dr.FieldCount; column++)
    {
        Console.Write(" | {0}", dr.GetName(column));
    }
    Console.WriteLine("\n{0}: Fetch the actual data: ", DAM);
    int row = 0;
    while (dr.Read())
    {
        Console.WriteLine(" | {0} | {1} ", dr.GetValue(0), dr.GetValue(1));
        row++;
    }
    Console.WriteLine("{0}: Total Row Count: {1}", DAM, row);
    dr.Close();
}
catch (OleDbException ex)
{
    Console.WriteLine("{0}: OleDbException: Unable to connect or retrieve data from data source: {1}.",
        DAM, ex.ToString());
}
catch (Exception ex)
{
    Console.WriteLine("{0}: Exception: Unable to connect or retrieve data from data source: {1}.",
        DAM, ex.ToString());
}
finally
{
    cn.Close();
    Console.WriteLine("{0}: Cleanup. Done.", DAM);
}

Visual Basic.NET ADO.NET 示例

以下为 Visual Basic.NET ADO.NET 代码示例。

' Connection string for ADO.NET via OleDB
Dim cn As OleDbConnection = 
    New OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\\Northwind.accdb;Jet OLEDB:Database Password=1L0v3Acce55;")
Dim cmd As OleDbCommand
Dim dr As OleDbDataReader

Try
    cn.Open()
    WriteLine(DAM + ": : Successfully connected to database. Data source name:" + ControlChars.Lf + "  " + cn.DataSource)
    ' Prepare SQL query.
    Dim query As String = "SELECT Customers.[Company], Customers.[First Name] FROM Customers ORDER BY Customers.[Company] ASC;"
    WriteLine(DAM + ": SQL Query:" + ControlChars.Lf + "  " + query)

    ' Run the query and create a record set
    cmd = New OleDbCommand(query, cn)
    dr = cmd.ExecuteReader
    WriteLine(DAM + ": Retrieve schema info for the given result set:  ")
    Dim column, row As Integer
    For column = 0 To dr.FieldCount - 1
        Write(" | " + dr.GetName(column))
    Next column
    WriteLine(ControlChars.Lf + DAM + ": Fetch the actual data:  ")
    row = 0
    While dr.Read()
        WriteLine(" | " + dr(0) + " | " + dr(1))
        row += 1
    End While
    WriteLine(DAM + ": Total Row Count: " + row.ToString())
    dr.Close()
Catch ex As OleDbException
    WriteLine(ControlChars.Lf + DAM + ": OleDbException: Unable to connect or retrieve data from data source: " + ex.Message())
Catch ex As Exception
    WriteLine(ControlChars.Lf + DAM + ": Exception: Unable to connect or retrieve data from data source: " + ex.Message())
Finally
    cn.Close()
    WriteLine(DAM + ": Cleanup. Done.")
End Try

ADO

ActiveX 数据对象 (ADO) 为 OLE DB 数据提供程序提供基于 COM 的应用程序级接口。虽然与直接对 OLE DB 编码相比,性能有所降低,但 ADO 学习和使用起来非常简单。ADO 向 C++ 编程人员提供基本 OLE DB 接口的访问权限。大多数开发人员对此类较低级别的控制(例如管理内存资源和手动聚合组件,OLE DB 对数据访问过程提供此类控制)通常不感兴趣。

与 DAO(公开单个数据库引擎的功能)不同,ADO 使用常见的编程模型全局访问数据。

ADO 示例

ADO 使用 MDAC 2.8 或更高版本随附的 Msado15.dll 库。若要编译此代码,必须使用 #import 宏通过指定类似于下面代码示例中显示的指令来生成 .tlh 标头。

#import <C:\\Program Files\\Common Files\\System\\ado\\msado15.dll>  \
    rename( "EOF", "AdoNSEOF" )

对于连接信息,请使用 Aceoledb.dll 中实现的 Microsoft.ACE.OLEDB.12.0 数据提供程序。

_bstr_t bstrConnect = 
    "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\\Northwind.accdb;Jet OLEDB:Database Password=1L0v3Acce55;";

下面的代码示例获取架构和数据。

// COM errors are handled by C++ try/catch block
try
{
    ADODB::_ConnectionPtr pConn("ADODB.Connection");
    hr = pConn->Open(bstrConnect, "admin", "", ADODB::adConnectUnspecified);
    if (SUCCEEDED(hr))
    {
        cout<<DAM<<": Successfully connected to database. Data source name:\n  "
           <<pConn->GetConnectionString()<<endl;

        // Prepare SQL query.
        _bstr_t query = "SELECT Customers.[Company], Customers.[First Name] FROM Customers ORDER BY Customers.[Company] ASC;";
        cout<<DAM<<": SQL query:\n  "<<query<<endl;

        // Run the query and create a record set
        ADODB::_RecordsetPtr pRS("ADODB.Recordset");
        hr = pRS->Open(query, 
                _variant_t((IDispatch *) pConn, true), 
                ADODB::adOpenUnspecified,
                ADODB::adLockUnspecified, 
                ADODB::adCmdText);
        if (SUCCEEDED(hr))
        {
            cout<<DAM<<": Retrieve schema info for the given result set: "<<endl;
            ADODB::Fields* pFields = NULL;
            hr = pRS->get_Fields(&pFields);
            if (SUCCEEDED(hr) && pFields && pFields->GetCount() > 0)
            {
                for (long nIndex=0; nIndex < pFields->GetCount(); nIndex++)
                {
                    cout<<" | "<<_bstr_t(pFields->GetItem(nIndex)->GetName());
                }
                cout<<endl;
            }
            else
            {
                cout<<DAM<<": Error: Number of fields in the result set is 0."<<endl;
            }

            cout<<DAM<<": Fetch the actual data: "<<endl;
            int rowCount = 0;
            while (!pRS->AdoNSEOF)
            {
                for (long nIndex=0; nIndex < pFields->GetCount(); nIndex++)
                {
                    cout<<" | "<<_bstr_t(pFields->GetItem(nIndex)->GetValue());
                }
                cout<<endl;
                pRS->MoveNext();
                rowCount++;
            }
            cout<<DAM<<": Total Row Count: "<<rowCount<<endl;
        }
        
        pRS->Close();
        pConn->Close();
        cout<<DAM<<": Cleanup. Done."<<endl;
    }
    else
    {
        cout<<DAM<<": Unable to connect to data source: "<<bstrConnect<<endl;
    }
}
catch(_com_error& e)
{
    cout<<DAM<<": _com_error: "<<e.Description()<<endl;
}

ODBC

ODBC(开放式数据库连接)是 Microsoft 目前提供的数据访问技术中最古老的一种,它旨在使您能够创建常见基本代码,以便提供针对各种关系数据存储的访问权限。其方法在传统的、非面向对象的、类 C 语言的 API 中公开。

Direct ODBC 示例

建议只将此数据访问方法用于现有应用程序的维护,或仅在您必须使用 ANSI/ISO C 时使用。下面的代码示例显示 Direct ODBC 的连接信息,它通过包括 <Sqlext.h> 来使用 Aceodbc.dll 库中实现的 {Microsoft Access Driver (*.mdb, *.accdb)} 驱动程序。

SQLCHAR szDSN[256] = 
    "Driver={Microsoft Access Driver (*.mdb, *.accdb)};DSN='';DBQ=C:\\Northwind.accdb;PWD=1L0v3Acce55;";

下面的代码示例获取架构和数据。

HENV    hEnv;
HDBC    hDbc;

/* ODBC API return status */
SQLRETURN  rc;

SQLSMALLINT  iConnStrLength2Ptr;
SQLCHAR      szConnStrOut[255];

SQLCHAR* query = "SELECT Customers.[Company], Customers.[First Name] FROM Customers ORDER BY Customers.[Company] ASC;";

SQLCHAR         chval1[128], chval2[128], colName[128];
SQLINTEGER      ret1, ret2;

/* Number of rows and columns in result set */
SQLINTEGER      rowCount = 0;
SQLSMALLINT     fieldCount = 0, column = 0;
HSTMT           hStmt;

/* Allocate an environment handle */
rc = SQLAllocEnv(&hEnv);
/* Allocate a connection handle */
rc = SQLAllocConnect(hEnv, &hDbc);

/* Connect to the 'Northwind 2007.accdb' database */
rc = SQLDriverConnect(hDbc, NULL, szDSN,  _countof(szDSN), 
szConnStrOut, 255, &iConnStrLength2Ptr, SQL_DRIVER_NOPROMPT);
if (SQL_SUCCEEDED(rc)) 
{
    printf("%s: Successfully connected to database. Data source name: \n  %s\n", 
       DAM, szConnStrOut);

    /* Prepare SQL query */
    printf("%s: SQL query:\n  %s\n", DAM, query);

    rc = SQLAllocStmt(hDbc,&hStmt);
    rc = SQLPrepare(hStmt, query, SQL_NTS);
   
    /* Bind result set columns to the local buffers */ 
    rc = SQLBindCol(hStmt, 1, SQL_C_CHAR, chval1, 128, &ret1);
    rc = SQLBindCol(hStmt, 2, SQL_C_CHAR, chval2, 128, &ret2);
   
    /* Run the query and create a record set */
    rc = SQLExecute(hStmt); 
    if (SQL_SUCCEEDED(rc)) 
    {
        printf("%s: Retrieve schema info for the given result set:\n", DAM);
        SQLNumResultCols(hStmt, &fieldCount);
        if (fieldCount > 0)
        {
            for (column = 1; column <= fieldCount; column++)
            {
                SQLDescribeCol(hStmt, column,
                    colName, sizeof(colName), 0, 0, 0, 0, 0);
                printf(" | %s", colName);    
            }
            printf("\n");
        }
        else
        {
            printf("%s: Error: Number of fields in the result set is 0.\n", DAM);
        }

        printf("%s: Fetch the actual data:\n", DAM);
        /* Loop through the rows in the result set */
        rc = SQLFetch(hStmt);
        while (SQL_SUCCEEDED(rc)) 
        {
            printf(" | %s | %s\n", chval1, chval2);
            rc = SQLFetch(hStmt);
            rowCount++;
        };

        printf("%s: Total Row Count: %d\n", DAM, rowCount);
        rc = SQLFreeStmt(hStmt, SQL_DROP);
    }
}
else
{
    printf("%s: Couldn't connect to %s.\n", DAM, szDSN);
}

/* Disconnect and free up allocated handles */
SQLDisconnect(hDbc);
SQLFreeHandle(SQL_HANDLE_DBC, hDbc);
SQLFreeHandle(SQL_HANDLE_ENV, hEnv);

printf("%s: Cleanup. Done.\n", DAM);

MFC ODBC 示例

下面的代码示例显示 MFC ODBC 的连接信息,其通过包括 <Afxdb.h> 来使用 Aceodbc.dll 中实现的 {Microsoft Access Driver (*.mdb, *.accdb)} 驱动程序。

LPCTSTR lpszConnect = 
    _T("Driver={Microsoft Access Driver (*.mdb, *.accdb)};DSN='';DBQ=C:\\Northwind.accdb;PWD=1L0v3Acce55;");

下面的代码示例获取架构和数据。

BOOL result = TRUE;
CDatabase db;

TRY
{
    result = db.OpenEx(lpszConnect, 
        CDatabase::openReadOnly |
        CDatabase::noOdbcDialog);
    if (FALSE == result)
    {
        cout<<DAM<<": Unable to connect to data source "<<lpszConnect<<endl;
        return result;
    }

    cout<<DAM<<": Successfully connected to database. Data source name:\n  "
        <<db.GetDatabaseName()<<endl;

    // Prepare SQL query
    LPCTSTR query = "SELECT Customers.[Company], Customers.[First Name] FROM Customers ORDER BY Customers.[Company] ASC;";
    cout<<DAM<<": SQL query:\n  "<<query<<endl;
       
    // Run the query and create a record set
    CRecordset rs(&db); 
    result = rs.Open(CRecordset::dynaset, query, CRecordset::none);
    if (result == TRUE)
    {
        cout<<DAM<<": Retrieve schema info for the given result set: "<<endl;
        CODBCFieldInfo fInfo; 
        short sFieldCount = rs.GetODBCFieldCount();
        if (sFieldCount > 0)
        {
            for (short nIndex=0; nIndex < sFieldCount; nIndex++)
            {
                CODBCFieldInfo fInfo;
                rs.GetODBCFieldInfo(nIndex, fInfo);
                cout<<" | "<<fInfo.m_strName;
            }
            cout<<endl;
        }
        else
        {
            cout<<DAM<<": Error: Number of fields in the result set is 0."<<endl;
        }
    
        cout<<DAM<<": Fetch the actual data: "<<endl;
        CDBVariant var;
        CString value;
       
        // Loop through the rows in the result set
        int rowCount = 0;
        while (!rs.IsEOF())
        {
            for (short nIndex=0; nIndex < sFieldCount; nIndex++)
            {
                rs.GetFieldValue(nIndex, var);
                switch (var.m_dwType)
                {
                    case DBVT_STRING:
                        value.Format("%s", var.m_pstring->GetBuffer(var.m_pstring->GetLength()));
                        break;
                    case DBVT_ASTRING:
                        value.Format("%s", var.m_pstringA->GetBuffer(var.m_pstringA->GetLength()));
                        break;
                    case DBVT_WSTRING:
                        value.Format("%s", var.m_pstringW->GetBuffer(var.m_pstringW->GetLength()));
                        break;
                    default:
                        value = "";
                }
                cout<<" | "<<value;
            }
            cout<<endl;
            rowCount++;
            rs.MoveNext();
        }
        cout<<DAM<<": Total Row Count: "<<rowCount<<endl;
    }
}
CATCH_ALL(e)
{
    TCHAR  errMsg[255];
    e->GetErrorMessage(errMsg, 255);
    cout<<DAM<<": CException: "<<errMsg<<endl;
}
END_CATCH_ALL

db.Close();
cout<<DAM<<": Cleanup. Done."<<endl;

JDBC-ODBC 示例

JDBC 是一个数据访问层,它允许 Java 与基础数据源交互。JDBC-ODBC 桥是一个数据库驱动程序实现,它依赖 ODBC 驱动程序才能连接至 Access 数据库。驱动程序将 JDBC 方法调用转化为 ODBC 函数调用。

private static String strConnect =
"jdbc:odbc:DRIVER=Microsoft Access Driver (*.mdb, *.accdb);DBQ=C:\\Northwind.accdb;PWD=1L0v3Acce55;";

下面的 Java 代码示例获取架构和数据。

try {
   Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
   Connection con = DriverManager.getConnection(strConnect, "",""); 
   if (null == con) {
   System.out.println(DAM + "Unable to connect to data source " + strConnect);
   return;
   }
   
   System.out.println(DAM + ": Successfully connected to database. Data source name:\n  " 
+ con.getMetaData().getURL());
  
   // Prepare SQL query.
   String query = "SELECT Customers.[Company], Customers.[First Name] FROM Customers ORDER BY Customers.[Company] ASC;";
   System.out.println(DAM + ": SQL query:\n " + query);
   
   // Run the query and create a record set
   Statement stmt = con.createStatement();
   stmt.execute(query); 
   ResultSet rs = stmt.getResultSet(); 
   if (rs != null) {
   System.out.println(DAM + ": Retrieve schema info for the given result set: ");
   ResultSetMetaData rsmd = rs.getMetaData();
   for (int i=1; i <= rsmd.getColumnCount(); i++) {
   System.out.print(" | " + rsmd.getColumnName(i));
   }
   
   System.out.println("\n" + DAM + ": Fetch the actual data: ");
   int rowCount = 0;
   while (rs.next()) {
   for (int i=1; i <= rsmd.getColumnCount(); i++) {
       System.out.print(" | " + rs.getString(i));
       }
   System.out.println("");
   rowCount++;
       }
   System.out.println(DAM + ": Total Row Count: " + rowCount);
   }
   stmt.close(); 
   con.close(); 
} catch (Exception err) {
   System.out.println(DAM + ": Exception: " + err.getMessage());
} finally {
   System.out.println(DAM + ": Cleanup. Done.");
}

性能数据

性能结果针对 64 位 Access 2010 在运行 Windows Server 2008 SP2 的 64 位操作系统(带 AMD 64 Athlon X2 双核处理器 4600+ 2.40 GHz CPU 和 8 GB RAM)上生成。基准程序不输出至用户界面 (UI) 并且不使用 ACE 引擎中的新功能或高级功能。

图 3 显示提供性能数据的图表。

图 3. 性能测量(单位为秒)

绩效衡量

备注

所有基准程序均作为控制台应用程序运行(VBA DAO 代码除外,其从 VBA/VBE 环境运行)。这意味着 VBA 代码是唯一具有特权在 MSAccess.exe 的同一地址空间中执行的程序,这样可以显著减少磁盘 I/O 操作和页面错误,从而提高总体性能。

使用 32 位和 64 位 ACE 提供程序

Access 2007 产品的 ACE 提供程序(ACE DAO、ACE OLE DB 或 ACE ODBC)仅提供 32 位版本。而 Access 2010 产品的 ACE 提供程序则同时提供 32 位和 64 位版本。

一般情况下,目前有三种可能的配置。

仅 64 位的解决方案(64 位的 Access、64 位的 Windows)

若要实现 64 位的解决方案,您必须执行以下操作:

  1. 在 64 位的 Windows 上部署 64 位的 Access 2010

  2. 构建自定义的 64 位数据访问应用程序

仅 32 位的解决方案(32 位的 Access、32 位的 Windows)

如果您有 32 位的应用程序,希望通过 Access 2010 继续运行它而不进行更改,则必须安装 32 位版本的 Access 2010。

32 位 Access 2010 的工作方式与 32 位 Access 2007 完全一样,无需对 VBA 代码、COM 加载项或 ActiveX 控件进行更改即可继续运行。

WOW64 解决方案(32 位的 Access、64 位的 Windows)

WOW64 技术允许在 Windows 64 位的平台上执行 32 位的应用程序。可以将 32 位的 Access 2010 安装在 64 位的 Windows 上。在这种情况下,您的数据应用程序必须是 32 位才能与 ACE 提供程序通信。这是 64 位 Windows 操作系统上的默认安装,可提供与 32 位 Office 应用程序的兼容性。

尽管 32 位的应用程序能够以透明方式运行,但是不支持在同一进程中混合使用两种类型的代码。64 位的应用程序不能针对 32 位的系统库 (DLL) 进行链接,同样 32 位的应用程序也不能针对 64 位的系统库进行链接。

重要说明重要说明

如果您尝试针对 64 位的 Access 运行旧式的 32 位代码,则会发生运行时错误。例如,如果应用程序(32 位代码)和与 64 位 Microsoft Access 一同安装的 64 位 ACE 提供程序之间的版本不匹配,则可能会造成错误"'Microsoft.ACE.OLEDB.12.0' 提供程序未在本地计算机上注册"。若要更正此问题,可以将自定义代码升级至 64 位版本或卸载 64 位 Access 然后替换为 32 位 Access。

图 4. ACE 提供程序和应用程序之间的版本匹配

32 位与 64 位

并行安装

不支持 64 位和 32 位 Office 2010 版本的并行安装。这包括 Access。

其他注意事项

在部署 64 位 Access 之前,先确定其是否为您特定环境的正确部署选项。有几个方面可能会影响当前 32 位 Access 解决方案的兼容性。例如,如果您使用已删除其源代码的数据库(例如 .mde, .ade 和 .accde 文件),或者使用带有 Declare 语句的 VBA 代码、COM 加载项或 ActiveX 控件,则可能必须投入一些开发时间才能让此功能与 64 位的 Access 配合工作。此问题的变通解决方法是将 32 位 Access 安装在 32 位 Windows 上,或将 32 位 Access (WOW64) 安装在 64 位 Windows 上。有关此主题的详细信息,请参阅 64 位 Office 2010 版本

选择数据访问技术时的注意事项

如果您开发了使用现有 Access 数据库的解决方案,则可能希望继续使用应用程序的当前数据访问技术(只要它满足您的要求)。弃用的数据访问方法一节列出了可以继续提供与旧式 Access 文件格式的连接的驱动程序。请注意所有新的 ACE 提供程序都完全向后兼容。

如果您希望应用程序具有较长的生命周期,请在需求分析和设计期间考虑几点因素。以下几点因素会影响您对于最适合的数据访问驱动程序的使用决策:

  • **语言/平台。**您是否限制为使用本机语言(C、C++、VBA)和托管的(C#、Visual Basic.NET)语言实现您的解决方案?托管语言及其基本的数据访问框架 (ADO.NET) 提供更简单的实现、更好的平台互操作性和可扩展的数据访问。例如,如果您需要与 .NET Framework、XML 以及特定解决方案进行重大的集成,其中该解决方案需要断开连接的业务逻辑(关系数据的内存中呈现)或经过良好定义的、已构成的接口(其行为、性能和语义均可预测),则 ADO.NET 可能是您最佳的选择。另一方面,如果您需要尽可能最佳的性能或受限于使用特定语言,则可能还需要考虑其他方法(Direct DAO、Direct OLE DB 等等)。如果希望采用 C 语言开发您的解决方案,则只能选择 Direct ODBC 方法。同样,对于 Java 开发,您可能要使用 JDBC-ODBC 驱动程序。

  • **功能。**如果您的解决方案完全依赖于 Access 数据库,并且始终这样,则应该很自然地选择 ACE DAO 驱动程序,因为它可提供最全面的功能。但长远来看,本机数据访问技术通常会缩短开发时间、简化代码并提供更好的性能。如果您必须具备高级的记录集操作功能并连接至支持的外部源,请考虑使用 ADO.NET(或 ADO)或 OLE DB。仅 ACE DAO 驱动程序提供对旧式功能(例如链接的表和保存的查询)和 Access 2007 引入的复杂新数据类型的完全支持。ACE OLE DB 驱动程序对复杂数据提供有限的支持。例如,若要对复杂数据集启用更好的支持(在记录集内检索记录集),请设置连接参数"JET OLE DB:支持复杂数据"。否则,默认情况下,只能获得分隔的复杂字段列表。ADO.NET、ADO 和 ACE ODBC 始终能获得分隔的复杂字段列表。

  • **安全性。**在多用户和 Web 环境中编写安全数据库代码比创建高强度的加密密码更为重要。访问数据库的应用程序存在多个潜在的故障点,攻击者可以利用这些故障点检索、操作或破坏敏感数据。因此了解安全性的方方面面(从应用程序设计阶段的威胁模型分析,到其最终的部署和后续维护)很重要。总体来说,.NET Framework 为提高应用程序的安全性提供易于使用和良好集成的环境。

  • **性能。**尽管 ADO.NET 和 ADO 速度很快,但是它们确实会在与 ACE 引擎配合使用时,在您的应用程序和 ACE OLE DB 提供程序之间插入一个附加的抽象层。总体来说,Direct DAO、OLE DB 和 ODBC 方法最快,特别是针对大型的数据库。如果要考虑性能,并且预计数据库会随时间显著增大,则可以使用 OLE DB 或 DAO 接口以 C++ 语言编写应用程序。

  • **维护。**若要开发简单的解决方案、ADO.NET 或 Direct DAO(可能也有 ADO),请查看注释。选择 OLE DB 数据访问技术会影响应用程序长期维护的成本。OLE DB 比 DAO 或 ADO.NET 更为昂贵,这是因为维护和改进复杂的 COM 代码更难。可以使用 ATL OLE DB 方法(已提供示例源)来替代 Direct OLE DB 方法,前者很适合提取基本的 COM 复杂性。

备注

Office Access 2007 ACE 提供程序只能使用 32 位的客户端代码。Access 2010 ACE 提供程序支持 32 位代码或 64 位代码。若要实现 64 位的解决方案,您和您的客户都必须部署 64 位的 Access 2010 产品。

弃用的数据访问方法

表 2 列出了自 Access 2007 后已弃用的数据访问方法。这些方法不支持用于以 .accdb 文件格式保存的 Access 数据库,并且只能用于维护旧式应用程序。

表 2. 弃用的数据访问方法

提供程序名称

数据访问方法

连接和其他信息

支持的语言

JET4.0 OLE DB 提供程序

OLE DB

Microsoft.JET.OLEDB.4.0

<Atldbcli.h>

C++

MFC DAO

MFC DAO

<Afxdao.h>;

与 MDAC 一同安装;具有前缀 CDao. 的 MFC 类。

MFC DAO 类向用户提供一种使用旧式 Microsoft JET 数据库引擎的方式。但自 Access 2007 之后,他们不再受支持。DAO 3.6 是此技术的最后版本。其不能用于 64 位的 Windows。Visual C++ .NET 向导不会生成自动创建和打开记录集的代码。

C++

Access ODBC 驱动程序 4.0

MFC ODBC

Driver={Microsoft Access Driver (*.mdb)};DBQ= mdb 文件的路径

<Afxdb.h>;

Odbcjt32.dll;

C++

结论

本技术文章阐述了 Microsoft Access 的概要体系结构、其 ACE 引擎和数据提供程序。本文探讨了对 Access 编程时可用的各种数据访问技术,而不管您开发的是本机代码还是托管代码,32 位还是 64 位。使用诸如 DAO、OLE DB、ADO.NET、ADO、ODBC 或 JDBC 之类的数据访问技术,您可以继续创建自定义的 Access 解决方案,即使对最复杂和最苛刻的应用场景也可以这样做。总体来说,作为 ACE 引擎的默认提供程序,ACE DAO 驱动程序提供针对 Access 数据库的最全面的本机接口,而 ADO.NET 是针对 .NET 语言的另一个不错的选择。它们不仅能与 ACE 引擎很好地集成,还能对旧式文件格式提供快速、稳定和向后兼容的环境。