非托管应用程序的基于 Web 的数据访问对象
非托管代码应用程序通过 Web 服务访问数据库中的数据,无需数据库提供商。
引言
我描述了托管代码应用程序(基于 Web 的数据访问对象)如何在没有数据库提供商的情况下通过 Web 服务访问数据库中的数据。今天,我将描述非托管代码应用程序如何通过 Web 服务访问数据库中的数据。非托管 C++ 可以直接引用 Web 服务。但这没什么用,因为非托管 C++ 不支持 .NET Framework 的 [System.Data.DataTable
] 类。在没有 [System.Data.DataTable
] 类的情况下处理记录集形式的数据非常困难。因此,我提出了一种方法,即非托管代码调用 COM DLL(动态链接库),该 DLL 引用 ASP.NET 的 Web 服务来实现 Web-DAO。当客户端请求 SQL 查询时,Web 服务器通过数据库提供商查询。然后将记录集形式的结果数据发送给客户端。客户端的托管代码 DLL 从 Web 服务器接收数据,并将其分配给 [System.Data.DataTable
] 类型的类变量。非托管代码应用程序从 COM DLL 的已分配变量中获取每个数据值。下图描述了非托管代码应用程序如何通过 COM DLL 访问数据库中的数据。
背景
首先,我们需要了解为什么要通过 Web 服务访问数据库中的数据以及如何访问。我们还应该了解如何创建托管代码的 COM DLL 以及在非托管代码中调用 COM DLL 的方法。以下文章对此进行了描述。
使用代码
步骤 1. 创建一个基于托管 C# 的 COM DLL
1. 为外部调用函数创建一个接口 [IWebDatabaseAccessObject]。
[Guid("db0eef1a-22e2-4cc2-9a9c-58d5de4614ae")]
public interface IWebDatabaseAccessObject
{
[DispId(1)]void SetConnectionString(string strConnection);
[DispId(2)]void SetConnection(string strIPAddress, int nPortNo, bool bSSL);
[DispId(3)]int ExecuteNonQuery(string strSQL);
[DispId(4)]bool Open(string strSQL);
[DispId(5)]bool IsEOF();
[DispId(6)]bool Close();
[DispId(7)]string GetFieldStringByColumnNo(int nColumnNo);
[DispId(8)]string GetFieldStringByColumnName(string strColumnName);
[DispId(9)]byte[] GetBlobFieldValueByColumnNo(int nColumnNo);
[DispId(10)]byte[] GetBlobFieldValueByColumnName(string strColumnName);
[DispId(11)]bool MoveNext();
[DispId(12)]int GetColumnSize();
[DispId(13)]int GetRecordCount();
[DispId(14)]string GetFieldNameByColumnNo(int nColumnNo);
[DispId(15)]int ExecuteNonQueryBlob(string strSQL, string strParameter, byte[] arbBlobValue);
}
2. 实现接口 [IWebDatabaseAccessObject] 的类 [WebDatabaseAccessObject]。
[Guid("6ff59b6a-fb7e-4df6-9399-541ce0cd8632")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("WebDAO")]
public class WebDatabaseAccessObject : IWebDatabaseAccessObject
3. 创建一个函数,为 ASP.NET 的 Web 服务分配 Web 服务器信息。
public string ConnectionString
{
get { return String.Format(@"{0}/{1}",
strConnection.TrimEnd('/'), strWebServiceFileName); }
}
// Set IP Address and Port No. of Web Server
public void SetConnectionString(string strConnection)
{
this.strConnection = strConnection;
}
public void SetConnection(string strIPAddress, int nPortNo, bool bSSL)
{
this.strConnection = String.Format("http{3}://{0}:{1}",
strIPAddress, nPortNo, (bSSL) ? "s" : null);
}
4. 我们编写代码以向 Web 服务器请求 SQL 查询,并将记录集形式的结果数据分配给 [System.Data.DataTable] 类型的变量。Open 函数连接到 Web 服务的 Web 服务器,接收结果数据并异常地将其分配给变量。
// Get Data in a Database by the Parameter-SQL
public bool Open(string strSQL)
{
DSWebDAO.WebServiceDAO wsdWebServiceDAO = new DSWebDAO.WebServiceDAO();
wsdWebServiceDAO.Url = ConnectionString;
try
{
dtRecodeSet = wsdWebServiceDAO.GetRecodeSet(strSQL);
nRecodeIndex = 0;
}
catch
{
}
return (dtRecodeSet != null);
}
5. 我们应该在 COM DLL 中创建类似于 ADO 或 DAO 结构的成员函数。MoveNext 函数移动到记录集形式数据的下一条记录行,GetFieldValue 函数可以获取选定记录行中的每个列数据。
// Move Next Row
public bool MoveNext()
{
bool bReturnFlag = false;
if (dtRecodeSet != null)
if (bReturnFlag = (nRecodeIndex < dtRecodeSet.Rows.Count))
nRecodeIndex++;
return bReturnFlag;
}
// Get Selected Column Value of Selected Row
object GetFieldValue(object objColumn)
{
object objReturn = null;
try
{
if (dtRecodeSet != null)
if (nRecodeIndex >= 0 && nRecodeIndex < dtRecodeSet.Rows.Count)
{
switch (Type.GetTypeCode(objColumn.GetType()))
{
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
objReturn = dtRecodeSet.Rows[nRecodeIndex][Convert.ToInt32(objColumn)];
break;
default:
objReturn = dtRecodeSet.Rows[nRecodeIndex][(DataColumn)objColumn];
break;
}
}
}
catch
{
}
return objReturn;
}
string GetFieldString(object objColumn)
{
return Convert.ToString(GetFieldValue(objColumn));
}
public string GetFieldStringByColumnName(string strColumnName)
{
return GetFieldString(dtRecodeSet.Columns[strColumnName]);
}
public string GetFieldStringByColumnNo(int strColumnNo)
{
return GetFieldString(strColumnNo);
}
步骤 2. 创建一个非托管 C++ 的包装类来调用托管 C# COM DLL
接下来,在生成类型库 (tlb) 文件后,我们将像下面的代码一样导入它。
#import "WebDAO.tlb" named_guids raw_interfaces_only
我们应该为类型库 (tlb) 文件创建一个包装类,并可以在包装类中插入附加函数。例如,GetNumberByColumnNo 函数可以将 GetStringByColumnNo 函数转换为获取数字类型的值。
CWebDAO::CWebDAO(CString strConnection)
{
bCOMInitailzed = Initialize();
BSTR bstrTemp = strConnection.AllocSysString();
m_pDotNetCOMPtr->SetConnectionString(bstrTemp);
SysFreeString(bstrTemp);
}
BOOL CWebDAO::Initialize()
{
HRESULT hRes = m_pDotNetCOMPtr.CreateInstance(WebDAO::CLSID_WebDatabaseAccessObject);
return (hRes == S_OK);
}
BOOL CWebDAO::Open(CString strSQL, BOOL bOnlyTableName, BOOL bBlob, BOOL bUseClient)
{
VARIANT_BOOL vbRetVal;
if(bCOMInitailzed)
{
BSTR bstrTemp = strSQL.AllocSysString();
m_pDotNetCOMPtr->Open(bstrTemp, &vbRetVal);
SysFreeString(bstrTemp);
}
return (bCOMInitailzed &&(vbRetVal == -1));
}
int CWebDAO::GetNumberByColumnNo(int nColumnNo)
{
int nReturn = -99999;
CString strReturn = _T("");
try
{
if(bCOMInitailzed)
{
BSTR bstrReturn;
m_pDotNetCOMPtr->GetFieldStringByColumnNo(nColumnNo, &bstrReturn);
strReturn = (LPCTSTR)(_bstr_t)bstrReturn;
}
nReturn = atoi((LPSTR)(LPCTSTR)strReturn);
}
catch (CException* e)
{
e->Delete();
}
return nReturn;
}
步骤 3. 非托管 C++ 应用程序调用托管 C# COM DLL 的包装类
非托管 C++ 应用程序必须调用 CoInitialize 函数来初始化和反初始化 COM 组件。
CoInitialize(NULL);
int CWebDAOTestApp::ExitInstance()
{
// TODO: Add your specialized code here and/or call the base class
CoUninitialize();
return CWinApp::ExitInstance();
}
在运行 IIS(Internet Information Services)之后,运行非托管 C++ 应用程序 [WebDAOTest]。请在 [Web Address] 文本框中输入 ASP.NET 开发服务器的根 URL。如果您正在运行 Web 服务的实际 Web 服务器,请输入 Web-DAO 的实际 URL。如果应用程序正确连接到 Web 服务器,您将能够获得结果数据。
关注点
通常,ADO 或 DAO 的 Open 函数连接到数据库服务器并保持连接。但是 WebDatabaseAccessObject 类的 Open 函数不同,因为 Web 服务是不连续的、请求-响应模式。因此,WebDatabaseAccessObject 类的 Open 函数连接到 Web 服务的 Web 服务器,此外,它接收结果数据并将其分配给 System.Data.DataTable 类的变量。如果您想在结果数据中包含 BLOB(二进制大对象),您应该使用 CWebDAO 的 GetBlobFieldValue 函数而不是 GetFieldValue 函数。如果您希望包装类 CWebDAO 替换现有的 ADO 类或包装,您应该参考以下代码。如果您修改了 CWebDAO 包装类的函数和变量名,现有的类将很容易被替换。
#ifdef _WEB_
CWebDAO recordSet(CDwUtility::GetWebDAOConnection());
#else
CDwAdoRecord recordSet(CDwUtility::GetDBConnection());
#endif
CString strSQL;
strSQL.Format("SELECT COUNT(*) AS EXAM_COUNT FROM T_CODE WHERE EXAM_KEY = %s", m_strExamKey);
BOOL bExamCount = FALSE;
if (recordSet.Open(strSQL))
{
while (recordSet.IsEOF() == FALSE)
{
int nCount;
recordSet.GetFieldValue(_T("EXAM_COUNT"), nCount);
bExamCount = (nCount > 0);
recordSet.MoveNext();
}
recordSet.Close();
}