由于工作的关系,主要开发平台是在windows上。在一些小项目中需要使用数据库时,经常会先使用ACCESS制作项目原型,等验证之后再转移到MySQL下。通常使用的连接Access的方式是ADO,由于公司以往没有对ADO进行封装,使用时颇为不便。 最近看UE4的源代码的时候发现其中有对ADO的一个封装,阅读之后觉得挺好,因此想着把这部分从UE4中独立出来,看看在普通场景下的应用效果如何。
【UE4源代码分析】-007 仿UE4使用ADO进行数据库操作 1、UE4的DataBase支持2、UE4 ADO封装解析 2.1 基类 2.1.1 FDatabaseColumnInfo2.1.2 FDataBaseRecordSet2.1.3 FDataBaseConnection 2.2 派生类 2.2.1 ADO的引入2.2.2 FADODataBaseRecordSet2.2.3 FADODataBaseConnection 3、独立于UE4的封装4、测试5、总结6、附录UE4对数据库的支持主要使用DatabaseSupport Module。其实现文件在\Engine\Source\Runtime\DatabaseSupport\路径中,主要包括Database.h\Database.cpp,DatabaseSupport.h\DatabaseSupport.cpp四个文件,其中DatabaseSupport.h\DatabaseSupport.cpp主要用于实现数据库支持模块,具体的数据库操作都实现在Database.h\Database.cpp中。 因此,在这里我主要讨论Database.h\Database.cpp这两个文件。 在Database.h文件开头处,首先进行了三个宏定义:
/** * Whether to compile in support for database connectivity and SQL execution. */ #ifndef WITH_DATABASE_SUPPORT #define WITH_DATABASE_SUPPORT (!UE_BUILD_MINIMAL && !UE_BUILD_SHIPPING) #endif // Only use ADO on windows, if support is enabled and not for shipping games. // @todo clang: #import is not supported by Clang on Windows platform, but we currently need this for ADO symbol importing. For now we disable ADO support in Clang builds. #define USE_ADO_INTEGRATION (PLATFORM_WINDOWS && !(UE_BUILD_SHIPPING && WITH_EDITOR) && WITH_DATABASE_SUPPORT && !PLATFORM_COMPILER_CLANG) #define USE_REMOTE_INTEGRATION (!USE_ADO_INTEGRATION && !(UE_BUILD_SHIPPING && WITH_EDITOR) && WITH_DATABASE_SUPPORT && !PLATFORM_COMPILER_CLANG)从第一个宏WITH_DATABASE_SUPPORT可以发现,UE4默认情况下,只有在非发行模式并且是非最小模式编译时才能启动数据库支持。 第二个宏USE_ADO_INTEGRATION可以理解为启用ADO数据库支持需要同时满足四个条件: - Windows平台; - 不是带编辑器的发行版; - 启用了数据库支持; - 编译器不是CLang。(CLang编译器在windows平台上不支持#import操作,目前暂时禁用了windows平台上CLang编译时的ADO支持)。 第三个宏USE_REMOTE_INTEGRATION是启用远程数据库支持的宏,在这里我们不做详细讨论。
一般的数据库操作主要关心连接以及记录集,在UE4中,分别为连接和记录集定义了支持的类FDataBaseRecordSet和FDataBaseConnection类,为了方便操作记录集,还定义了辅助结构FDatabaseColumnInfo和内部迭代器类TIterator。在以上这些类的基础上,分别为ADO操作数据库派生了记录集类和数据库连接类。
图1 UE4数据库操作记录集类图 图2 UE4数据库操作数据库连接类图FDatabaseColumnInfo主要用于辅助操作记录集,用于记录记录集中列的名称和数据类型。
/** * This struct holds info relating to a column. Specifically, we need to get back * certain meta info from a RecordSet so we can "Get" data from it. */ struct FDatabaseColumnInfo { /** Default constructor **/ FDatabaseColumnInfo(): DataType(DBT_UNKOWN) {} /** The name of the column **/ FString ColumnName; /** This is the type of data in this column. (e.g. so you can do GetFloat or GetInt on the column **/ EDataBaseUnrealTypes DataType; bool operator==(const FDatabaseColumnInfo& OtherFDatabaseColumnInfo) const { return (ColumnName==OtherFDatabaseColumnInfo.ColumnName) && (DataType==OtherFDatabaseColumnInfo.DataType); } };在FDataBaseRecordSet::GetColumnNames接口中,返回的是FDatabaseColumnInfo对象组成的数组。
FDataBaseRecordSet类是数据库记录集的基类,主要定义了数据库记录集对象需要具备的接口函数,并实现了一个空版本的接口,并为遍历记录集提供了迭代器。
/** * Empty base class for iterating over database records returned via query. Used on platforms not supporting * a direct database connection. */ class FDataBaseRecordSet { // Protected functions used internally for iteration. protected: /** Moves to the first record in the set. */ virtual void MoveToFirst() {} /** Moves to the next record in the set. */ virtual void MoveToNext() {} /** * Returns whether we are at the end. * * @return true if at the end, false otherwise */ virtual bool IsAtEnd() const { return true; } public: /** * Returns a count of the number of records in the record set */ virtual int32 GetRecordCount() const { return 0; } /** * Returns a string associated with the passed in field/ column for the current row. * * @param Column Name of column to retrieve data for in current row */ virtual FString GetString( const TCHAR* Column ) const { return TEXT("No database connection compiled in."); } /** * Returns an integer associated with the passed in field/ column for the current row. * * @param Column Name of column to retrieve data for in current row */ virtual int32 GetInt( const TCHAR* Column ) const { return 0; } /** * Returns a float associated with the passed in field/ column for the current row. * * @param Column Name of column to retrieve data for in current row */ virtual float GetFloat( const TCHAR* Column ) const { return 0; } /** * Returns a int64 associated with the passed in field/ column for the current row. * * @param Column Name of column to retrieve data for in current row */ virtual int64 GetBigInt( const TCHAR* Column ) const { return 0; } /** * Returns the set of column names for this Recordset. This is useful for determining * what you can actually ask the record set for without having to hard code those ahead of time. */ virtual TArray<FDatabaseColumnInfo> GetColumnNames() const { TArray<FDatabaseColumnInfo> Retval; return Retval; } /** Virtual destructor as class has virtual functions. */ virtual ~FDataBaseRecordSet() {} /** * Iterator helper class based on FObjectIterator. */ class TIterator { public: /** * Initialization constructor. * * @param InRecordSet RecordSet to iterate over */ TIterator( FDataBaseRecordSet* InRecordSet ) : RecordSet( InRecordSet ) { RecordSet->MoveToFirst(); } /** * operator++ used to iterate to next element. */ void operator++() { RecordSet->MoveToNext(); } /** Conversion to "bool" returning true if the iterator is valid. */ FORCEINLINE explicit operator bool() const { return !RecordSet->IsAtEnd(); } /** inverse of the "bool" operator */ FORCEINLINE bool operator !() const { return !(bool)*this; } // Access operators FORCEINLINE FDataBaseRecordSet* operator*() const { return RecordSet; } FORCEINLINE FDataBaseRecordSet* operator->() const { return RecordSet; } protected: /** Database record set being iterated over. */ FDataBaseRecordSet* RecordSet; }; };FDataBaseConnection是数据库连接类的基类,主要定义了数据库操作的连接、关闭、执行SQL语句等接口。
/** * Empty base class for database access via executing SQL commands. Used on platforms not supporting * a direct database connection. */ class FDataBaseConnection { public: /** Virtual destructor as we have virtual functions. */ virtual ~FDataBaseConnection() {} /** * Opens a connection to the database. * * @param ConnectionString Connection string passed to database layer * @param RemoteConnectionIP The IP address which the RemoteConnection should connect to * @param RemoteConnectionStringOverride The connection string which the RemoteConnection is going to utilize * * @return true if connection was successfully established, false otherwise */ virtual bool Open( const TCHAR* ConnectionString, const TCHAR* RemoteConnectionIP, const TCHAR* RemoteConnectionStringOverride ) { return false; } /** * Closes connection to database. */ virtual void Close() {} /** * Executes the passed in command on the database. * * @param CommandString Command to execute * * @return true if execution was successful, false otherwise */ virtual bool Execute( const TCHAR* CommandString ) { return false; } /** * Executes the passed in command on the database. The caller is responsible for deleting * the created RecordSet. * * @param CommandString Command to execute * @param RecordSet Reference to recordset pointer that is going to hold result * * @return true if execution was successful, false otherwise */ virtual bool Execute( const TCHAR* CommandString, FDataBaseRecordSet*& RecordSet ) { RecordSet = nullptr; return false; } /** * Static function creating appropriate database connection object. * * @return instance of platform specific database connection object */ DATABASESUPPORT_API static FDataBaseConnection* CreateObject(); };基类实现的都是空操作,因此,需要对基类进行有效的派生之后才能用于实际使用。UE4的ADO支持就是通过对基类的有效派生,将数据库操作与ADO操作数据库相结合,完成数据库的操作。
通常情况下,在C++中使用ADO操作数据库都会通过#import指令引入msado15.dll,UE4也不例外引入了msado15.dll。不同之处在于引入时的文件路径设置。
// Using import allows making use of smart pointers easily. Please post to the list if a workaround such as // using %COMMONFILES% works to hide the localization issues and non default program file folders. //#import "C:\Program files\Common Files\System\Ado\msado15.dll" rename("EOF", "ADOEOF") #pragma warning(push) #pragma warning(disable: 4471) // a forward declaration of an unscoped enumeration must have an underlying type (int assumed) #import "System\ADO\msado15.dll" rename("EOF", "ADOEOF") //lint !e322 #pragma warning(pop)一般情况下,会采用//#import "C:\Program files\Common Files\System\Ado\msado15.dll" rename("EOF", "ADOEOF")的方式,采用绝对路径的方式引入,但由于系统安装不同的原因,可能导致msado15.dll并不在C盘下,而导致运行编译出错。
FADODataBaseRecordSet类派生自FDataBaseRecordSet类,主要完成ADO记录集的遍历、字段数据的提取的功能。限于篇幅,文章中对类的实现进行了删减,读者可以自行查看UE4的源代码。
/** * ADO implementation of database record set. */ class FADODataBaseRecordSet : public FDataBaseRecordSet { private: ADODB::_RecordsetPtr ADORecordSet; protected: /** Moves to the first record in the set. */ virtual void MoveToFirst() { } /** Moves to the next record in the set. */ virtual void MoveToNext() { } /** * Returns whether we are at the end. * * @return true if at the end, false otherwise */ virtual bool IsAtEnd() const { return !!ADORecordSet->ADOEOF; } public: /** * Returns a count of the number of records in the record set */ virtual int32 GetRecordCount() const { return ADORecordSet->RecordCount; } /** * Returns a string associated with the passed in field/ column for the current row. * * @param Column Name of column to retrieve data for in current row */ virtual FString GetString( const TCHAR* Column ) const { return ReturnString; } /** * Returns an integer associated with the passed in field/ column for the current row. * * @param Column Name of column to retrieve data for in current row */ virtual int32 GetInt( const TCHAR* Column ) const { return ReturnValue; } /** * Returns a float associated with the passed in field/ column for the current row. * * @param Column Name of column to retrieve data for in current row */ virtual float GetFloat( const TCHAR* Column ) const { return ReturnValue; } /** * Returns an int64 associated with the passed in field/ column for the current row. * * @param Column Name of column to retrieve data for in current row */ virtual int64 GetBigInt( const TCHAR* Column ) const { return ReturnValue; } /** * Returns the set of column names for this Recordset. This is useful for determining * what you can actually ask the record set for without having to hard code those ahead of time. */ virtual TArray<FDatabaseColumnInfo> GetColumnNames() const { return Retval; } /** * Constructor, used to associate ADO record set with this class. * * @param InADORecordSet ADO record set to use */ FADODataBaseRecordSet( ADODB::_RecordsetPtr InADORecordSet ) : ADORecordSet( InADORecordSet ) { } /** Destructor, cleaning up ADO record set. */ virtual ~FADODataBaseRecordSet() { if(ADORecordSet && (ADORecordSet->State & ADODB::adStateOpen)) { // We're using smart pointers so all we need to do is close and assign NULL. ADORecordSet->Close(); } ADORecordSet = NULL; } };FADODataBaseConnection派生自FDataBaseConnection,主要完成数据库的连接工作。
/** * Data base connection class using ADO C++ interface to communicate with SQL server. */ class FADODataBaseConnection : public FDataBaseConnection { private: /** ADO database connection object. */ ADODB::_ConnectionPtr DataBaseConnection; public: /** Constructor, initializing all member variables. */ FADODataBaseConnection() { DataBaseConnection = NULL; } /** Destructor, tearing down connection. */ virtual ~FADODataBaseConnection() { Close(); } /** * Opens a connection to the database. * * @param ConnectionString Connection string passed to database layer * @param RemoteConnectionIP The IP address which the RemoteConnection should connect to * @param RemoteConnectionStringOverride The connection string which the RemoteConnection is going to utilize * * @return true if connection was successfully established, false otherwise */ virtual bool Open( const TCHAR* ConnectionString, const TCHAR* RemoteConnectionIP, const TCHAR* RemoteConnectionStringOverride ) { return true; } /** * Closes connection to database. */ virtual void Close() { } /** * Executes the passed in command on the database. * * @param CommandString Command to execute * * @return true if execution was successful, false otherwise */ virtual bool Execute( const TCHAR* CommandString ) { return true; } /** * Executes the passed in command on the database. The caller is responsible for deleting * the created RecordSet. * * @param CommandString Command to execute * @param RecordSet Reference to recordset pointer that is going to hold result * * @return true if execution was successful, false otherwise */ virtual bool Execute( const TCHAR* CommandString, FDataBaseRecordSet*& RecordSet ) { return RecordSet != NULL; } };从以上的类的实现可以看出,UE4中数据库对ADO的封装主要是将数据库连接封装成了FADODataBaseConnection,记录集封装成了FADODataBaseRecordSet类,并且在这两个类的实现中,涉及的UE基本类型主要是FString和TArray,这两个类都可以通过使用普通C++类进行替代,因此,将代码从UE4中分离出来难度将不会太大。
正如在第2节中所表明的,可以参考UE4 ADO操作数据库的方式对ADO进行封装,只要解决在无UE4代码的情况下替代FString和TARRY两个类即可。 在实际编译中发现在#import引入msado15.dll时,会报找不到文件的错误,我没有仔细去分析什么原因了,直接采用了绝对路径,等下次有时间再去找原因了。 我直接将其封装成了一个头文件,方便使用。注意,我是在MFC环境下封装的,所以头文件里会带上stdafx.h,需要使用的话请自己酌情修改。 代码比较长,在附录中。
我尝试使用一个MFC工程,利用刚才封装的ADO操作数据库代码去连接并读取数据库里的内容。我使用的数据库是Access数据库,数据库表结构如下:
图3 数据库表结构数据库中已有的记录集如所示。
图4 6条数据记录在对话框窗口初始化的时候连接并读取数据库表中的记录,将记录总数和其中的username字段内容显示在界面上。
FDataBaseConnection* pConn = new FADODataBaseConnection; bool hr = pConn->Open(L"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=DatabaseFromUE4.mdb", NULL, NULL); if (hr) { FDataBaseRecordSet* pRes = NULL; pConn->Execute(L"SELECT * FROM usernameS", pRes); if (pRes) { int nCount = pRes->GetRecordCount(); CString strInfo; strInfo.Format(L"表中有%d条记录,名字分别是:", nCount); FDataBaseRecordSet::TIterator itor(pRes); while (itor) { CString temp = itor->GetString(L"username"); TRACE(L"%s\n", temp); strInfo = strInfo + L" " + temp; ++itor; } GetDlgItem(IDC_INFO)->SetWindowText(strInfo); } delete pRes; pRes = NULL; } pConn->Close(); delete pConn; pConn = NULL;程序运行界面如下所示。
图5 程序运行界面从上图可以看出,通过封装的数据库操作,将数据库中的数据记录成功的读取出来了。
参考UE4的数据库操作,对ADO操作数据库的过程进行了封装,能够完成数据库的查询,数据记录集的遍历,数据内容的获取。下次可以在项目中实际测试使用。 开发时多总结,闲暇时多阅读代码,总会有收获的。
项目源代码:https://github.com/freehawkzk/ADODBFromUE4.git
#pragma once #include "stdafx.h" #include <vector> /** * Enums for Database types. Each Database has their own set of DB types and */ enum EDataBaseUnrealTypes { DBT_UNKOWN, DBT_FLOAT, DBT_INT, DBT_STRING, }; /** * This struct holds info relating to a column. Specifically, we need to get back * certain meta info from a RecordSet so we can "Get" data from it. */ struct FDatabaseColumnInfo { /** Default constructor **/ FDatabaseColumnInfo() : DataType(DBT_UNKOWN) {} /** The name of the column **/ CString ColumnName; /** This is the type of data in this column. (e.g. so you can do GetFloat or GetInt on the column **/ EDataBaseUnrealTypes DataType; bool operator==(const FDatabaseColumnInfo& OtherFDatabaseColumnInfo) const { return (ColumnName == OtherFDatabaseColumnInfo.ColumnName) && (DataType == OtherFDatabaseColumnInfo.DataType); } }; /** * Empty base class for iterating over database records returned via query. Used on platforms not supporting * a direct database connection. */ class FDataBaseRecordSet { // Protected functions used internally for iteration. protected: /** Moves to the first record in the set. */ virtual void MoveToFirst() {} /** Moves to the next record in the set. */ virtual void MoveToNext() {} /** * Returns whether we are at the end. * * @return true if at the end, false otherwise */ virtual bool IsAtEnd() const { return true; } public: /** * Returns a count of the number of records in the record set */ virtual int GetRecordCount() const { return 0; } /** * Returns a string associated with the passed in field/ column for the current row. * * @param Column Name of column to retrieve data for in current row */ virtual CString GetString(const TCHAR* Column) const { return TEXT("No database connection compiled in."); } /** * Returns an integer associated with the passed in field/ column for the current row. * * @param Column Name of column to retrieve data for in current row */ virtual int GetInt(const TCHAR* Column) const { return 0; } /** * Returns a float associated with the passed in field/ column for the current row. * * @param Column Name of column to retrieve data for in current row */ virtual float GetFloat(const TCHAR* Column) const { return 0; } /** * Returns a int64 associated with the passed in field/ column for the current row. * * @param Column Name of column to retrieve data for in current row */ virtual LONG64 GetBigInt(const TCHAR* Column) const { return 0; } /** * Returns the set of column names for this Recordset. This is useful for determining * what you can actually ask the record set for without having to hard code those ahead of time. */ virtual std::vector<FDatabaseColumnInfo> GetColumnNames() const { std::vector<FDatabaseColumnInfo> Retval; return Retval; } /** Virtual destructor as class has virtual functions. */ virtual ~FDataBaseRecordSet() {} /** * Iterator helper class based on FObjectIterator. */ class TIterator { public: /** * Initialization constructor. * * @param InRecordSet RecordSet to iterate over */ TIterator(FDataBaseRecordSet* InRecordSet) : RecordSet(InRecordSet) { RecordSet->MoveToFirst(); } /** * operator++ used to iterate to next element. */ void operator++() { RecordSet->MoveToNext(); } /** Conversion to "bool" returning true if the iterator is valid. */ FORCEINLINE explicit operator bool() const { return !RecordSet->IsAtEnd(); } /** inverse of the "bool" operator */ FORCEINLINE bool operator !() const { return !(bool)*this; } // Access operators FORCEINLINE FDataBaseRecordSet* operator*() const { return RecordSet; } FORCEINLINE FDataBaseRecordSet* operator->() const { return RecordSet; } protected: /** Database record set being iterated over. */ FDataBaseRecordSet* RecordSet; }; }; /** * Empty base class for database access via executing SQL commands. Used on platforms not supporting * a direct database connection. */ class FDataBaseConnection { public: /** Virtual destructor as we have virtual functions. */ virtual ~FDataBaseConnection() {} /** * Opens a connection to the database. * * @param ConnectionString Connection string passed to database layer * @param RemoteConnectionIP The IP address which the RemoteConnection should connect to * @param RemoteConnectionStringOverride The connection string which the RemoteConnection is going to utilize * * @return true if connection was successfully established, false otherwise */ virtual bool Open(const TCHAR* ConnectionString, const TCHAR* RemoteConnectionIP, const TCHAR* RemoteConnectionStringOverride) { return false; } /** * Closes connection to database. */ virtual void Close() {} /** * Executes the passed in command on the database. * * @param CommandString Command to execute * * @return true if execution was successful, false otherwise */ virtual bool Execute(const TCHAR* CommandString) { return false; } /** * Executes the passed in command on the database. The caller is responsible for deleting * the created RecordSet. * * @param CommandString Command to execute * @param RecordSet Reference to recordset pointer that is going to hold result * * @return true if execution was successful, false otherwise */ virtual bool Execute(const TCHAR* CommandString, FDataBaseRecordSet*& RecordSet) { RecordSet = nullptr; return false; } /** * Static function creating appropriate database connection object. * * @return instance of platform specific database connection object */ static FDataBaseConnection* CreateObject() { return new FDataBaseConnection(); } }; // Using import allows making use of smart pointers easily. Please post to the list if a workaround such as // using %COMMONFILES% works to hide the localization issues and non default program file folders. //#import "C:\Program files\Common Files\System\Ado\msado15.dll" rename("EOF", "ADOEOF") #pragma warning(push) #pragma warning(disable: 4471) // a forward declaration of an unscoped enumeration must have an underlying type (int assumed) #import "C:\Program files\Common Files\System\ADO\msado15.dll" rename("EOF", "ADOEOF") //lint !e322 #pragma warning(pop) /*----------------------------------------------------------------------------- FADODataBaseRecordSet implementation. -----------------------------------------------------------------------------*/ /** * ADO implementation of database record set. */ class FADODataBaseRecordSet : public FDataBaseRecordSet { private: ADODB::_RecordsetPtr ADORecordSet; protected: /** Moves to the first record in the set. */ virtual void MoveToFirst() { if (!ADORecordSet->BOF || !ADORecordSet->ADOEOF) { ADORecordSet->MoveFirst(); } } /** Moves to the next record in the set. */ virtual void MoveToNext() { if (!ADORecordSet->ADOEOF) { ADORecordSet->MoveNext(); } } /** * Returns whether we are at the end. * * @return true if at the end, false otherwise */ virtual bool IsAtEnd() const { return !!ADORecordSet->ADOEOF; } public: /** * Returns a count of the number of records in the record set */ virtual int GetRecordCount() const { return ADORecordSet->RecordCount; } /** * Returns a string associated with the passed in field/ column for the current row. * * @param Column Name of column to retrieve data for in current row */ virtual CString GetString(const TCHAR* Column) const { CString ReturnString; // Retrieve specified column field value for selected row. _variant_t Value = ADORecordSet->GetCollect(Column); // Check variant type for validity and cast to specified type. _variant_t has overloaded cast operators. if (Value.vt != VT_NULL) { ReturnString = (TCHAR*)_bstr_t(Value); } // Unknown column. else { ReturnString = TEXT("Unknown Column"); } return ReturnString; } /** * Returns an integer associated with the passed in field/ column for the current row. * * @param Column Name of column to retrieve data for in current row */ virtual int GetInt(const TCHAR* Column) const { int ReturnValue = 0; // Retrieve specified column field value for selected row. _variant_t Value = ADORecordSet->GetCollect(Column); // Check variant type for validity and cast to specified type. _variant_t has overloaded cast operators. if (Value.vt != VT_NULL) { ReturnValue = (int)Value; } return ReturnValue; } /** * Returns a float associated with the passed in field/ column for the current row. * * @param Column Name of column to retrieve data for in current row */ virtual float GetFloat(const TCHAR* Column) const { float ReturnValue = 0; // Retrieve specified column field value for selected row. _variant_t Value = ADORecordSet->GetCollect(Column); // Check variant type for validity and cast to specified type. _variant_t has overloaded cast operators. if (Value.vt != VT_NULL) { ReturnValue = (float)Value; } return ReturnValue; } /** * Returns an int64 associated with the passed in field/ column for the current row. * * @param Column Name of column to retrieve data for in current row */ virtual LONG64 GetBigInt(const TCHAR* Column) const { LONG64 ReturnValue = 0; // Retrieve specified column field value for selected row. _variant_t Value = ADORecordSet->GetCollect(Column); // Check variant type for validity and cast to specified type. _variant_t has overloaded cast operators. if (Value.vt != VT_NULL) { ReturnValue = (LONG64)Value; } return ReturnValue; } /** * Returns the set of column names for this Recordset. This is useful for determining * what you can actually ask the record set for without having to hard code those ahead of time. */ virtual std::vector<FDatabaseColumnInfo> GetColumnNames() const { std::vector<FDatabaseColumnInfo> Retval; if (!ADORecordSet->BOF || !ADORecordSet->ADOEOF) { ADORecordSet->MoveFirst(); for (int i = 0; i < ADORecordSet->Fields->Count; ++i) { _bstr_t bstrName = ADORecordSet->Fields->Item[i]->Name; _variant_t varValue = ADORecordSet->Fields->Item[i]->Value; ADODB::DataTypeEnum DataType = ADORecordSet->Fields->Item[i]->Type; FDatabaseColumnInfo NewInfo; NewInfo.ColumnName = CString((TCHAR*)_bstr_t(bstrName)); // from http://www.w3schools.com/ado/prop_field_type.asp#datatypeenum switch (DataType) { case ADODB::adInteger: case ADODB::adBigInt: NewInfo.DataType = DBT_INT; break; case ADODB::adSingle: case ADODB::adDouble: NewInfo.DataType = DBT_FLOAT; break; case ADODB::adWChar: case ADODB::adVarWChar: NewInfo.DataType = DBT_STRING; break; default: NewInfo.DataType = DBT_UNKOWN; break; } Retval.push_back(NewInfo); } } return Retval; } /** * Constructor, used to associate ADO record set with this class. * * @param InADORecordSet ADO record set to use */ FADODataBaseRecordSet(ADODB::_RecordsetPtr InADORecordSet) : ADORecordSet(InADORecordSet) { } /** Destructor, cleaning up ADO record set. */ virtual ~FADODataBaseRecordSet() { if (ADORecordSet && (ADORecordSet->State & ADODB::adStateOpen)) { // We're using smart pointers so all we need to do is close and assign NULL. ADORecordSet->Close(); } ADORecordSet = NULL; } }; /*----------------------------------------------------------------------------- FADODataBaseConnection implementation. -----------------------------------------------------------------------------*/ /** * Data base connection class using ADO C++ interface to communicate with SQL server. */ class FADODataBaseConnection : public FDataBaseConnection { private: /** ADO database connection object. */ ADODB::_ConnectionPtr DataBaseConnection; public: /** Constructor, initializing all member variables. */ FADODataBaseConnection() { DataBaseConnection = NULL; } /** Destructor, tearing down connection. */ virtual ~FADODataBaseConnection() { Close(); } /** * Opens a connection to the database. * * @param ConnectionString Connection string passed to database layer * @param RemoteConnectionIP The IP address which the RemoteConnection should connect to * @param RemoteConnectionStringOverride The connection string which the RemoteConnection is going to utilize * * @return true if connection was successfully established, false otherwise */ virtual bool Open(const TCHAR* ConnectionString, const TCHAR* RemoteConnectionIP, const TCHAR* RemoteConnectionStringOverride) { if (!::AfxOleInit()) { return false; } // Create instance of DB connection object. HRESULT hr = DataBaseConnection.CreateInstance(__uuidof(ADODB::Connection)); if (FAILED(hr)) { ::CoUninitialize(); throw _com_error(hr); } // Open the connection. Operation is synchronous. DataBaseConnection->Open(ConnectionString, TEXT(""), TEXT(""), ADODB::adConnectUnspecified); return true; } /** * Closes connection to database. */ virtual void Close() { // Close database connection if exists and free smart pointer. if (DataBaseConnection && (DataBaseConnection->State & ADODB::adStateOpen)) { DataBaseConnection->Close(); ::CoUninitialize(); } DataBaseConnection = NULL; } /** * Executes the passed in command on the database. * * @param CommandString Command to execute * * @return true if execution was successful, false otherwise */ virtual bool Execute(const TCHAR* CommandString) { // Execute command, passing in optimization to tell DB to not return records. DataBaseConnection->Execute(CommandString, NULL, ADODB::adExecuteNoRecords); return true; } /** * Executes the passed in command on the database. The caller is responsible for deleting * the created RecordSet. * * @param CommandString Command to execute * @param RecordSet Reference to recordset pointer that is going to hold result * * @return true if execution was successful, false otherwise */ virtual bool Execute(const TCHAR* CommandString, FDataBaseRecordSet*& RecordSet) { // Initialize return value. RecordSet = NULL; // Create instance of record set. ADODB::_RecordsetPtr ADORecordSet = NULL; ADORecordSet.CreateInstance(__uuidof(ADODB::Recordset)); // Execute the passed in command on the record set. The recordset returned will be in open state so you can call Get* on it directly. ADORecordSet->Open(CommandString, _variant_t((IDispatch *)DataBaseConnection), ADODB::adOpenStatic, ADODB::adLockReadOnly, ADODB::adCmdText); // Create record set from returned data. RecordSet = new FADODataBaseRecordSet(ADORecordSet); return RecordSet != NULL; } };