【SQL开源代码栏目提醒】:文章导读:在新的一年中,各位网友都进入紧张的学习或是工作阶段。
网学会员整理了SQL开源代码-更新数据源 - 财经管理的相关内容供大家参考,祝大家在新的一年里工作和学习顺利!
更新数据源 本章将以上一章讨论的有关通过ODBC开放数据库连接访问数据库的知识为基础并且将通过相同的机制更新Northwind Traders数据库。
本章主要内容 ● 数据库事务 ● 如何使用记录集对象更新数据库 ● 如何在更新操作中将数据从记录集传输到数据库 ● 如何更新表中的现有行 ● 如何添加新行到表中 21.1 更新操作 当编写的
代码仅仅用于查看一个数据库的信息时唯一的问题是是否获得了访问数据的授权。
只要这个数据库具有正确的访问保护其中的数据就是安全的。
一旦开始编写更新数据库的
代码那就是另外一回事了。
因为将要修改数据库的内容所以这样的修改可能会破坏数据库的完整性使表中的内容失去意义甚至使数据库无法使用。
在实际的数据库中使用编写的
代码之前始终要利用一个测试数据库对
代码进行正确的测试。
数据库更新通常包括更新现有表一行中的一个或多个字段如修改订单数量或者添加新行—— 在Northwind数据库的上下文中新行也许是新订单。
下面将开发这两种情况的示例但是首先要考虑内在的问题。
在多用户数据库的上下文中数据库更新操作所产生的大多数问题变得很明显。
如果不正确地控制更新过程几个用户的并行访问有可能产生两种问题。
如果一个人获准检索一个记录而与此同时正在对该记录进行更新操作那么就会出现第一种问题。
这个仅仅想读取数据的人最后有可能得到更新前的旧数据甚至得到一些字段包含旧数据、一些字段包含新数据的混合物。
第二种问题出现在并行更新的情况下一个人开始更新一个记录时对该记录的另一个更新已经在进行中。
如果这时只涉及表中的单个记录那么更新有可能失败。
如果涉及几个表中的记录那么数据库中的数据最后将处于一种不一致的状态。
在分析如何处理这些
问题之前首先观察一下对记录集的更新操作是如何进行的。
第 章 21 第21章 更新数据源 1001 21.1.1 CRecordset更新操作 第20章讨论了在记录集对象的DoFieldExchange成员中RFX_函数调用如何从数据库表中的选定字段检索数据然后把数据传输到记录集对象的数据成员。
在更新数据库表中的字段或者添加全新的行时也要使用这些函数。
在CRecordset类中有5个支持更新操作的成员函数见表21-1。
表 21-1 Edit 调用这个函数将开始更新现有的记录。
如果不能更新表那么这个函数将抛出CDBException异常如果出现了内存不足的情况它将抛出CMemoryException异常 AddNew 调用这个函数将开始添加全新的记录。
如果不能把新记录附加到表中这个函数将抛出CDBException异常 Update 调用这个函数将完成现有记录的更新或者新记录的添加。
如果不能更新单个记录或者出现了错误这个函数将抛出CDBException异常 Delete 通过创建和执行
SQL DELETE语句删除当前记录。
如果出现了错误—— 如数据库是只读的那么这个函数将抛出CDBException异常。
在进行了Delete操作以后记录集的所有数据成员都将被设置为空值—— 相当于没有值集。
在对记录集对象执行其他操作之前必须移动到新记录 CancelUpdate 取消修改现有记录或者添加新记录的未完成操作 这些函数都没有参数。
其中前4个函数可以抛出异常所以如果在有错误出现时不希望程序突然结束就应当把函数调用放在try
代码块中并且应当添加一个catch
代码块。
如果要删除记录集对象的当前记录只需调用它的Delete成员。
然后在使用上述函数之前必须将记录集滚动到一个新位置因为在调用Delete成员以后记录集对象的数据成员的值将是无效的。
图21-1说明了在更新现有记录或者添加新记录时事件的基本顺序。
1 调用记录集对象的Edit成员 -将记录集字段数据成员的当前值保存到缓冲区中。
2 将字段数据成员设置为新值。
3 调用记录集对象的Update成员 -通过与保存值进行比较检查已修改的字段。
-创建和执行
SQL INSERT语句更新已修改字段的DB。
-丢弃包含字段的旧保存值的缓冲区。
1 调用记录集对象的AddNew成员 -将记录集字段数据成员的当前值保存到缓冲区中。
-将记录集字段数据成员的当前值设置为PSEUDO_NULL。
2 设置新的字段数据成员值。
3 调用记录集对象的Update成员 -检查非NULL字段。
-创建和执行
SQL INSERT语句更新非NULL字段的DB。
-由缓冲区恢复旧保存值。
更新现有记录 更新新记录 图 21-1 Visual C 2008入门经典 1002 在调用记录集的AddNew成员开始添加新记录到表中时这个函数将把记录集对象中对应于字段值的所有数据成员的值保存到缓冲区中然后将这些数据成员设置为PSEUDO_NULL。
在指针中这并不表示零或者空。
它是一个表明数据成员还未设置的值。
在调用Update完成记录的添加时记录集数据成员的值将恢复到调用AddNew之前的原始值。
要使记录集包含新记录的值必须调用记录集对象的Requery成员。
如果操作成功这个函数将返回TRUE这是一个MFC BOOL类型的值。
如果想获得数据的不同视图以便在检索记录时将不同的
SQL命令或不同的过滤器用于这些记录也要调用Requery。
数据在记录集数据成员和数据库之间传输时始终要使用记录集对象的DoFieldExchange成员所以RFX_函数提供了双重功能—— 将数据写入数据库以及从数据库读取数据。
1. 检查操作是否合法 应该确认希望对记录集对象执行的操作是否合法。
对于只读记录集而言非常容易忘记验证
工作—— 即忘记了重新设置Northwind.mdb文件的只读属性。
如果希望更新只读表就会抛出异常而如果验证了其操作是否合法就可以完全避免这种情况。
使用异常捕获非预期错误的效率很低通常难度也很大。
较好的办法是尽可能事先进行检查比如本案例就是这样。
这样异常处理
代码就可以真正用于异常情况。
如果可以修改表中由记录集对象表示的记录那么CRecordset的CanUpdate成员将返回TRUE。
在添加新记录时可以事先调用CRecordset的CanAppend成员进行检查。
如果允许在表中添加新记录这个函数将返回TRUE。
2. 记录锁定 记录锁定防止其他用户在表行被更新期间访问锁定的记录。
更新期间锁定记录的时间由记录集对象中设置的锁定模式确定。
在CRecordset中定义了两种锁定模式分别称为乐观锁定模式和悲观锁定模式见表21-2。
表 21-2 CRecordset::optimistic 在乐观锁定模式中只在执行Update成员函数时锁定记录。
这大大缩短了数据库的其他用户不能访问记录的时间。
如果编辑操作需要很长时间则悲观锁定模式通常不切合实际因为其他用户可能需要访问数据库。
标准解决方法是使用乐观锁定模式并引入某种冲突解决机制 CRecordset::pessimistic 在悲观锁定模式中一调用Edit函数就锁定记录在调用Update函数或者终止更新操作之前记录将保持锁定状态其他用户无法访问记录。
在准备进行交互式更新时这显然将严重影响性能但是为了维护数据的完整性这种模式在许多情况下是必要的 记录集对象的默认模式是乐观锁定模式所以只有在需要悲观锁定模式时才需要设置它。
要设置悲观锁定模式需要调用参数为CRecordset::pessi
mistic的记录集对象的SetLockingMode成员。
当然也可以调用参数为CRecordset::optimistic的SetLockingMode函数然后对它进行重置。
第21章 更新数据源 1003 21.1.2 事务 在数据库上下文中事务的概念是为了在必要时安全地取消操作。
事务把对数据库的一系列定义明确地更改组合成单个操作如果出现错误那么在事务完成之前的任何时间点可以撤消或者回滚所有更改。
显然如果一个更新在部分完成时失败例如由于硬件问题而失败那么它对数据库的完整性可能会有灾难性的影响。
事务不仅仅是对单个表的更新。
它可以对数据库进行非常复杂的操作包括对多个表进行一系列的更改而且可能需要很长的时间才能完成。
在这些情况下如果要保证数据库的完整性实际上必须做到支持事务。
利用基于事务的操作数据库系统可以管理事务的处理记录恢复信息这样在出现问题时就可以撤消事务对数据所做的所有操作。
事务事件记录在磁盘上的日志文件中因此即使计算机断电系统也可以通过读取该日志中的条目进行恢复。
使数据库操作基于事务以后就可以防止数据库在处理期间出现错误。
通常事务处理将根据需要在处理过程中锁定记录它还保证其他数据库用户在访问已经由事务更改的数据时能够马上看到所做的修改。
大型
计算机上的大部分大型商业数据库
系统都支持事务但是对于在PC上运行的数据库系统来说情况并非始终如此。
尽管这样MFC中的CDatabase类却支持事务碰巧Microsoft ODBC又支持Access数据库所以可以对Northwind数据库试验一下事务处理。
CDatabase事务操作 事务是通过CDatabase类对象的成员来管理的该对象提供到数据库的连接。
对于给定的连接要确定是否支持事务需要调用CDatabase对象的CanTransact成员。
如果支持事务这个函数将返回TRUE。
CDatabase对象还有一个CanUpdate成员如果数据源是只读的它将返回FALSE不过这种情况很少。
CDatabase对象有3个成员函数参与事务处理见表21-3。
表 21-3 BeginTrans 在数据库上启动一个事务。
在调用CommitTrans或Rollback之前后续的所有记录集操作都是这个事务的一部分。
如果事务启动成功这个函数将返回TRUE CommitTrans 提交事务完成作为事务一部分的所有记录集操作。
如果出现错误这个函数将返回FALSE在这种情况下数据源的状态是不确定的 Rollback 回滚调用BeginTrans函数以来执行的所有记录集操作并将数据源恢复到调用BeginTrans函数时的状态 事务中事件的顺序非常简单 ● 调用BeginTrans函数启动事务。
● 根据需要调用记录集的Edit、Update、AddNew函数。
● 调用CommitTrans函数完成事务。
在事务的范围之外调用Update时将对记录集执行Edit或AddNew操作。
在事务的范围内调用CDatabase对象的CommitTrans成员之前这些操作是不执行的。
如果需要在调用BeginTrans之后结束事务只需调用Rollback函数。
Visual C 2008入门经典 1004 CommitTrans和Rollback的执行可能会带来问题—— 例如记录集中正在被操作的位置可能丢失所以需要在程序中采取某种措施以便在完成或终止事务后恢复记录指针。
CDatabase对象中有两个成员可以提供帮助。
在调用CommitTrans之后需要调用CDatabase的成员GetCursorCommitBehavior在调用Rollback之后需要调用GetCursorRollbackBehavior。
这两个函数将返回下列3个int型数值之一它们表明应当采取的动作见表21-4。
表 21-4
SQL_CB_PRESERVE 记录集与数据源的连接不受提交或回滚操作的影响所以不用采取任何动作
SQL_CB_CLOSE 需要调用记录集对象的Requery 恢复记录集中的当前位置
SQL_CB_DELETE 必须通过调用记录集对象的Close 成员关闭记录集如果必要的话必须重新打开记录集 实际使用事务时还有其他一些问题因为使用的特定驱动程序可能影响打开记录集的时间。
对于一些驱动程序来说必须在调用BeginTrans之前打开记录集。
对于另一些驱动程序来说除非在调用BeginTrans之后打开记录集否则Rollback将不进行操作。
Microsoft Access ODBC驱动程序就是例证。
在应用程序中使用事务之前需要了解将要使用的特定驱动程序。
21.2 简单的更新示例 现在让我们通过一个非常简单的示例获得一些实际的更新操作经验。
该示例最初省略了本章迄今为止所讨论的大多数功能但随后将以之为基础应用某些已经学过的技术。
可以使用上一章用过的MFC Application Wizard以最低限度的工作量创建更新数据库表的应用程序。
我们将要创建的程序可以更新Order Details表中的某些字段。
使用MFC Application模板创建一个名为DBSimpleUpdate的项目。
像在上一章所做的那样选择使用以ODBC作为Client type选项、不带文件支持选项的数据库视图。
我们仍将通过ODBC使用Northwind数据库但这次应该选择dynaset作为记录集类型。
在多用户的环境中我们的程序在访问动态集时只要对记录进行了修改动态集就会自动更新。
这样可以确保应用程序中获得的数据始终是最新的。
对于要修改现有记录或添加新记录的操作而言应该选择dynaset作为记录集类型。
因为需要更新数据库所以必须将记录集映射为单个数据库表。
MFC中的数据库类不支持需要连接两个或多个表的记录集的更新。
如图21-2所示选择Order Details表作为默认的记录集。
如果在这里选择多个表则记录集的更新操作是禁止的因为那样将使记录集自动具有只读属性。
该数据库类只支持对连接多个表的记录集进行只读访问而不支持对其进行更新操作。
如图21-3所示可以将视图和记录集类的名称以及相关文件的名称修改成与所处理的表匹配。
第21章 更新数据源 1005 图 21-2 图 21-3 现在需要做的就是单击Finish按钮然后定制对话框资源来做任何想做的事情。
定制应用程序 Order Details表包含5列Order ID、Product ID、Unit Price、Quantity和Discount。
如果显示Class View并查看COrderDetailsSet类的成员那么将看到对应这5列的数据成员。
在对应该记录集的对话框上需要为每个成员安排一个静态文本控件和一个编辑控件。
此处排列的这些控件如图21-4所示读者可以以自己喜欢的方式布置它们。
像在上一章所做的那样给编辑控件分配与字段名匹配的ID例如最后一个编辑控件的ID应该是IDC_DISCOUNT。
编辑控件的默认样式集允许键盘输入但基于用户希望限制可以更改的记录集字段这样的假设应该使用Properties窗口中的样式选项卡将前3个编辑控件设定为只读属性。
只读控件中显示的值可以在程序中设定但不能从键盘上向这样的控件中输入数值。
为了一次将这3个控件全部设定为只读属性可以按住Ctrl键依次选中3个控件然Visual C 2008入门经典 1006 后右击显示出弹出菜单并选择Properties。
此后在Properties窗口中设定的任何属性都将同时应用于所有3个控件。
在上面显示的对话框中将只能为Quantity和Discount字段输入数据。
图 21-4 还需要做的唯一一件事情是使编辑控件与对应的记录集的数据成员联系起来。
正如在上一章所看到的那样只需在记录集视图类COrderDetailsView的DoDataExchange函数中为记录集中的每个数据字段添加DDX_函数调用即可。
相应的
代码如下 void COrderDetailsView::DoDataExchangeCDataExchange pDX CRecordView::DoDataExchangepDX DDX_FieldTextpDX IDC_ORDERID m_pSet-gtm_OrderID m_pSet DDX_FieldTextpDX IDC_PRODUCTID m_pSet-gtm_ProductID m_pSet DDX_FieldTextpDX IDC_UNITPRICE m_pSet-gtm_UnitPrice m_pSet DDX_FieldTextpDX IDC_QUANTITY m_pSet-gtm_Quantity m_pSet DDX_FieldTextpDX IDC_DISCOUNT m_pSet-gtm_Discount m_pSet 做完这件事之后就完成了更新Order Details表的程序。
如果已经正确地设置了这些控件并且没有忘记使COrderDetailsSet类中GetDefaultConnect函数的定义前面出现的error指令以注释的形式存在那么该
程序应该立刻就能编译。
error指令的作用是确保考虑连接到数据库之后的安全问题。
当该程序执行时将能够使用工具栏按钮在表中这些行中间移动。
如果在Quantity或Discount的编辑控件中输入某订单的数据则该记录集将在向前或向后移动时更新。
该应用程序的窗口如图21-5所示。
在图21-5中可以看到在ID为10248的订单中ID为72的产品的数量和折扣值已经被修改为两个不太可能的数值。
图 21-5 第21章 更新数据源 1007 示例说明 当为了移动到另一个记录而单击某个工具栏按钮时默认基类CRecordView提供的处理程序OnMove将被调用。
该函数在调用COrderDetailsSet中继承的CRecordset类的Move成员移动到记录集中新记录之前先写出已经输入该记录集的任何修改。
记住这里有两级数据交换在进行。
在COrderDetailsSet类的DoFieldExchange成员中调用的RFX_函数在数据库中记录集的行与该类的数据成员之间传输数据而在COrderDetailsView类的DoDataExchange成员中调用的DDX_函数在编辑控件与COrderDetailsSet类的数据成员之间传输数据。
当在编辑控件中修改数值时新数据被传输到记录集对象的适当数据成员中。
单击工具栏按钮移动到下一条记录时新数据将由DoFieldExchange函数写出到数据库中。
该示例就其本身而言很好但在用户方面没有任何明显动作的情况下就把数据写出到数据库中有点儿令人不安。
实际上应该对发生的事情有更多一些的控制权即示例程序中的
代码可以在执行更新操作之前要求用户做某件事情。
21.3 管理更新过程 我们实际上希望用户方面采取积极的动作来允许更新操作的执行而不是让更新操作默认执行。
首先可以使所有编辑控件都成为只读控件这样所有控件默认就都不能接受来自键盘的数据输入。
然后可以给该对话框添加一个Edit Order按钮其作用是允许适当的编辑控件接受键盘输入。
新的对话框如图21-6所示。
这次将在程序中实现两种概念模式只读和编辑。
当禁止更新时属于只读模式因为控件是只读的当选中的控件可以接受键盘输入时属于编辑模式因此可以更新记录集。
我们的意图是当用户单击Edit Order按钮时希望更新的那些字段的编辑控件将可以接受键盘输入程序进入编辑模式。
将该按钮添加到DBSimpleUpdate应用程序的对话框。
可以将该按钮的ID设定为IDC_EDITORDER还可以在COrderDetailsView类中添加该按钮的处理程序方法是右击该按钮并从弹出菜单中选择Add Event Handler。
将该函数的名称缩写为OnEditorder。
理想情况是应该禁止在更新模式中使用工具栏按钮或Record菜单项移动到表中另一行因为希望用户单击某个按钮时结束更新操作而不是移动记录集的当前位置。
当单击Edit Order按钮时数量和折扣这两个控件的只读状态应该消除并且应该有一个要执行更新操作时应该单击的按钮。
为了适应所有这些要求希望该应用程序的对话框在单击Edit Order按钮之后如图21-7所示。
对应Quantity和Discount的编辑控件现在允许输入数据Edit Order按钮换用了新标签Update另外出现一个标签为Cancel的新按钮以允许用户在必要时取消更新操作。
当单击Edit Order按钮之后除了要禁用在记录中间移动的工具栏按钮以外还应该禁用当前的记录。
因此Record菜单下拉
列表中的菜单项也应该被禁用。
该程序现在处于“编辑”模式。
可以给该对话框添加Cancel按钮但不希望该按钮最初就显示出来因此应该将该按钮的Visible属性设置成False。
设置ID属性值为IDC_CANCEL。
还需要Cancel按钮的处理程序因此现在以给Edit Order按钮添加处理程序的相同方式在COrderDetailsView类中添加一个名为OnCancel的函数—— 稍后再编写该函数的
代码。
Visual C 2008入门经典 1008 图 21-6 图 21-7 更新操作的过程如下用户在对话框上允许输入数据的字段中输入数据然后单击Update按钮完成更新操作。
此后该对话框返回到最初的“只读”模式状态所有编辑控件都成为只读控件。
如果用户不希望进行更新操作则可以单击Cancel按钮而不是Update按钮。
为了实现这种机制并有效地管理更新过程需要在Edit Order按钮被单击之后做下面这几件事情 ● 将Edit Order按钮上的文本修改为Update使之现在成为完成更新操作的按钮。
● 使Cancel按钮出现在对话框上换句话说使该按钮可见。
● 在COrderDetailsView类中将“已经进入编辑模式”的情况记录下来。
这是必要的因为要为两项不同的用途使用同一个按钮所以要根据所处的模式切换Edit Order和Update这两个标签。
● 允许那些对应于希望更新的字段的编辑控件接受键盘输入。
下面看一看如何编写
代码做任何想做的事情。
21.3.1 实现更新模式 首先提供记录应用程序是否处于更新模式的功能。
要实现该功能可以给COrderDetailsView类添加某个enum声明并声明该enum类型的一个变量来反映当前的模式。
给COrderDetailsView类的公有部分添加下面这两行
代码 enum Mode READ_ONLY UPDATE // Application modes Mode m_Mode // Records the current mode 应用程序最初处于READ_ONLY模式因此可以在构造函数中相应地初始化m_Mode COrderDetailsView::COrderDetailsView : CRecordViewCOrderDetailsView::IDD m_ModeREAD_ONLY m_pSet NULL // TOD.