数据库基本概念和 ADO 编程创建数据库及数据表

数据库 专栏收录该内容
3 篇文章 0 订阅

目录

一、数据库、数据库管理系统、数据区编程技术概念及区别

二、ADO 和 ADOX 概念及区别

三、ADO 的结构

三、用 ADOX 对象创建数据库

四、用 ADO创建数据库中的表

五、常见的 SQL 语句

六、使用_ConnectionPtr接口开发ACCESS数据库

七、使用_RecordsetPtr接口开发ACCESS数据库


一、数据库、数据库管理系统、数据库编程技术概念及区别

数据库(DataBase,DB):是以一定的组织方式将相关的数据组织在一起,存放在计算机外存储器上,能为多个用户共享的与应用程序彼此独立的一组关系数据的集合。可以简单理解为:存放数据的仓库。

数据管理系统(DataBase Management System,DBMS):是一种操纵和管理数据库的软件系统。DBMS 是用于描述、管理和维护数据库的程序系统,是数据库系统的核心组成部分。它建立在操作系统的基础上,对数据库进行统一的管理和控制。其功能包括数据库定义、数据库管理、数据库建立和维护、与操作系统通信等。通常,数据库管理系统能够方便用户快速地建立、维护、修改、检索和删除数据库中的数据。可以简单的理解为:“管理”数据库的应用软件,实现“增删改查”如 SQL、ACCESS。

数据库编程技术:Visual C++中提供了 4 中不同的技术来使应用程序访问数据库,即 DAO(Data Access Object)、ODBC(Open Database Connectivy)、OLE DB(Object Link and Embedding DataBase)和 ADO(ActiveX Data Objects)。其中最简单的是 ODBC,最常用的是 ADO。ODBC 编程技术可以简单的理解为:不通过 DBMS 访问数据库的标准接口,数据库厂商按照接口标准开发的一套软件,这套软件叫 ODBC 驱动,电脑安装驱动后就可以使用 C++语言管理数据库。

二、ADO 和 ADOX 概念及区别

ADO是Microsoft 最新推出的数据库访问的高层软件接口。它和Microsoft以前的数据库访问接口DAO、ODBC相比具有更大的灵活性,使用也更方便,开发效率大为提高。ADO 访问数据库是通过访问 OLE DB 数据提供程序来进行的,提供了一种对 OLE DB 数据提供程序的简单高层访问接口。ADO 技术简化了 OLE DB 的操作,OLE DB 的程序中使用了大量的通用对象模型(COM)接口,而 ADO 封装了这些接口,因此,ADO 是一种高层的访问技术。

ADO 访问数据源的特点:

  • 易于使用。这是 ADO 技术最重要的一个特征。由于 ADO 是高层应用,所以相对于 OLE DB 或者 ODBC 来说,它具有面向对象的特征。
  • 高速访问数据源。由于 ADO 技术基于 OLE DB,因而它也继承了 OLE DB 访问数据库的高速性。到目前为止,ADO 是目前最快的数据库访问技术。
  • 可以访问不同数据源。ADO 技术可以访问包括关系数据库和菲关系数据库在内的所有文件系统,此特点使应用程序具有灵活性和通用性。
  • 可以用于 Microsoft ActiveX 页。ADO 技术可以以 ActiveX 控件的形式出现,因此可以被用于 Microsoft ActiveX 页,此特征可简化 web 页的编程。
  • 程序占用内存少。由于 ADO 是基于组件对象模型的访问技术,因而用 ADO产生的应用程序占用内存少。

ADOX是核心ADO对象的扩展库。它提供的附加对象可用于创建、修改和删除模式对象,如表和过程。要使用ADOX,则应建立对ADOX类型库的引用。ADOX库文件名为 Msadox.dll。

通俗地讲,ADO是访问数据库的一种接口,可以使用它方便地进行数据库编程。而ADOX是微软对ADO功能的扩展,比如:可以ADOX创建数据库(而ADO没有创建数据库的功能)。

三、ADO 的结构

要系统学习 ADO 编程可以参考 Microsoft提供的编程指南:https://docs.microsoft.com/zh-cn/office/client-developer/access/desktop-database-reference/ado-programmer-s-guide

ADO 中包含了一系列的对象与集合,ADO 数据对象模型结构如图所示。

ADO 结构图

从上图可以看出 ADO 中包含了 7 个对象和 4 个集合,下表分别给出了各个对象与集合的功能。

ADO 对象的功能描述
对象说明
链接对象(Connection)用于与数据源的链接以及处理一些命令和事物
命令对象(Command)用于处理传递给数据源的命令
记录集对象(Recordset)用于处理数据的记录集,如获取和修改数据
域对象(Field)用于表示记录集中的列信息,包括列值以及其他信息
参数对象(Parameter)用于对传递给数据源的命令赋参数值
属性对象(Property)用于操作在 ADO 中使用的其他对象的详细属性
错误对象(Error)用于获得连接对象所发生的详细错误信息
ADO 集合的功能描述
集合说明
域集合(FIelds)记录集对象中包含了域对象的集合,域对象的集合中包含了所有代表记录集中每列的域对象
参数集合(Parameters)命令对象中包含了参数对象的集合,参数集合中包含了应用于命令对象的所有参数对象。
属性集合(Properties)在链接对象、命令对象、记录集对象和域对象中都包含了属性对象的集合,属性对象的集合中包含了这些对象的所有特性
错误集合(Errors)连接对象中包含了错误对象的集合,错误集合中包含了再一次连接数据源时所产生的所有错误对象

三、用 ADOX 对象创建数据库

请参考博主文章:https://blog.csdn.net/qq_41291253/article/details/103522820

四、用 ADO创建数据库中的表

我们一般用ADOX创建数据库,然后再用ADO创建数据库的表。

例程CREATE_DB_AND_TABLE演示如何使用ADO创建ACCESS数据库的表。

打开VC++ 6.0,新建一个基于对话框的工程CREATE_DB_AND_TABLE。在对话框IDD_CREATE_DB_AND_TABLE_DIALOG中添加如下控件:  

 

控件名称

 

ID

 

用途

 

编辑框

 

IDC_DBNAME

 

输入数据库名称

 

按钮

 

IDC_BTN_CREATE

 

创建数据库

 

编辑框

 

IDC_TABLENAME

 

输入表名

 

按钮

 

IDC_BTN_CREATE_TABLE

 

创建表

使用ClassWizard给两个编辑框创建CString变量:  

 

编辑框

 

CString变量

 

编辑框IDC_DBNAME

 

m_dbName

 

编辑框IDC_TABLENAME

 

m_tableName

创建数据库:双击IDC_BTN_CREATE按钮,并编辑OnBtnCreate()函数如下:

void CADOXCreateDatabaseDlg::OnBtnCreate() 
{
    UpdateData(TRUE);

    CString str;
    str = "d://"+ m_dbName +".mdb";

    if(PathFileExists(str))
    {
       CString strTemp;
       strTemp.Format("%s已存在!", str);
       AfxMessageBox(strTemp);
       return;
    }

    _CatalogPtr m_pCatalog = NULL;
    CString DBName = "Provider = Microsoft.JET.OLEDB.4.0; Data source = ";
    DBName = DBName + str;

    try
    {
       m_pCatalog.CreateInstance(__uuidof(Catalog));
       m_pCatalog->Create(_bstr_t((LPCTSTR)DBName));
    }
    catch(_com_error &e)
    {
       AfxMessageBox(e.ErrorMessage());
       return;
    }   
}

以上代码在第三部分例程ADOXCreateDatabase中已经详细叙述,请点击: https://blog.csdn.net/qq_41291253/article/details/103522820 查看

创建数据表:双击IDC_BTN_CREATE_TABLE按钮,并编辑OnBtnCreateTable()函数如下:

void CCREATE_DB_AND_TABLEDlg::OnBtnCreateTable() 
{
    //先判断表名编辑框是否为空
    UpdateData(TRUE);
    if(!m_tableName.IsEmpty())
    {
       ADOX::_CatalogPtr m_pCatalog = NULL;
       ADOX::_TablePtr m_pTable = NULL;

       CString str;
       str="d://"+ m_dbName +".mdb";
       CString DBName = "Provider = Microsoft.JET.OLEDB.4.0; Data source =";
       DBName = DBName + str;

       //这段代码先检查表是否已经存在,如果表已经存在,不再创建,直接返回。
       try
       {
           m_pCatalog.CreateInstance(__uuidof(ADOX::Catalog));
           m_pCatalog->PutActiveConnection(_bstr_t(DBName));

           int tableCount = m_pCatalog->Tables->Count;
           int i = 0;
           while(i<tableCount)
           {
              m_pTable = (ADOX::_TablePtr)m_pCatalog->Tables->GetItem((long)i);
              CString tableName = m_pTable->Name;

              if(tableName == m_tableName)
              {
                  AfxMessageBox("该表已经存在!");
                  return;
              }
              i++;
           }
       }
       catch(_com_error &e)
       {
           AfxMessageBox(e.Description());
           return;
       }

       //创建表
       ADODB::_ConnectionPtr m_pConnection;
       _variant_t RecordsAffected;

       //此段代码和数据库建立链接
       try
       {
           m_pConnection.CreateInstance(__uuidof(ADODB::Connection));     
           m_pConnection->Open(_bstr_t(DBName), "", "", ADODB::adModeUnknown);
       }
       catch(_com_error e)
       {
           CString errormessage;
           errormessage.Format("连接数据库失败!/r错误信息:%s", e.ErrorMessage());
           AfxMessageBox(errormessage);
           return;
       }

       //此段代码用来创建表格
       try
       {
           CString strCommand;

           //执行SQL命令:CREATE TABLE创建表格
           //该表包含三个字段:记录编号 INTEGER,姓名 TEXT,出生年月 DATETIME
           //SQL语言中的create table语句被用来建立新的数据库表格。
           //create table语句的使用格式如下:
           //create tablename (column1 data type,column2 data type,column3 data type);
           //如果用户希望在建立新表格时规定列的限制条件,可以使用可选的条件选项
           //create table tablename 
           //    (column1 data type[constraint],
           //    column2 data type[constraint],
           //    column3 data type[constraint]);
       
           //举例:

           //create table employee
           //    (firstname varchar(15),
           //    lastname varchar(20),
           //    age number(3),
           //    address varchar(30),
           //    city varchar(20));

           //简单来说,创建新表格时,在关键词create table后面加入所要建立的表格的名称,
           //然后在括号内顺次设定各列的名称,数据类型,以及可选的限定条件等。         
           //使用SQL语句创建的数据库表格和表格中列的名称必须以字母开头,
           //后面可以使用字母,数字或下划线,名称的长度不能超过30个字符,
           //注意,用户在选择表格名称时不要使用SQL语言中的保留关键字,
           //如select,create,insert等,作为表格或列的名称

           strCommand.Format("CREATE TABLE %s(记录编号 INTEGER,姓名 TEXT,出生年月 DATETIME)",m_tableName);
           m_pConnection->Execute(_bstr_t(strCommand),&RecordsAffected,ADODB::adCmdText);

           if(m_pConnection->State)
              m_pConnection->Close();
       }
       catch(_com_error &e)
       {
           AfxMessageBox(e.Description());
       }
    }
}

/*
error C3872: "0xa0": 此字符不允许在标识符中使用

或者 error C3872: '0xa0': this character is not allowed in an identifier


这是因为直接复制代码的问题。0xa0是十六进制数,换成十进制就是160,表示汉字的开始。
解决办法:在报错的代码行检查两边的空格,用英文输入法的空格替换掉。
*/

这段代码先用ADOX的Catalog对象检查表是否已经存在,如果该表已经存在,直接返回;如果还没有该表,使用ADO的Connection对象的Execute函数创建表。

如例程ADOXCreateDatabase,在BOOL CCREATE_DB_AND_TABLEApp::InitInstance()函数中也需要加入:  

if(!AfxOleInit())
{
    AfxMessageBox("OLE初始化出错!");
    return FALSE;
}

在stdafx.h中加入如下语句:

#import "C:/Program Files/Common Files/system/ado/msadox.dll"
#import "C:/Program Files/Common Files/system/ado/msado15.dll" rename("EOF","adoEOF")

关于这两条语句,需要进行特别说明:

由于该例程同时使用ADOX和ADO,需要同时引入msado15.dll和msadox.dll两个库。这两个库的名字空间是不同的,msado15.dll的名字空间是ADODB,msadox.dll的名字空间是ADOX。在使用ADO所属的名字空间的变量,函数时,在前面加上ADODB::,在使用ADOX所属的名字空间的变量,函数时,在前面加上ADOX::。

另外,一般ADOX和ADO分开操作。您也可以在ADOX操作部分使用using namespace ADOX::,而在ADO操作部分使用using namespace ADO:,以区分名字空间。这样,您就不必再使用ADOX::和ADODB::了。

rename(“EOF”,”adoEOF”) //重命名EOF是必要的,因为典型的VC应用都已经定义了EOF作为常数-1,为了避免冲突,将ADO中的EOF重命名为adoEOF。

#import中有一个属性为no_namespace,这是告诉编译器该类不在一个单独的名字空间中,使用no_namespace意味着你不需要在初始化变量的时候引用名字空间。当然如果在您的应用中需要导入多个类型库的话,不要使用no_namespace,以免引起名字冲突。

再通俗一点讲,就是只导入一个类型库的话,可以在#import语句中加入no_namespace属性,您的程序可以直接使用这个类型库的名字空间的内容,而不必使用using namespace XXX;或XXX::,这是因为no_namespace属性告诉编译器该类型库不再名字空间,而是在全局空间上工作;如果您导入几个类型库,而这几个类型库之间没有定义冲突,您也可以在使用no_namespace属性;但如果两个类型库中有定义冲突,就不能使用no_namespace属性,如果使用no_namespace属性,就会在全局空间产生定义冲突。

对于本例程,您可以把stdafx.h中的

#import "C:/Program Files/Common Files/system/ado/msadox.dll" 
#import "C:/Program Files/Common Files/system/ado/msado15.dll" rename("EOF","adoEOF")

改为

#import "C:/Program Files/Common Files/system/ado/msadox.dll" 
#import "C:/Program Files/Common Files/system/ado/msado15.dll" no_namespace  rename("EOF","adoEOF")

这样改动后,void CCREATE_DB_AND_TABLEDlg::OnBtnCreateTable()中的ADODB::需要完全省略掉。

当然,您也可以把这两行改为:

#import "C:/Program Files/Common Files/system/ado/msadox.dll" no_namespace
#import "C:/Program Files/Common Files/system/ado/msado15.dll" rename("EOF","adoEOF")

但这样改动后,void CCREATE_DB_AND_TABLEDlg::OnBtnCreateTable()中的ADOX::需要完全省略掉。由于ADOX和ADO有定义冲突,也就是说,msado15.dll和msadox.dll有相同的定义部分,所以在一个程序中,不允许同时使用no_namespace。

五、常见的 SQL 语句

请参考博主的这篇文章:https://blog.csdn.net/qq_41291253/article/details/103519714

六、使用_ConnectionPtr接口开发ACCESS数据库

ADO中最重要的对象有三个:Connection、Recordset和Command,分别表示连接对象、记录集对象和命令对象。三个对象对应的智能指针分别是:_ConnectionPtr、_RecordsetPtr、_CommandPtr。ADO使用_ConnectionPtr这个指针来操纵Connection对象,类似地,后面用到的_CommandPtr和_RecordsetPtr分别表示命令对象指针和记录集对象指针。

Connection对象是这三个对象的基础,它的主要作用是建立与数据库的连接,建立了与数据库的连接后,才能进行其它有关数据库的访问和操作。

Connection对象的用法:首先定义一个Connection类型的指针,然后调用CreateInstance()来创建一个连接对象的实例,再调用Open函数建立与数据源的连接。最后使用Execute()函数执行SQL语句创建表。

关于调用CreateInstance()来创建连接对象的实例,还需作一点说明。ADO库包含三个基本接口:_ConnectionPtr接口,_RecordsetPtr接口和_CommandPtr接口。其分别对应Connection对象(完成应用程序对数据源的访问连接),Recordset对象(将查询的结果以记录集的方式存储)和Command对象(对已连接的数据源进行命令操作)。

_ConnectionPtr m_pConnection;
_RecordsetPtr m_pRecordset;
_CommandPtr m_pCommand;

而这三个对象实例的创建,可以使用如下语句:

m_pConnection.CreateInstance(__uuidof(Connection));
//或者:
m_pConnection.CreateInstance(“ADODB.Connection”);

m_pRecordset.CreateInstance(__uuidof(Recordset));
//或者:
m_pRecordset.CreateInstance(“ADODB.Recordset”);

m_pCommand.CreateInstance(__uuidof(Command));
//或者:
m_pCommand.CreateInstance(“ADODB.Command”);

两种方法的作用完全相同,使用哪种方法,完全是您的个人爱好问题。

Connection 对象是 ADO 模型中的顶层对象,它代表与数据源之间的一个连接。其常用的方法见下表

Connection 对象的方法及说明
方法说明
Open()打开与数据源的连接
Excute()执行指定的查询、SQL 语句、存储过程或特定提供者的文本等内容
Close()关闭 Connection 对象,释放所有关联的系统资源
BeginTrans()启动新的事务
CommitTrans()保存所有更改并结束当前事务,也可启动新事务
RollBackTrans()取消当前事务中所做的任何更改并结束事务,也可以启动新事务

在编程过程中,还会经常用到 Connection 对象的 ConnectionString 属性(形参/parameter)、CursorLocation 属性和 State 属性。

(1)ConnectionString 属性

这个属性表示连接数据库的字符串。在进行连接前,通过该属性来配置建立一个与数据源连接的信息。ConnectionString 属性在连接关闭时是可读写的,在连接打开时是只读的。

连接字符串主要有两种,分别为使用 DSN 方法和不使用 DSN 的方法。

①使用 DSN 的连接方法,格式如下:

ConnectionString = "DSN = DSNName"; User ID = user; Password = password"
//DSN 是指定的 ODBC 数据源
//User ID是登录数据库的用户名
//Password是登录数据库的密码

②不使用 DSN 的连接方法,格式如下:

ConnectionString = "Provider = ProviderName;
                    Persist Security Info = true | false;
                    User ID = user;
                    Password = password;
                    Initial Catalog = DBName;
                    Data Source = server"
//Provider 为数据库提供程序,不同的数据库其值不同。常见的 Access 数据库、Oracle 数据库和 MS SQL 数据库的 Provider 值分别为Microsoft.Jet.OLEDB.4.0、MSDAORA和 SQLOLEDB
//Persist Security Info指明要不要安全信息验证
//Initial Catalog即为 DataBase,如果写为 DataBase 也是正确的
//Data Source指数据库

(2)CursorLocation 属性

这个属性是 Connection 对象和 Recordset 对象都具有的。该属性用来指定游标引擎的位置。该属性对于连接都是可读写的,对于关闭的记录集是可读写的,对于打开的记录集是只读的。该属性可以设置或返回以下某个常量(长整型值),如下表

CursorLocation 可设置的值
常量说明
adUSrNone没有使用游标服务(该常量已过时并且只为了向后兼容才出现)
adUseClient使用由本地游标库提供的客户端游标。本地游标服务通常允许使用的许多功能可能是驱动程序提供的游标无法使用的,因此使用该设置对于那些将要启用的功能是有好处的。adUseClient 具有向后兼容性,也支持同义的 adUseClientBatch
adUseServer默认值。使用数据提供者的或驱动程序提供的游标。这些游标有时非常灵活,对于其他用户对数据源所做的更改具有额外的敏感性。但是,Microsoft Client Cursor Provider(如已断开关联的记录集)的某些功能无法由服务器端游标模拟,通过该设置将无法使用这些功能

当 CursorLocation 用于客户端 Recordset 或 Connection 对象时,只能将其设置为 adUseClient。

(3)State 属性

该属性用于 Connection 对象、Command 对象和 Recordset 对象,用于描述对象的状态。该属性可以设置为下表所示的常量。

State 属性所能取的常量值
常量说明
adStateClosed对象关闭(默认值)
adStateOpen对象打开
adStateConnection对象正在连接
adStateExecuting对象正在执行一个命令
adStateFetchingRecordset 对象正在获取所需的行

Connection的 Open方法和 Execute方法原型间下面:

Open 方法在 Microsoft 文档中表述:https://docs.microsoft.com/zh-cn/office/client-developer/access/desktop-database-reference/open-method-ado-connection

Execute 方法在 Microsoft 文档中表述:https://docs.microsoft.com/zh-cn/office/vba/access/concepts/miscellaneous/execute-method-ado-connection

//Open方法的原型:

Open(_bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options)

//ConnectionString为连接字串,UserID是用户名,Password是登陆密码
//Options是连接选项,可以是如下几个常量:
//adModeUnknown 缺省,当前的许可权未设置
//adModeRead 只读
//adModeWrite 只写
//adModeReadWrite 可以读写
//adModeShareDenyRead 阻止其它Connection对象以读权限打开连接
//adModeShareDenyWrite 阻止其它Connection对象以写权限打开连接
//adModeShareExclusive 阻止其它Connection对象打开连接
//adModeShareDenyNone 阻止其它程序或对象以任何权限建立连接
//Execute原型:

Execute(_bstr_t CommandText, VARIANT* RecordsAffected, long Options)

//其中CommandText是命令字串,通常是SQL命令,
//参数RecordsAffected是操作完成后所影响的行数
//参数Options表示CommandText中内容的类型,可以取下列值之一:
//adCmdText 表明CommandText是文本命令
//adCmdTable 表明CommandText是一个表名
//adCmdProc 表明CommandText是一个存储过程
//adCmdUnknown 未知

例程ConnPtr_Open_Exe

打开VC++ 6.0,新建一个基于对话框的工程ConnPtr_Open_Exe。在对话框IDD_CONNPTR_OPEN_EXE_DIALOG中进行编辑:

使用三个Group Box分成三个部分,第一部分演示使用Execute()函数来执行INSERT INTO命令;第二部分演示使用Execute()函数来执行Update命令;第三部分演示使用Execute()函数来执行SELECT命令。其中,第一部分和第二部分不需要返回记录集,第三部分演示返回记录集显示结果。

该对话框几个控件如下:

 

控件名称

 

ID

 

用途

 

按钮

 

IDC_BTN_INSERT_INTO

 

执行INSERT INTO语句

 

按钮

 

IDC_BTN_UPDATE

 

执行Update语句

 

按钮

 

IDC_BTN_SELECT

 

执行SELECT语句

 

列表框

 

IDC_LIST1

 

显示SELECT语句执行结果

使用ClassWizard给列表框IDC_LIST1创建CListBox变量m_list1:

双击IDC_BTN_INSERT_INTO按钮,并编辑OnBtnInsertInto()函数如下:

void CConnPtr_Open_ExeDlg::OnBtnInsertInto() 
{
    _ConnectionPtr m_pConnection;
    _variant_t RecordsAffected;

    //链接数据库
    try
    {
       m_pConnection.CreateInstance(__uuidof(Connection));
       m_pConnection->Open("Provider = Microsoft.Jet.OLEDB.4.0;
            Data Source = Northwind.mdb", "", "", adModeUnknown);
    }
    catch(_com_error e)
    {
       CString errormessage;
       errormessage.Format("连接数据库失败!/r错误信息:%s", e.ErrorMessage());
       AfxMessageBox(errormessage);
       return;
    }

    //插入数据
    try
    {
       _bstr_t strCmd = "INSERT INTO Employees(EmployeeID, FirstName, LastName, HireDate, City, Country)
            VALUES(10, 'Mary', 'Williams', '15/4/1993 12:00:00', 'New York', 'USA')";
       m_pConnection->Execute(strCmd, &RecordsAffected, adCmdText);
    }
    catch(_com_error &e)
    {
       AfxMessageBox(e.Description());
    }

    //State:指定对象的当前状态。该属性是只读的,可选值如下
    //    adStateClosed:默认,指示对象是关闭的
    //    adStateOpen:指示对象是打开的
    if(m_pConnection->State)
       m_pConnection->Close();
}

双击IDC_BTN_UPDATE按钮,并编辑OnBtnUpdate()函数如下:

void CConnPtr_Open_ExeDlg::OnBtnUpdate() 
{
    _ConnectionPtr m_pConnection;
    _variant_t RecordsAffected;

    //建立数据库链接
    try
    {
       m_pConnection.CreateInstance(__uuidof(Connection));
       m_pConnection->Open("Provider = Microsoft.Jet.OLEDB.4.0;
        Data Source = Northwind.mdb", "", "", adModeUnknown);
    }

    catch(_com_error e)
    {
       CString errormessage;
       errormessage.Format("连接数据库失败!/r错误信息:%s", e.ErrorMessage());
       AfxMessageBox(errormessage);
       return;
    }

    //更新数据
    try
    {
       _bstr_t strCmd = "UPDATE Employees 
        SET FirstName = 'Bill', LastName = 'Clinton', HireDate = '25/11/1994 12:00:00', City = 'Los Angeles' 
        WHERE EmployeeID = 10";
       m_pConnection->Execute(strCmd, &RecordsAffected, adCmdText);
    }
    catch(_com_error &e)
    {
       AfxMessageBox(e.Description());
    }

    if(m_pConnection->State)
       m_pConnection->Close();  
}

双击IDC_BTN_SELECT按钮,并编辑OnBtnSelect()函数如下:

void CConnPtr_Open_ExeDlg::OnBtnSelect() 
{
    _ConnectionPtr m_pConnection;
    _variant_t RecordsAffected;
    _RecordsetPtr m_pRecordset;

    //链接数据库
    try
    {
       m_pConnection.CreateInstance(__uuidof(Connection));
       m_pConnection->Open("Provider = Microsoft.Jet.OLEDB.4.0;
        Data Source = Northwind.mdb", "", "", adModeUnknown);
    }
    catch(_com_error e)
    {
       CString errormessage;
       errormessage.Format("连接数据库失败!/r错误信息:%s", e.ErrorMessage());
       AfxMessageBox(errormessage);
       return;
    }

    //打开表中城市为伦敦的数据
    try
    {
       m_pRecordset.CreateInstance("ADODB.Recordset"); //为Recordset对象创建实例
       _bstr_t strCmd = "SELECT EmployeeID, FirstName, LastName, HireDate, City 
        FROM Employees 
        WHERE City = 'London'";
       m_pRecordset = m_pConnection->Execute(strCmd, &RecordsAffected, adCmdText);
    }
    catch(_com_error &e)
    {
       AfxMessageBox(e.Description());
    }

    //将数据显示到列表控件中
    _variant_t vEmployeeID, vFirstName, vLastName, vHireDate, vCity;

    try
    {
       while(!m_pRecordset->adoEOF)
       {
           vEmployeeID = m_pRecordset->GetCollect(_variant_t((long)0));
           //取得第1列的值,从0开始计数,你也可以直接列出列的名称,如下一行
           vFirstName = m_pRecordset->GetCollect("FirstName");
           vLastName = m_pRecordset->GetCollect("LastName");
           vHireDate = m_pRecordset->GetCollect("HireDate");
           vCity = m_pRecordset->GetCollect("City");

           CString strtemp;
           if(vEmployeeID.vt != VT_NULL)
              strtemp.Format("%d", vEmployeeID.lVal);

           if(vFirstName.vt != VT_NULL)
           {
              strtemp += "      ";
              strtemp += (LPCTSTR)(_bstr_t)vFirstName;
           }

           if(vLastName.vt != VT_NULL)
           {
              strtemp += "      ";
              strtemp += (LPCTSTR)(_bstr_t)vLastName;
           }

           if(vHireDate.vt != VT_NULL)
           {
              strtemp += "      ";
              strtemp += (LPCTSTR)(_bstr_t)vHireDate;
           }

           if(vCity.vt != VT_NULL)
           {
              strtemp += "      ";
              strtemp += (LPCTSTR)(_bstr_t)vCity;
           }

           m_list1.AddString(strtemp);
           m_list1.AddString("/n");
           m_pRecordset->MoveNext();
       }
    }
    catch(_com_error &e)
    {
       AfxMessageBox(e.Description());
    }

    m_pRecordset->Close();
    m_pRecordset = NULL;
    m_pConnection->Close();  
    m_pConnection = NULL;
}

在stdafx.h中加入如下语句:

#import "C:/Program Files/Common Files/system/ado/msado15.dll" rename("EOF","adoEOF")

在BOOL CConnPtr_Open_ExeApp::InitInstance()函数中加入:

AfxOleInit();

编译并运行该程序,观察运行结果。点击IDC_BTN_INSERT_INTO按钮,打开数据库Northwind.mdb的Employees表,你就会发现增加了一条记录:

10, Mary , Williams , 15/4/1993 12:00:00 , New York , USA

关闭数据库。

继续点击IDC_BTN_UPDATE按钮,打开数据库Northwind.mdb的Employees表,你就会发现第10条记录变为:

10, Bill , Clinton , 25/11/1994 12:00:00 , Los Angeles, USA

关闭数据库。

继续点击IDC_BTN_SELECT按钮,你就会发现列表框中会显示出City为London的记录,如下: 

 

EmployeeID

 

FirstName

 

LastName

 

HireDate

 

City

 

5

 

Steven

 

Buchanan

 

17/10/1993 12:00:00 

 

London

 

6

 

Michael

 

Suyama

 

17/10/1993 12:00:00 

 

London

 

7

 

Robert

 

King

 

2/1/1994 12:00:00 

 

London

 

9

 

Anne

 

Dodsworth

 

15/11/1994 12:00:00 

 

London

 该部分演示了如何使用_ConnectionPtr接口开发ACCESS数据库:先创建一个Connection对象实例,然后用Open方法打开一个库连接,最后使用Execute方法执行SQL语句进行其它有关数据库的访问和操作。接下来的第五部分将演示使用使用_RecordsetPtr接口开发ACCESS数据库。

七、使用_RecordsetPtr接口开发ACCESS数据库

_RecordsetPtr智能指针,它是专门为通过记录集操作数据库而设立的指针,通过该接口可以对数据库的表内的记录、字段等进行各种操作。

要搞清楚:数据库和ADO的记录集是两个不同的概念, 是存在于不同物理位置的两个存储空间。 记录集相当于是实际数据的一份拷贝。 正因为记录集是相对脱离数据库而存在的, 所以才存在后面将要介绍的Open方法中涉及的光标类型和锁定类型这两个问题。

Recordset 对象表示从数据源选择的一组记录的集合,主要方法如下表:

Recordset 对象的方法及说明
方法说明
Open()直接打开一个记录集,而不是作为执行命令或连接命令产生的记录集
Close()关闭记录集
MoveFirst()移动到记录集的第一条记录处
MoveLast()移动到记录集的最后一条记录处
MovePrevious()

移动到记录集中当前记录的前一条记录处

MoveNext()移动到记录集中当前记录的后一条记录处
Move()移动到指定的记录
AddNew()添加一条新纪录,在调用 Update()后,记录被添加到记录集
Delete()

删除记录集中的当前记录

GetCollect()得到指定字段的值
PutCollect()准备将指定字段的值写入记录集,调用 Update()进行更新
Update()将当前对记录集的改动保存到数据源中
CancelUpdate()取消 Update()更新前所做的动作
Requery()重新执行以前执行过的命令,重新获得记录集

_RecordsetPtr接口的使用方法:

先创建记录集对象:

_ConnectionPtr m_pRecordset;
m_pRecordset.CreateInstance(__uuidof(Recorset));

创建记录集对象后,只是为它分配内存空间,记录集中不含任何数据。记录集对象是用来获得数据库中的数据并对其操作的,所以还要打开记录集,从数据库中取得数据记录。可有多种方法打开记录集,下面只介绍最常用的Open方法:

为记录集对象分配了空间后就可以用Open函数打开记录集,该函数原型为:

HRESULT Recordset15::Open(
    const _variant_t& Source,             //Source是数据查询字符串。
    const _variant_t& ActiveConnection,   //ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象)。
    enum CursorTypeEnum CursorType,       //CursorType 光标类型,它是枚举CursorTypeEnum中的一个值。
    enum LockTypeEnum LockType,           //LockType 锁定类型 它是枚举LockTypeEnum中的一个值
    long Options)                         //Options 指定Source的类型

光标类型CursorType,可取如下值之一:

adOpenUnspecified = -1 //不作特别指定
adOpenForwardOnly = 0  //默认值,前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可节省资源,提高浏览速度,但诸如BookMark、RecordCount、AbsolutePosition、AbsolutePage都不能使用。
adOpenKeyset = 1       //键集游标,采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。
adOpenDynamic = 2      //动态光标,所有数据库的操作都会立即在用户记录集上反应出来。
adOpenStatic = 3       //静态游标。它为记录产生一个静态备份,其他用户的新增、删除、更新操作对你的记录集来说是不可见的。

LockType锁定类型,它可以是以下值之一,请看如下枚举结构

enum LockTypeEnum
{
    adLockUnspecified = -1,   //未指定
    adLockReadOnly = 1,       //只读记录集,默认值。无法更改数据。
    adLockPessimistic = 2,    //悲观锁定方式。只有在调用Update方法时才锁定记录。这是最安全的锁定机制
    adLockOptimistc = 3,      //乐观锁定方式,只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等操作
    adLockBatchOptimistic = 4 //乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。
}

关于光标和锁定类型,对于一般用户,建议您只作简单了解,后面将进一步进行解说。

Options可以取如下值之一:

adCmdText: 表明CommandText是文本命令。
adCmdTable:表明CommandText是一个表名。
adCmdProc:表明CommandText是一个存储过程。
adCmdUnknown:未知。

例如:假设m_pConnection是我们已经建立好的连接,我们使用_RecordsetPtr接口的Open方法打开Employees表的记录集的语句如下:

m_pRecordset->Open(“SELECT * FROM Employees”,
           _variant_t((IDispatch*)m_pConnection,true),
           adOpenStatic,
           adLockOptimistic,
           adCmdText);

例程RecordsetPtr演示使用_RecordsetPtr指针通过记录集操作数据库。

打开VC++ 6.0,新建一个基于对话框的工程RecordsetPtr。在对话框IDD_RECORDSETPTR_DIALOG中进行编辑:

使用三个Group Box分成四个部分,第一部分演示如何读取数据库数据;第二部分演示如何修改数据库;第三部分演示如何向数据库中插入数据;第四部分演示如何删除数据库中的数据。

使用ClassWizard给列表框IDC_LIST1创建CListBox变量m_list1:

双击IDC_BTN_READREC按钮,并编辑OnBtnReadrec()函数如下:

void CRecordsetPtrDlg::OnBtnReadrec() 
{
    _ConnectionPtr m_pConnection;  
    _RecordsetPtr m_pRecordset;

    //链接数据库
    try
    {
       m_pConnection.CreateInstance(__uuidof(Connection));
       m_pConnection->Open("Provider = Microsoft.Jet.OLEDB.4.0;
        Data Source = Northwind.mdb", "", "", adModeUnknown);
    }
    catch(_com_error e)
    {
       CString errormessage;
       errormessage.Format("连接数据库失败!/r错误信息:%s", e.ErrorMessage());
       AfxMessageBox(errormessage);
       return;
    }

    //打开数据表
    try
    {
       m_pRecordset.CreateInstance("ADODB.Recordset");
       m_pRecordset->Open("SELECT EmployeeID, FirstName, LastName, HireDate, City 
        FROM Employees 
        WHERE City = 'London'",
           _variant_t((IDispatch*)m_pConnection, true),
           adOpenStatic,
           adLockOptimistic,
           adCmdText);
    }
    catch(_com_error &e)
    {
       AfxMessageBox(e.Description());
    }

    //列表中显示数据
    _variant_t vEmployeeID, vFirstName, vLastName, vHireDate, vCity;
    try
    {
       while(!m_pRecordset->adoEOF)
       {
           vEmployeeID = m_pRecordset->GetCollect(_variant_t((long)0));
           //取得第1列的值,从0开始计数,你也可以直接列出列的名称,如下一行
           vFirstName = m_pRecordset->GetCollect("FirstName");
           vLastName = m_pRecordset->GetCollect("LastName");
           vHireDate = m_pRecordset->GetCollect("HireDate");
           vCity = m_pRecordset->GetCollect("City");

           CString strtemp;
           if(vEmployeeID.vt != VT_NULL)
              strtemp.Format("%d", vEmployeeID.lVal);

           if(vFirstName.vt != VT_NULL)
           {
              strtemp += "      ";
              strtemp += (LPCTSTR)(_bstr_t)vFirstName;
           }

           if(vLastName.vt != VT_NULL)
           {
              strtemp += "      ";
              strtemp += (LPCTSTR)(_bstr_t)vLastName;
           }

           if(vHireDate.vt != VT_NULL)
           {
              strtemp += "      ";
              strtemp += (LPCTSTR)(_bstr_t)vHireDate;
           }

           if(vCity.vt != VT_NULL)
           {
              strtemp += "      ";
              strtemp += (LPCTSTR)(_bstr_t)vCity;
           }

           m_list1.AddString(strtemp);          
           m_list1.AddString("/n");
           m_pRecordset->MoveNext();
       }       
    }
    catch(_com_error &e)
    {
       AfxMessageBox(e.Description());
    }

    m_pRecordset->Close();
    m_pRecordset = NULL;
    m_pConnection->Close();  
    m_pConnection = NULL;  
}

该段代码演示了如何读取数据库表内的数据。其原理是如果没有遇到表结束标志adoEOF,则用GetCollect(字段名)来获取当前记录指针所指的字段值,然后再用MoveNext()方法移动到下一条记录位置。

双击IDC_BTN_CHANGE按钮,并编辑OnBtnChange()函数如下:

void CRecordsetPtrDlg::OnBtnChange() 
{
    _ConnectionPtr m_pConnection;    
    _RecordsetPtr m_pRecordset;

    //链接数据库
    try
    {
       m_pConnection.CreateInstance(__uuidof(Connection));
       m_pConnection->Open("Provider = Microsoft.Jet.OLEDB.4.0;
        Data Source = Northwind.mdb", "", "", adModeUnknown);
    }
    catch(_com_error e)
    {
       CString errormessage;
       errormessage.Format("连接数据库失败!/r错误信息:%s", e.ErrorMessage());
       AfxMessageBox(errormessage);
       return;
    }

    //打开数据表
    try
    {
       m_pRecordset.CreateInstance("ADODB.Recordset");
       m_pRecordset->Open("SELECT EmployeeID, FirstName, LastName, HireDate, City 
        FROM Employees 
        WHERE (City='London') AND (EmployeeID=6)",
           _variant_t((IDispatch*)m_pConnection,true),
           adOpenStatic,
           adLockOptimistic,
           adCmdText);
    }
    catch(_com_error &e)
    {
       AfxMessageBox(e.Description());
    }

    try
    {
       while(!m_pRecordset->adoEOF)
       {
           m_pRecordset->PutCollect("LastName", _variant_t("Jackson"));
           m_pRecordset->MoveNext();
       }
       m_pRecordset->Update();
    }
    catch(_com_error* e)
    {
       AfxMessageBox(e->ErrorMessage());
    }
   
    m_pRecordset->Close();
    m_pRecordset = NULL;
    m_pConnection->Close();  
    m_pConnection = NULL;
}

该段代码演示了如何修改记录中的字段值:

将记录指针移动到要修改记录的位置处,直接用PutCollect(字段名,值)将新值写入并Update()更新到数据库即可。

移动记录指针可以通过MoveFirst()方法移动到第一条记录,MoveLast()方法移动到最后一条记录,MovePrevious()方法移动到当前记录的前一条记录,MoveNext()方法移动到当前记录的下一条记录。也可以使用Move(记录号)移动记录指针到需要位置。注意:Move()方法是相对于当前记录来移动指针位置的。正值向后移动,负值向前移动。如Move(3),当前记录是3时,它将从记录3开始往后再移动3条记录位置。关于移动记录指针后面将会用到。

双击IDC_BTN_NEW按钮,并编辑OnBtnNew()函数如下:

void CRecordsetPtrDlg::OnBtnNew() 
{
    _ConnectionPtr m_pConnection;    
    _RecordsetPtr m_pRecordset;

    try
    {
       m_pConnection.CreateInstance(__uuidof(Connection));
       m_pConnection->Open("Provider = Microsoft.Jet.OLEDB.4.0;
        Data Source = Northwind.mdb", "", "", adModeUnknown);
    }
    catch(_com_error e)
    {
       CString errormessage;
       errormessage.Format("连接数据库失败!/r错误信息:%s", e.ErrorMessage());
       AfxMessageBox(errormessage);
       return;
    }

    try
    {
       m_pRecordset.CreateInstance("ADODB.Recordset");
       m_pRecordset->Open("SELECT * FROM Employees",
           _variant_t((IDispatch*)m_pConnection,true),
           adOpenStatic,
           adLockOptimistic,
           adCmdText);
    }
    catch(_com_error &e)
    {
       AfxMessageBox(e.Description());
    }

    try
    {
       m_pRecordset->MoveLast();
       m_pRecordset->AddNew();
       m_pRecordset->PutCollect("EmployeeID", _variant_t((long)10));
       m_pRecordset->PutCollect("FirstName", _variant_t("Mary"));
       m_pRecordset->PutCollect("LastName", _variant_t("Williams"));
       m_pRecordset->PutCollect("HireDate", _variant_t("15/4/1993 12:00:00"));
       m_pRecordset->PutCollect("City", _variant_t("New York"));
       m_pRecordset->PutCollect("Country", _variant_t("USA"));
    }   
    catch(_com_error *e)
    {
       AfxMessageBox(e->ErrorMessage());
    }
   
    m_pRecordset->Update();
    m_pRecordset->Close();
    m_pRecordset = NULL;
    m_pConnection->Close();  
    m_pConnection = NULL;
}

该段代码演示如何插入记录:

先用AddNew()方法新增一个空记录,再用PutCollect(字段名,值)输入每个字段的值,最后用Update()更新到数据库即可。

双击IDC_BTN_DELETE按钮,并编辑OnBtnDelete()函数如下:

void CRecordsetPtrDlg::OnBtnDelete() 
{
    _ConnectionPtr m_pConnection;
    _RecordsetPtr m_pRecordset;

    try
    {
       m_pConnection.CreateInstance(__uuidof(Connection));
       m_pConnection->Open("Provider = Microsoft.Jet.OLEDB.4.0;
        Data Source = Northwind.mdb", "", "", adModeUnknown);
    }
    catch(_com_error e)
    {
       CString errormessage;
       errormessage.Format("连接数据库失败!/r错误信息:%s", e.ErrorMessage());
       AfxMessageBox(errormessage);
       return;
    }

    try
    {
       m_pRecordset.CreateInstance("ADODB.Recordset");
       m_pRecordset->Open("SELECT * FROM Employees",
           _variant_t((IDispatch*)m_pConnection,true),
           adOpenStatic,
           adLockOptimistic,
           adCmdText);
    }
    catch(_com_error &e)
    {
       AfxMessageBox(e.Description());
    }

    try
    {
       //假设删除第10条记录
       m_pRecordset->MoveFirst();
       m_pRecordset->Move(9);
       m_pRecordset->Delete(adAffectCurrent);
       //参数adAffectCurrent为删除当前记录
       m_pRecordset->Update();
    }
    catch(_com_error *e)
    {
       AfxMessageBox(e->ErrorMessage());
    }

    m_pRecordset->Close();
    m_pRecordset = NULL;
    m_pConnection->Close();  
    m_pConnection = NULL;  
}

该段代码演示了如何删除记录:

先将记录指针移动到要删除的记录的位置,直接用Delete()方法删除它,并用Update()来更新数据库即可。

使用记录集操作完毕后要关闭记录集:

直接用Close()方法关闭记录集并赋予其空值。代码如下:

m_pRecordset->Close();
m_pRecordset = NULL;
m_pConnection->Close();
m_pConnection = NULL;

八、Connection对数据库Recordset对数据库操作区别

使用_RecordsetPtr接口开发ACCESS数据库就介绍到这里,它可以更加灵活地操作数据库。当然,您还可以使用_CommandPtr接口开发ACCESS数据库,它提供了一个简单的方法来执行返回记录集的SQL语句。本文不讲解_CommandPtr接口。

用心的读者您可能已经发现,使用Connection对象的Execute方法可以完成数据库的操作,使用Recordset也可以完成同样功能的数据库操作。我们应该采用哪种方法呢?另外,Connection对象的Execute方法返回一个记录集,Recordset的Open方法也打开一个记录集,二者有什么区别呢?下面我们将进行说明。 

前面已经讲过,数据库和ADO的记录集是两个不同的概念, 是存在于不同物理位置的两个存储空间. 记录集相当于是实际数据的一份拷贝. 正因为记录集是相对脱离数据库而存在的, 所以才光标类型和锁定类型这两个问题。

在记录集被创建以后,数据提供者负责在数据库和记录集之间进行侦测,记录集的类型決定了提供者在多大程度上确保数据库与记录集之间的一致性,这通常是由光标类型決定的, 而同时也決定了提供者采取什么方式来确保用戶在更新数据库时, 本次更新的完整性, 这是由锁类型決定的。

比方说, 一个用户正在对一个记录集做一次更新,并试图将此更新应用到数据库中。因为通过记录集来更新数据是分两步完成的, 第一步是修改记录集的內容, 第二步才是将修改通过数据提供者更新到数据库中, 两步之间存在时间上的细微差別. 所以, 他可能会遇到一个问题, 即他的第一步到第二步的操作, 是否会因为这期间同时遇到其他用户的操作而失败。

对数据库的部分记录进行锁定解決了这个问题, 而不同类型的锁, 解決问题的方式也不一样。 有的在第一步开始时就锁定数据库, 有的在第二步开始时才锁定数据库。锁定占用了数据库资源, 而使得记录集在对于使用者的更新操作方面更加可靠。

综上所述, 可以得出一个结论: 锁是由于记录集的存在而存在的, 沒有记录集, 或者說沒有可供更新的记录集,锁就沒有存在的意义。 

从效率上考虑,锁会降低并发性, 尤其是当同时连接数据库的用户增多时. 不采用记录集来更新数据库总是更好的选择,虽然使用起来会更麻烦一些。

选择与数据库联系得较少的记录集总是能提高效率, 因为提供者无需做很多的侦测工作. 比如, 动态的记录集总是实时地将数据库的状态反映到记录集中, 这很有用, 但也会耗费无提供者更多的精力. 如果无需更新数据, 只读的记录集是与数据库联系得最少的一种, 选择使用这种记录集来代替其它类型在效率上通常会有很明显的提高。

Connection对象的Execute方法只能得到一个只能前移的、只读的记录集。

使用RecordAffected参数找出有多少条记录受到影响。一旦Execute命令执行完毕,受SQL命令影响的记录数就返回到RecordAffected参数中。

Connection的Execute方法不支持任何锁定类型,这就是和Recordset的Open方法的主要区别所在,所以用Recordset的Open方法将更灵活,而Connection的Execute方法更简洁高效。

Execute是Connection对象对数据库操作的一个方法,你可以把它理解为执行SQL语句,也就是说Execute是一个操作方法,而不是像open方法返回一个记录集,然后再对记录集操作。

尽量使用SQL语句进行数据库操作。尽管采用Recordset对象来更新数据是非常方便的,但是它的开销也更大,所以如果可能的话,就要采用SQL语句来更新数据。

Execute返回的记录集是只读的, 不可以更新, 所以也就不存在锁。这个记录集不具备Recordset可以改变光标类型和锁定类型的功能,它只能返回一个只能前移的、只读的记录集。

尽管很多开发人员都习惯采用“SELECT * FROM 表名”的模式进行查询,但是为了提高系统的效率,如果你只需要其中某几个字段值的话,最好把这几个字段直接写出来,同时需要限定返回记录集的范围(通过WHERE子句进行限定)。

本文参考:

https://blog.csdn.net/suhuaiqiang_janlay/article/details/5943659

https://blog.csdn.net/suhuaiqiang_janlay/article/details/5943688

https://blog.csdn.net/suhuaiqiang_janlay/article/details/5943692

https://blog.csdn.net/suhuaiqiang_janlay/article/details/5943701

https://blog.csdn.net/suhuaiqiang_janlay/article/details/5943723

https://blog.csdn.net/suhuaiqiang_janlay/article/details/5943727

[马石安、魏文平]Visual C++程序设计与应用教程(第三版)/ 清华大学出版社

[明日科技]Visual C++从入门到精通(第三版)/ 清华大学出版社

  • 2
    点赞
  • 0
    评论
  • 9
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:精致技术 设计师:CSDN官方博客 返回首页

打赏作者

一丁_

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值