Skip to content

freehawkzk/ADODBFromUE4

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 

Repository files navigation

【UE4源代码分析】-007 仿UE4使用ADO进行数据库操作

  由于工作的关系,主要开发平台是在windows上。在一些小项目中需要使用数据库时,经常会先使用ACCESS制作项目原型,等验证之后再转移到MySQL下。通常使用的连接Access的方式是ADO,由于公司以往没有对ADO进行封装,使用时颇为不便。   最近看UE4的源代码的时候发现其中有对ADO的一个封装,阅读之后觉得挺好,因此想着把这部分从UE4中独立出来,看看在普通场景下的应用效果如何。 [TOC]

1、UE4的DataBase支持

  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是启用远程数据库支持的宏,在这里我们不做详细讨论。

2、UE4 ADO封装解析

  一般的数据库操作主要关心连接以及记录集,在UE4中,分别为连接和记录集定义了支持的类FDataBaseRecordSetFDataBaseConnection类,为了方便操作记录集,还定义了辅助结构FDatabaseColumnInfo和内部迭代器类TIterator。在以上这些类的基础上,分别为ADO操作数据库派生了记录集类和数据库连接类。

图1 UE4数据库操作记录集类图

图2 UE4数据库操作数据库连接类图

2.1 基类

2.1.1 FDatabaseColumnInfo

  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对象组成的数组。

2.1.2 FDataBaseRecordSet

  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;
	};
};

2.1.3 FDataBaseConnection

  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();
};

2.2 派生类

  基类实现的都是空操作,因此,需要对基类进行有效的派生之后才能用于实际使用。UE4的ADO支持就是通过对基类的有效派生,将数据库操作与ADO操作数据库相结合,完成数据库的操作。

2.2.1 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盘下,而导致运行编译出错。

2.2.2 FADODataBaseRecordSet

  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;
	}
};

2.2.3 FADODataBaseConnection

  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基本类型主要是FStringTArray,这两个类都可以通过使用普通C++类进行替代,因此,将代码从UE4中分离出来难度将不会太大。

3、独立于UE4的封装

  正如在第2节中所表明的,可以参考UE4 ADO操作数据库的方式对ADO进行封装,只要解决在无UE4代码的情况下替代FString和TARRY两个类即可。   在实际编译中发现在#import引入msado15.dll时,会报找不到文件的错误,我没有仔细去分析什么原因了,直接采用了绝对路径,等下次有时间再去找原因了。   我直接将其封装成了一个头文件,方便使用。注意,我是在MFC环境下封装的,所以头文件里会带上stdafx.h,需要使用的话请自己酌情修改。   代码比较长,在附录中。

4、测试

  我尝试使用一个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 程序运行界面

  从上图可以看出,通过封装的数据库操作,将数据库中的数据记录成功的读取出来了。

5、总结

  参考UE4的数据库操作,对ADO操作数据库的过程进行了封装,能够完成数据库的查询,数据记录集的遍历,数据内容的获取。下次可以在项目中实际测试使用。   开发时多总结,闲暇时多阅读代码,总会有收获的。

6、附录

#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;
	}

};

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published