使用 Access 2010 進行資料程式設計

**摘要:**學習如何使用 Microsoft Office Access 2007 或 Microsoft Access 2010 開發原生 (C、C++、Java、VBA) 或 Managed (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 Foundation Classes (MFC) Data Access Object (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 環境中工作。

  • 資料匯入、匯出以及連結:從不同的來源使用資料。

透過資料存取的觀點看來,您可以像 ACE 引擎對使用者以圖形展示一般檢視 Access。

在何處取得 ACE 引擎?

若要執行本技術文章中所提供的程式碼範例,您的電腦上必須具有 ACE 引擎,也就是說您必須安裝下列 Access 2010 (或 Office Access 2007) 產品:

注意

已發行 Microsoft Access Database Engine 2007 及 2010 驅動程式,供方案程式開發人員建立工具以讀取和寫入 Office 檔案,例如 .accdb, .xlsx 及 .xlsb。並且允許方案建立者擁有不需要在伺服器上安裝 Office,即可讀寫 Office 檔案格式的伺服器 (例如 SQL)。但此驅動程式並未授權用為儲存獨立資料。

關於 Microsoft JET 引擎的去向

在 Access 2007 之前,Access 使用 Microsoft Joint Engine Technology (JET) 引擎。過去普遍認為 JET 是 Access 的一部分,但 JET 引擎其實是獨立的產品。自從 Microsoft Windows 2000 發行之後,JET 已納入為 Windows 作業系統的一部分,並會隨著 Microsoft Data Access Components (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 程式設計:

  • Data Access Objects (DAO)

  • 物件連結與嵌入,資料庫 (OLE DB)

  • ADO.NET

  • ActiveX Data Objects (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

Access 開發人員最初將 DAO 排除在資料存取方法之外。

Direct DAO 範例

此存取方法因為最能顯現 ACE 引擎的功能,所以能為 Access 2007 納入的新功提供最佳的支援。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> 的方式,使用 Active Template Library (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 Data Objects (ADO) 提供 COM 的應用程式層級介面給 OLE DB 資料提供者。雖然相較於直接編碼 OLE DB,ADO 的效能較慢,卻反而容易學習及使用。ADO 讓 C++ 程式設計師能夠存取基礎 OLE DB 介面。但大多數的開發人員並不熱衷於使用此類低階的控制方法,像是透過 OLE DB 對資料存取處理序管理記憶體資源及手動彙總元件。

與 DAO 的不同之處在於,DAO 使用單一資料庫引擎功能,ADO 則是使用一般程式設計模型進行全域存取資料。

ADO 範例

ADO 使用 Msado15.dll 程式庫 (包含於 MDAC 2.8 或更高版本中)。若要編譯此程式碼,請使用 #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.");
}

效能資料

系統會在執行 Windows Server 2008 SP2 的 64 位元作業系統 (且具備 AMD 64 Athlon X2 雙核心處理器 4600+ 2.40 GHz CPU 及 8 GB RAM) 上產生 64 位元 Access 2010 的效能結果。此效能評定程式不會輸出到使用者介面,也不會使用 ACE 引擎中的新功能或進階功能。

圖 3 是呈現效能資料的圖表。

圖 3. 效能測量 (以秒為單位)

效能測量

注意

所有的效能評定程式會以主控台應用程式的方式執行,只有 VBA DAO 程式碼除外,它會從 VBA/VBE 環境執行。這表示在相同的 MSAccess.exe 位址空間中,VBA 程式碼是唯一具有執行權限的程式,因此能大幅減少磁碟 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 位元 Access 2010 到 64 位元 Windows

  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 位元程式碼,會得到執行階段錯誤。例如,['Microsoft.ACE.OLEDB.12.0' 提供者並未登錄於本機電腦上。] 的錯誤,就可能是由於您的應用程式 (32 位元程式碼) 與其中一個由 64 位元 Microsoft Access 所一併安裝的 64 位元 ACE 提供者,產生了版本不符的現象。若要修正此問題,可以將您的自訂程式碼升級為 64 位元版本,或是將 64 位元的 Access 解除安裝而以 32 位元的 Access 取代。

圖 4. ACE 提供者與應用程式之間的版本對應

32 位元與 64 位元的比較

並存安裝

不支援 64 位元與 Office 2010 32 位元版本的並存安裝。包括 Access。

其他考量

在部署 64 位元 Access 以前,請先決定該部署選項是否適合您的特定環境。有數種可能會影響您現有 32 位元 Access 解決方案之相容性的情況。例如,若您使用已移除其來源程式碼 (像是 .mde, .ade 及 .accde 檔案) 的資料庫,或是透過 Declare 陳述式、COM 增益集或 ActiveX 控制項使用了 VBA 程式碼,您就可能必須投入相當的開發時間,才能使該功能與 64 位元的 Access 一起順利運作。針對此問題的替代因應措施是將 32 位元的 Access 安裝到 32 位元的 Windows 上,或是將 32 位元的 Access (WOW64) 安裝到 64 位元的 Windows。如需本主題的詳細資訊,請參閱 64 位元版本的 Office 2010

選擇資料存取技術時的考量

若您已開發了可與現有 Access 資料庫並用的解決方案,您也可能會在考量需求之下繼續使用該應用程式的既有資料存取技術。<已遭取代的資料存取方法>一節列出了繼續提供與舊版 Access 檔案格式相連的驅動程式。需要注意的是,所有新的 ACE 提供者也能完全與舊版相容。

若您希望應用程式能擁有較長的生命週期,請在需求分析和設計時考慮這些因素。某些因素可能包含下列事項,而影響您決定使用何種最適合的資料存取驅動程式:

  • **語言/平台。**您是否受限於只能使用原生語言 (C、C++、VBA) 實作您的解決方案,而無法使用 Managed (C#、Visual Basic.NET) 語言進行?Managed 語言及其基礎資料存取架構 (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: Support Complex Data"。否則根據預設,您只會取得複雜欄位的分隔清單。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 方法的替代選項,ATL OLE DB 在擷取基礎 COM 複雜性上有著出色的表現。

注意

Office Access 2007 ACE 提供者僅能用於 32 位元的用戶端程式。Access 2010 ACE 提供者支援 32 位元或 64 位元的程式碼。若要實作 64 位元的解決方案,您與您的用戶端都必須部署 64 位元的 Access 2010 產品。如需詳細資訊,請參閱<關於 32-bit 與 64-bit ACE 提供者>。

已遭取代的資料存取方法

表 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 Driver 4.0

MFC ODBC

Driver={Microsoft Access Driver (*.mdb)};DBQ= mdb 檔案路徑

<Afxdb.h>;

Odbcjt32.dll;

C++

結論

本技術文章揭示了 Microsoft Access 的高階結構、其 ACE 引擎與資料提供者。並探討了各種不同的資料存取技術,供您處理對 Access 的程式設計,不論是開發原生或 Managed 程式碼,或是為 32 位元或 64 位元等各方面。透過使用像是 DAO、OLE DB、ADO.NET、ADO、ODBC 或 JDBC 等資料存取技術,您可以在面對最複雜或最苛求的情況下,也能夠繼續建立自訂 Access 解決方案。總而言之,ACE DAO 驅動程式身為 ACE 引擎的預設提供者,能提供了最完整的 Access 資料庫原生介面,而且 ADO.NET 對於 .NET 語言而言也是一項很好的替代選擇。他們不但與 ACE 引擎整合完善,更提供了快速、穩定以及支援舊版檔案格式的回溯相容環境。