In this lab, you will provide the implementation for UploadStockData, the component you created for Lab 9. You will add code to implement the ConnectToDatabase(), Disconnect(), and UploadRecord() methods of the IUploadStockData interface. These methods use the ADO library to connect to the Stocks database. To refresh your memory about ADO, refer back to Lesson 3 of Chapter 7.
In this Lab, you will also write code to implement the Upload command on the Data menu of the STUpload application. The user will select this command to upload the data in the currently loaded document to the Stocks database.
This Lab assumes that you have installed SQL Server and have set up the Stocks database as directed in the "Getting Started" section of the introduction.
The UploadStockData component creates an ADO Connection object to connect to the Stocks database. You provide the Connection object with information about which provider, database, and security settings to use by specifying the details in a connection string. For example, the connection string used by the ADO data control in my version of the STUpload application is as follows:
Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=Stocks;Data Source=(local) |
When distributing an application, it is not advisable to hard-code connection information into the application source code because this might mean that changes to the database and network configuration will require you to recompile and redistribute your application. To avoid problems of this kind, you can specify the connection information in a special disk file known as a data link file. A data link file has a .udl extension. When OLE DB is installed on your computer, you can configure the data link file using a simple user interface. After the data link file has been configured, you simply set the connection string to refer to the file as follows:
File Name=C:\DataLinks\STLink.udl |
The configuration in the data link file can easily be redistributed or reconfigured on the client computer if the database or network configuration changes.
Figure 10.4 Editing the data link file
In this section of the lab, you will return to the STLoadData project and implement the ConnectToDatabase(), Disconnect(), and UploadRecord() methods of the IUploadStockData interface exposed by the UploadStockData component. In the course of completing these exercises, you will learn how a client can use the COM interfaces supplied by ADO to work with an OLE DB data provider.
The first step is to import the ADO type library to make the GUID definitions available to the project, and to generate smart pointers for the ADO interfaces.
#import "C:\Program Files\Common Files\System\ado\msado15.dll" \ no_namespace rename("EOF", "adoEOF") |
The path to the msado15.dll file might be different on your computer. Check that the version in your code points to the correct location. Note, also, that in the example just given, a continuation character \ is used to break the statement over two lines.
The compiler processes the #import statement in the StdAfx.h file and creates the msado15.tlh and msado15.tli files in your Debug folder. These files contain the GUID definitions and smart pointer declarations for the ADO interfaces, which are now available for use in the STLoadData project.
The UploadStockData component will connect to the database through a single ADO Connection object, which will be used by successive calls to the UploadRecord() method. This Connection object will be implemented as a member variable of the component class and will be opened and closed by the ConnectToDatabase() and Disconnect() methods.
_ConnectionPtr m_pConnection; |
m_pConnection = NULL; |
STDMETHODIMP CUploadStockData::ConnectToDatabase() { // Test to see if we're connected already if(m_pConnection) return S_OK; HRESULT hr = m_pConnection.CreateInstance(__uuidof(Connection)); if(FAILED(hr)) return hr; hr = m_pConnection->Open(L"File Name=C:\\STLink.UDL", L"", L"", -1); if(FAILED(hr)) return hr; return S_OK; } |
Remember that the m_pConnection variable is a _com_ptr_t smart pointer type that points to the interface exposed by the ADO Connection object.
STDMETHODIMP CUploadStockData::Disconnect() { if(m_pConnection) { m_pConnection->Close(); m_pConnection = NULL; } return S_OK; } |
You can now add code to implement the IUploadStockData::UploadRecord() method.
(This code can be found in CH10_03.cpp, installed from the companion CD.)
STDMETHODIMP CUploadStockData::UploadRecord(BSTR fund, DATE date, double price, BSTR uplBy, DATE uplDate) { // Test for live connection to data source if(m_pConnection == NULL) return E_FAIL; // Create recordset _RecordsetPtr pRecordset; HRESULT hr = pRecordset.CreateInstance(__uuidof(Recordset)); if(FAILED(hr)) return hr; try { // Open recordset _variant_t vConnection = m_pConnection.GetInterfacePtr(); hr = pRecordset->Open(L"pricehistory", vConnection, adOpenForwardOnly, adLockOptimistic, adCmdTableDirect); if(FAILED(hr)) return hr; // Add new record, set fields to new values and update hr = pRecordset->AddNew(); if(FAILED(hr)) throw_com_error(hr); pRecordset->Fields->GetItem(L"ph_fund")->Value = fund; pRecordset->Fields->GetItem(L"ph_date")->Value = date; pRecordset->Fields->GetItem(L"ph_price")->Value = price; pRecordset->Fields->GetItem(L"ph_uploadedby")->Value = uplBy; pRecordset->Fields->GetItem(L"ph_uploaddate")-> Value = uplDate; hr = pRecordset->Update(); if(FAILED(hr)) throw_com_error(hr); } catch(_com_error e) { // very unsophisticated error handling try { pRecordset->Close(); } catch(...) // Close() may throw another exception { } return E_FAIL; } pRecordset->Close(); return S_OK; } |
Note that the existing Connection object m_pConnection is passed as an argument to the Recordset Open() method. For details of the other arguments to Open(), look up "ADO" in the Visual C++ Help file.
Note, too, that the AddNew() and Update() methods of the ADO Recordset object are used together to add a new record. A new record is added to the recordset with AddNew(), the field values (accessed through the Recordset object's Fields collection) are set to the new values, and the Update() method is called to update the database.
In the next section of the lab, you will implement the Upload command on the Data menu of the STUpload application. You will also add a user-interface update command handler to make the menu and toolbar command available when a document is currently loaded into the application.
(This code can be found in CH10_04.cpp, installed from the companion CD.)
void CSTUploadDoc::OnUpdateDataUpload(CCmdUI* pCmdUI) { // Enable the UploadData command only if there is // data on file and a fund currently selected for viewing BOOL bEnable = GetCurrentFund().IsEmpty() ? FALSE : TRUE; pCmdUI->Enable(bEnable); } |
#include <comdef.h> // for Compiler COM support #include <lmcons.h> // for the UNLEN constant #include ".\STLoadData\STLoadData.h" #include ".\STLoadData\STLoadData_i.c" |
void CSTUploadDoc::OnDataUpload() { if(AfxMessageBox("Upload current file to database?", MB_OKCANCEL) == IDCANCEL) return; ::CoInitialize(NULL); _COM_SMARTPTR_TYPEDEF(IUploadStockData, __uuidof(IUploadStockData)); IUploadStockDataPtr pServer; HRESULT hr = pServer.CreateInstance(CLSID_UploadStockData); if(SUCCEEDED(hr)) hr = pServer->ConnectToDatabase(); if(SUCCEEDED(hr)) { try { POSITION pos = m_DocList.GetHeadPosition(); while(pos) { CStockData sd = m_DocList.GetNext(pos); BSTR fund = sd.GetFund().AllocSysString(); DATE date = sd.GetDate().m_dt; double price = sd.GetPrice(); DWORD dwLen = UNLEN + 1; TCHAR cUser[UNLEN + 1]; ::GetUserName(cUser, &dwLen); CString strUser(cUser); BSTR uplBy = (strUser.Left(10)).AllocSysString(); COleDateTime dtToday = COleDateTime::GetCurrentTime(); DATE uplDate = dtToday.m_dt; HRESULT hr = pServer->UploadRecord(fund, date, price, uplBy, uplDate); ::SysFreeString(fund); ::SysFreeString(uplBy); if(FAILED(hr)) { CString strPrompt = "Upload of:\n"; strPrompt += sd.GetAsString(); strPrompt += "\nfailed"; if(AfxMessageBox(strPrompt, MB_OKCANCEL) == IDCANCEL) break; } } if(!pos) // We got to the end of the loop AfxMessageBox("Upload completed successfully"); } catch(_com_error e) { ::MessageBox(NULL, e.ErrorMessage(), NULL, MB_OK); } pServer->Disconnect(); } ::CoUninitialize(); } |
Look through the code to make sure you understand how the code creates and uses an instance of the UploadStockData component to add to the database records contained in the document's m_DocList member. Note that the Windows API function GetUserName() is used to get the name of the currently logged-on user. The first 10 characters of this name are placed in the ph_uploadedby field in the pricehistory table of the Stocks database.
You can now build the STUpload application.
select * from pricehistory |
The results of the query will appear in the lower half of the Query Analyzer window as shown in Figure 10.5. Check to see that the rows that you added appear at the bottom of the table with your user name and the current date in the ph_uploadedby and ph_uploaddate columns.
Figure 10.5 The Query Analyzer displaying the pricehistory table