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

使用 WTL 进行数据访问对象 (DAO) 操作

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (16投票s)

2008年4月13日

CPOL

6分钟阅读

viewsIcon

80248

DAO 已过时,但它仍然非常灵活且对开发者友好。

引言

微软告诉我们 DAO 模型已过时。DAO360.dll 是其终点。不应再用 DAO 开发新项目。

然而,在某些环境中,DAO 仍然是一个出色的工具。如果您的客户在“非管理员”环境中工作,那么创建本地数据库的选项就不多了。ADO 可能是一个可行的选择。但这也要取决于您客户的需求。使用 ADO 创建对 MS Access 可见的 QueryDef 是很有问题的。如果您的客户将使用 MS Access 进行数据分析,那么 DAO 仍然是您的好帮手。

最近,我们需要快速从 Microsoft Access 数据库中提取一些数据并将其放入工程师使用的数据库中。该项目是用 WTL 编写的,我盲目地开始使用 ADO,并在一两天内完成了项目。几天后,工程师们带着一些“简单”的要求找到了我。第一个要求是,当他们在 Access 中打开表时,他们不喜欢列的宽度。第二个要求是在不更改底层数据的情况下,对数据进行特殊格式化。

我勤奋地跑去,认为这只需要半个小时就能完成。我大错特错了!您是否曾经尝试过使用 ADO 或 ADOX 设置表或数据表视图中列的宽度?我查阅了文档,在 ADOX 或 ADO 中找不到任何关于可见列宽的信息。我天真地试图获取 ADOX 中列的“Format”属性的指针。不起作用。几个小时后,我开始想,也许 DAO 并没有像微软所说的那样“死亡”?我知道如何在 DAO 中完成所有这些工作,但 ADO 似乎并没有提供一个很好的途径来满足我客户的愿望。

然后,我偶然发现了 Yuriy Tkachenko 的文章《如何在没有 MFC 的情况下在 Visual C++ 中使用 DAO》。我决定管他什么 ADO 和微软给我们的规则。导入 DAO DLL 并工作了几个小时后,我得到了一个满足客户需求的工作产品。令人惊讶的是,程序运行得更快了。比我的 ADO 产品快很多。

背景

我的客户希望将他们的数据保存在 Access 数据库(MDB 文件格式)中。为什么?可移植性。他们的桌面是锁定的,安装 SQL Server Personal Edition 或 Oracle Express Edition 是不切实际的。一个不需要管理员权限来安装服务器软件的基于文件的数据库系统是更受欢迎的。我一开始使用的是 ADO。但是,我无法提供客户想要的列格式级别。

本文是 Yuri 文章的延伸。我将更详细地介绍 DAO 的使用。没有演示代码,只有代码片段,演示了如何使用 DAO 创建数据库文件、创建表以及索引表。我应用了 DAO 可访问的列属性,而 ADO 或 ADOX 无法访问。在代码片段中,我还使用了 ATL 和 WTL 程序员常见的代码和模式。

DAO - 它仍然有用

基本上,您使用 DAO 所做的与使用 ADO 所做的类似。不同之处在于,您可以访问所有漂亮的 Access 小部件。

您需要包含此导入语句。我通常将此导入放在我的 stdafx.h 文件中。您的使用情况可能有所不同。

#import <C:\Program Files\Common Files\Microsoft Shared\DAO\dao360.dll> 
                                              rename("EOF", "EndOfFile")

在 WTL 中创建 DBEngine 对象

DAO36.dll 导入项目后,您需要创建一个 DBEngine 实例。

您需要在类头文件中为 DAO COM 指针创建一些变量

private:
    CComPtr<DAO::_dbengine> m_DBE;
    DAO::WorkspacePtr m_Workspace;
    DAO::DatabasePtr m_CurrDB;

要创建实例,可以将这些语句放在类构造函数中

try
{
    m_DBE.CoCreateInstance(__uuidof(DAO::DBEngine));
}
catch(_com_error &e)
{
    ::MessageBox(m_hWnd, (LPCTSTR)e.Description(), 
                (LPCTSTR)e.Source(), MB_ICONSTOP);
    m_szSQLState = e.Error();
    _tcscpy_s(m_szSQLErrMsg, iERR_MESG_CHARS, (LPCTSTR)e.Description());
}

使用 DAO 创建数据库文件

创建 DBEngine 组件实例后,您就可以创建数据库文件(MDB)了。

此代码片段还在代码的 catch 块中引入了对 DAO 错误集合的访问。

try
{
    m_CurrDB = m_DBE->CreateDatabase(_bstr_t(szMDFFilePath), 
               _bstr_t(DAO::dbLangGeneral), _variant_t(DAO::dbVersion40));
}
catch(_com_error &e)
{
    DAO::ErrorsPtr pErrs = m_DBE->Errors;
    DAO::ErrorPtr pErr;
    long count = pErrs->Count;
    if (count > 0)
    {
        pErr = pErrs->Item[0];
        m_strMsg.Format(_T("Error: %d, Description: %s"), 
                        pErr->Number, (LPCTSTR) pErr->Description);
        MessageBox((LPCTSTR)m_strMsg, (LPCTSTR)e.Source(), MB_ICONSTOP);
    }
    else
        MessageBox((LPCTSTR)e.Description(), (LPCTSTR)e.Source(), MB_ICONSTOP);
}

使用 DAO 创建新的 TableDef

上一节演示了如何使用 DAO 创建数据库文件。现在,我们将添加一个表定义。我想指出的是,您最好直接设计一个表定义并将其附加到 TableDefs 集合中,然后再尝试修改字段属性、添加索引或尝试任何其他 Access 小部件的魔法。修改直接可访问的属性(例如,在 tabledef 保存之前修改 AllowZeroLength)似乎是可以接受的。通过集合访问的属性(例如,“Format”)似乎在 tabledef 保存后才能访问。

以下是一些简单的代码,用于将 tabledef 添加到我们之前创建的数据库中。

BOOL CMainDlg::InitMDBFile()
{
    // Declare variables
    DAO::_FieldPtr fldNew;
    DAO::_FieldPtr fldIdx;
    DAO::_TableDefPtr tdfDestTable;
    DAO::_IndexPtr idxPtr;
    DAO::PropertyPtr pPrp;
    DAO::PropertiesPtr pPrps;
    try
    {
        // Create tabledef tbEngine
        tdfDestTable = m_CurrDB->CreateTableDef(_T("tbEngine"));
        // Add a text field
        fldNew = tdfDestTable->CreateField(_T("SerialNo"), DAO::dbText, 12);
        tdfDestTable->Fields->Append(fldNew);
        // Add a text field
        fldNew = tdfDestTable->CreateField(_T("PartNo"), DAO::dbText, 12);
        // Set zero length allowed property
        fldNew->put_AllowZeroLength(VARIANT_TRUE);
        tdfDestTable->Fields->Append( fldNew);
        // Add an integer
        fldNew = tdfDestTable->CreateField(_T("OPT_FLAGS"), DAO::dbInteger, 0);
        tdfDestTable->Fields->Append( fldNew);
        // Add an integer
        fldNew = tdfDestTable->CreateField(_T("STATUS"), DAO::dbInteger, 0);
        tdfDestTable->Fields->Append( fldNew);
        // Add an integer
        fldNew = tdfDestTable->CreateField(_T("LOCATION"), DAO::dbInteger, 0);
        tdfDestTable->Fields->Append( fldNew);
        // Add a text field
        fldNew = tdfDestTable->CreateField(_T("RFID"), DAO::dbText, 10);
        // Set zero length allowed property
        fldNew->put_AllowZeroLength(VARIANT_TRUE);
        tdfDestTable->Fields->Append( fldNew);
        // Append the tabledef before we start modifying DAO widgets
        m_CurrDB->TableDefs->Append(tdfDestTable);

        //Now, we can add a primary key:

        idxPtr = tdfDestTable->CreateIndex(_T("PrimaryKey"));
        idxPtr->Primary = VARIANT_TRUE;
        fldIdx = idxPtr->CreateField(_T("SerialNo"), DAO::dbText, 12);
        // This is a bit tricky. You have to cast the _variant_t Fields
        // to a collection pointer called IndexFieldsPtr. The index
        // fields collection is passed back to the calling code as a variant.
        DAO::IndexFieldsPtr pFlds = idxPtr->Fields;
        pFlds->Append(fldIdx);
        tdfDestTable->Indexes->Append(idxPtr);
        m_CurrDB->Close();
    }

catch 块与上一节相同。到目前为止,一切顺利。

请特别注意一个非常重要的问题。您必须第二次创建索引字段,在您将要附加到刚刚创建的 tabledef 的索引中。注意 1:您不能使用您创建的用于附加到 tabledef 的字段对象来附加到索引。我知道。我试过了。它不起作用。注意 2:DAO::dbNumericDAO::dbDecimal 不起作用。抱歉。别怪我。

使用 DAO 为列添加属性

此代码演示了如何为表添加列。假设您已经将表定义添加到了 TableDefs 集合中。然后,您需要获取要修改的列的指针,然后创建属性。没错。即使我们知道 Access 有一个 Format 属性,我们仍然需要为我们的列创建它。

    // We have to create the format property, since it doesn't yet exist
    // See KB 190522 in MSDN
    // http://support.microsoft.com/kb/190522
    fldNew = tdfDestTable->Fields->GetItem(_T("RPM"));
    pPrp = fldNew->CreateProperty(_T("Format"), DAO::dbText);
    pPrp->put_Value(_variant_t(_T("0.0")));
    // Append the property format to the properties collection
    fldNew->Properties->Append(pPrp);

    fldNew = tdfDestTable->Fields->GetItem(_T("CURDATETIME"));
    pPrp = fldNew->CreateProperty(_T("ColumnWidth"), DAO::dbInteger);
    pPrp->Value = _variant_t((long)(1.5 * 1440));
    // Append the property format to the properties collection
    fldNew->Properties->Append(pPrp);

创建表大致就是这些。接下来,我们将看看如何执行简单的查询。

使用 DAO 执行 SQL 操作查询

执行不返回数据的 SQL 语句很简单。在前面几篇 DAO 文章的基础上,以下是如何执行不返回任何数据的 SQL 语句(即操作查询)。

try
{
    m_CurrDB->Execute(_bstr_t(szStmt));
    vRecsAffected = m_CurrDB->RecordsAffected;
}

catch(_com_error &e)
{
    DAO::ErrorsPtr pErrs = m_DBE->GetErrors();
    long nCount = pErrs->Count;
    if (nCount > 0)
    {
        DAO::ErrorPtr pErr = pErrs->GetItem(0);
        m_szSQLState = pErr->Number;
        _tcscpy_s(m_szSQLErrMsg, iERR_MESG_CHARS, 
                 (LPCTSTR)pErr->Description);
    }
    else
        ::MessageBox(parent_hWnd, (LPCTSTR)e.Description(), 
                    (LPCTSTR)e.Source(), MB_ICONSTOP);
}

使用 DAO 执行返回数据的 SQL 查询

这是一个使用 DAO 执行返回数据查询的快速示例。一个快速的说明。此示例打开一个记录集,您需要在代码中的某个地方关闭它。您不想留下一个悬空的记录集指针!

DAO::RecordsetPtr pRec = NULL;
try
{
    // This statement will open a recordset for read-write.
    // Be sure to check the Updateable property before attempting
    // to update the returned recordset.
    pRec = m_CurrDB->OpenRecordset(_bstr_t(szStmt), DAO::dbOpenDynaset);
    // The following will open the recordset as read only
    // pRec = m_CurrDB->OpenRecordset(_bstr_t(szStmt), DAO::dbOpenDynaset, DAO::dbReadOnly);
    vRecsAffected = m_CurrDB->RecordsAffected;
}

catch(_com_error &e)
{
    DAO::ErrorsPtr pErrs = m_DBE->GetErrors();
    long nCount = pErrs->Count;
    if (nCount > 0)
    {
        DAO::ErrorPtr pErr = pErrs->GetItem(0);
        m_szSQLState = pErr->Number;
        _tcscpy_s(m_szSQLErrMsg, iERR_MESG_CHARS, 
                 (LPCTSTR)pErr->Description);
    }
    else
        ::MessageBox(parent_hWnd, (LPCTSTR)e.Description(), 
                    (LPCTSTR)e.Source(), MB_ICONSTOP);
}

使用 DAO 创建 QueryDef

有两种方法可以创建 QueryDef。您必须小心,因为第一种方法会自动将 QueryDef 附加到 QueryDefs 集合中。之后,如果您尝试执行追加操作,您将收到一个无效操作的异常。

第一种方法:声明一个指向新 querydef 的指针。

// Smart pointer defined when DAO DLL is imported
DAO::_QueryDefPtr        qdfTakeOffView;

接下来,您需要创建 querydef

// The current database pointer exposes the CreateQueryDef method
qdfMyNewView = m_CurrDB->CreateQueryDef(_T("qdfMyNewView"),
                                        _T("SELECT * FROM EXISTING_TABLE;"));

这就是创建新 QueryDef 的全部内容。但是,有一件事您需要知道:以这种方式调用 CreateQueryDef 会自动将 querydef 附加到 QueryDefs 集合中。所以,不要尝试这样做。

// Don't do this if you created a named QueryDef!
m_CurrDB->QueryDefs->Append(qdfMyNewView);

如果您尝试将现有查询附加到集合中,您将收到“无效操作”异常。

第二种方法:您可以创建一个未命名的 querydef,然后修改其属性。

// Create an empty QueryDef object
qdfMyNewView = m_CurrDB->CreateQueryDef(vtMissing, vtMissing);
// Assign the name property
qdfMyNewView->Name = _T("qdfMyNewView");
// Assign the sql string
qdfMyNewView->SQL = _T("SELECT * FROM EXISTING_TABLE;");
// It is OK to append this query to the QueryDefs collection
m_CurrDB->QueryDefs->Append(qdfMyNewView);

如果您创建一个空的 QueryDef,您必须将其附加到 QueryDefs 集合中。

QueryDefs 的优势

创建 QueryDef 的好处是什么?嗯,DAO 允许您直接在查询中访问 VBA 表达式。这可能存在安全风险,因此请小心。

// Define the unformatted sql string
LPCTSTR szQueryTmpl = 
    _T("SELECT DATA.SerialNo, DATA2.Rfid, DATA.CurrentDT,")
    _T(" DateDiff(\"s\",\"%s\",[CurrentDT]) AS RelativeSampleTime,")
    _T(" DateDiff(\"s\",\"%s\",[CurrentDT]) AS NormalizedSampleTime,
    _T(" FROM DATA INNER JOIN DATA2 ON DATA.SerialNo = DATA2.SerialNo")
    _T(" WHERE (((DateDiff(\"m\",\"%s\",[CurrentDT])) Between 60 And -60))")
    _T(" ORDER BY Location, CurrentDT");

// Create the buffer to hold the formatted sql string
LPTSTR szQryBuf = new TCHAR[2048];
// Just some time parsing stuff
// You have to include atlcomtime.h to get this class
COleDateTime curTime;
if (curTime.ParseDateTime(szDatetime))
{
 COleDateTimeSpan sixtySecs(0, 0, 0, 60);
 curTime -= sixtySecs;
 // Format the query string with our time values
 if (-1 != _stprintf_s(szQryBuf, 2048,
                       szQueryTmpl,
                       szDatetime,
                       (LPCTSTR) curTime.Format(_T("%Y-%m-%d %H:%M:%S")),
                       szDatetime))
 {
     // Obtain an existing query using QueryDefs and the exposed Item object
     qdfMyNewView = m_CurrDB->QueryDefs->Item[_bstr_t(_T("qdfMyNewView"))];
     // Assign the new string to the QueryDef
     // This automatically persists the query string, it's
     // saved in the MDB file.
     qdfMyNewView->SQL = _bstr_t(szQryBuf);
 }
...

正如您在此示例中看到的,VBA 函数 DateDiff 直接在查询中调用。很酷,嗯?

摘要

将 DAO 用作 COM 对象既简单又有效。尽管 DAO 已过时,但在某些情况下仍然非常有用。

© . All rights reserved.