李维书中写的: 处理错误 处理错误是应用程序要面对的工作之一,尤其是当修改了数据时,应用程序 一定要加以处理,否则可能会面临数据遗失或是发生数据完整性被破坏的情形。 如果你熟悉B D E / I D A P I,那么便可能知道如何处理B D E / I D A P I发生错误时的情形。 B D E / I D A P I在发生时程序员可以从E D B E n g i n e E r r o r例外对象中取出发生错误的原 因以及错误的原生错误码。但是当程序员使用A D O来开发应用程序,而且在应用 程序执行时发生A D O错误时,程序员就无法像B D E / I D A P I一样从E D B E n g i n e E r r o r 例外对象取得发生错误的原因。程序员必须从A D O 封装的E r r o r s 集合对象 ( C o l l e c t i o n )中取出错误的原因。 既然E r r o r s称为集合对象,也就表示A D O封装的E r r o r s对象是一个包含许多其 他对象的容器对象( C o n t a i n e r )。E r r o r s包含的对象是称为E r r o r的对象,每一个E r r o r 对象在A D O处理数据存取发生错误时便会包含一个发生错误的信息。图3 - 1 5说明 了E r r o r s和E r r o r对象之间的关系: 图3-15 Errors对象和Error对象的关系 E r r o r对象包含了数个属性。下面的表格说明了这些属性以及它们的意义: 属性意义 N u m b e r 发生错误情形的编号 S o u r c e 这个属性会回传发生错误的对象名称或对象的P r o g I D。如果是A D O的 P r o v i d e r发生错误,那么这个属性便会包含发生错误的P r o v i d e r的标识符。 如果是A D O本身发生错误,那么这个属性便会包含” A D O D B”子字符 串 D e s c r i p t i o n 包含发生的错误的说明信息 H e l p F i l e 辅助文档的路径名称 H e l p C o n t e x t 在辅助文档中说明错误的Help Context数值 S Q L S t a t e 这个属性包含发生错误时由P r o v i d e r回传的5个字符长度的错误码 N a t i v e E r r o r 这个属性包含发生错误时数据库服务器产生的原生数据库错误代码 在这些属性中我想大部分的人对于N a t i v e E r r o r这个属性是最有兴趣的,当然 S Q L S t a t e也是非常重要的参考属性值。 A D O的错误可以分为两类:即A D O的错误以及P r o v i d e r的错误。其中P r o v i d e r 的错误是指应用程序执行了P r o v i d e r 没有提供的功能,或执行了目前无法由 P r o v i d e r执行的功能,或P r o v i d e r在和数据库服务器互动时发生的错误。所有由 P r o v i d e r或数据库产生的错误都会由P r o v i d e r封装到A D O的E r r o r对象中,再加入 E r r o r s集合对象中,因此程序员可以通过E r r o r s和E r r o r取得错误信息。而A D O的错 误则是指和P r o v i d e r无关的错误。由于A D O的错误和P r o v i d e r无关,因此A D O的错 误并不会加入到E r r o r s对象之中。这表示在发生A D O的错误时,即使程序员加到 E r r o r s 集合对象中,也没有任何的E r r o r 对象包含发生的错误信息。不过 A D O E x p r e s s组件已经把A D O的错误和V C L的组件架构整合在一起,因此程序员仍 然可以通过A D O E x p r e s s组件的错误处理程序来拦截相关的A D O错误,并且根据这 些错误来决定应用程序下一步的动作。 在D e l p h i中,程序员可以通过A D O E x p r e s s取得A D O封装的E r r o r s集合对象, 再从E r r o r s集合对象中一一取出E r r o r对象便可以了解应用程序在存取或修改数据时 发生了什么错误。在D e l p h i的A D O I n t程序单元中定义了E r r o r s集合对象和E r r o r对象 的定义。下面是这两个对象的接口声明: Errors = interface ( _ C o l l e c t i o n ) [ ' { 0 0 0 0 0 5 0 1 - 0 0 0 0 - 0 0 1 0 - 8 0 0 0 - 0 0 A A 0 0 6 D 2 E A 4 } ' ] function Get_Item(Index: OleVariant): Error; s a f e c a l l ; 1 1 6 Delphi 5.x ADO/MTS/COM+高级程序设计篇 下载 p r o c e d u r e Clear; s a f e c a l l ; p r o p e r t y Item[Index: OleVariant]: Error r e a d Get_Item; d e f a u l t ; e n d ; E r r o r s D i s p = dispinterface [ ' { 0 0 0 0 0 5 0 1 - 0 0 0 0 - 0 0 1 0 - 8 0 0 0 - 0 0 A A 0 0 6 D 2 E A 4 } ' ] p r o p e r t y Item[Index: OleVariant]: Error readonly d i s p i d 0; d e f a u l t ; p r o c e d u r e Clear; d i s p i d 1 6 1 0 8 0 9 3 4 5 ; p r o p e r t y Count: Integer readonly d i s p i d 1 6 1 0 7 4 3 8 0 8 ; f u n c t i o n _NewEnum: IUnknown; d i s p i d - 4 ; p r o c e d u r e Refresh; d i s p i d 1 6 1 0 7 4 3 8 1 0 ; e n d ; E r r o r = i n t e r f a c e( I D i s p a t c h ) [ ' { 0 0 0 0 0 5 0 0 - 0 0 0 0 - 0 0 1 0 - 8 0 0 0 - 0 0 A A 0 0 6 D 2 E A 4 } ' ] f u n c t i o n Get_Number: Integer; s a f e c a l l; f u n c t i o n Get_Source: WideString; s a f e c a l l; f u n c t i o n Get_Description: WideString; s a f e c a l l; f u n c t i o n Get_HelpFile: WideString; s a f e c a l l; f u n c t i o n Get_HelpContext: Integer; s a f e c a l l; f u n c t i o n Get_SQLState: WideString; s a f e c a l l; f u n c t i o n Get_NativeError: Integer; safecall; p r o p e r t y Number: Integer r e a d G e t _ N u m b e r ; p r o p e r t y Source: WideString r e a d G e t _ S o u r c e ; p r o p e r t y Description: WideString r e a d G e t _ D e s c r i p t i o n ; p r o p e r t y HelpFile: WideString r e a d G e t _ H e l p F i l e ; p r o p e r t y HelpContext: Integer r e a d G e t _ H e l p C o n t e x t ; p r o p e r t y SQLState: WideString r e a d G e t _ S Q L S t a t e ; p r o p e r t y NativeError: Integer r e a d G e t _ N a t i v e E r r o r ; e n d ; E r r o r D i s p = dispinterface [ ' { 0 0 0 0 0 5 0 0 - 0 0 0 0 - 0 0 1 0 - 8 0 0 0 - 0 0 A A 0 0 6 D 2 E A 4 } ' ] p r o p e r t y Number: Integer readonly d i s p i d 1 6 1 0 7 4 3 8 0 8 ; p r o p e r t y Source: WideString readonly d i s p i d 1 6 1 0 7 4 3 8 0 9 ; p r o p e r t y Description: WideString readonly d i s p i d 0 ; p r o p e r t y HelpFile: WideString readonly d i s p i d 1 6 1 0 7 4 3 8 1 1 ; p r o p e r t y HelpContext: Integer readonly d i s p i d 1 6 1 0 7 4 3 8 1 2 ; p r o p e r t y SQLState: WideString readonly d i s p i d 1 6 1 0 7 4 3 8 1 3 ; p r o p e r t y NativeError: Integer readonly d i s p i d 1 6 1 0 7 4 3 8 1 4 ; e n d ; 第3章撰写使用A D O技术的应用系统(二) 1 1 7 下载 从上面的接口声明中我们可以了解以到, E r r o r s和E r r o r都是d u a l接口的对象。 因此只要能够取得E r r o r s集合对象,再通过E r r o r s D i s p接口的I t e m属性数组便可以 使用一个f o r循环取得所有的E r r o r对象。接着就可以通过E r r o r对象存取它的属性, 例如N a t i v e E r r o r等。 让我们使用几个范例来说明如何从A D O E x p r e s s组件取得E r r o r s集合对象,再 从E r r o r s中取得E r r o r对象并取得发生错误的详细信息。图3 - 1 6是一个A D O范例应用 程序的主窗体。在这个范例应用程序中使用了TA D O C o n n e c t i o n、TA D O D a t a S e t连 接到MS SQL Server 7的E m p l o y e e数据表,至于窗体的下方则是一个列表框。稍后 的范例应用程序会在这个列表框中显示A D O的错误信息。让我们使用这个范例应 用程序来修改一笔E m p l o y e e e数据表中的数据。但是在范例应用程序把修改的数据 更新回数据库之前,我们会使用MS SQL Server的工具到E m p l o y e e数据表中先改变 这笔由范例应用程序修改的数据,让范例应用程序在更新时找不到原先的数据, 这样A D O便会发生错误。 图3-16 范例应用程序主窗体 由于目前我们希望A D O产生的错误是属于更新数据的错误,因此必须拦截P o s t 数据的错误。事实上, V C L已经为A D O E x p r e s s组件封装了数个处理错误的事件处 理程序,如下表志示。 事件处理程序名称说明 O n D e l e t e E r r o r 在A D O E x p r e s s组件试图删除数据时发生错误 O n E d i t E r r o r 在A D O E x p r e s s组件试图新增或是编辑数据时发生错误 O n P o s t E r r o r 在A D O E x p r e s s组件试图更新数据回数据库时发生错误 1 1 8 Delphi 5.x ADO/MTS/COM+高级程序设计篇 下载 程序员可以在这三个事件处理程序中处理A D O发生的错误。不过很不幸的是, 从这些事件处理程序中取得A D O的错误信息却不容易。这是因为这些事件处理程 序都有如下的函数原型:
t y p e TDataSetErrorEvent = p r o c e d u r e(DataSet: TDataSet; E: EDatabaseError; v a r Action: TDataAction) of object; 由于这三个事件处理程序产生的例外对象是E D a t a b a s e E r r o r,因此程序员只能 取得错误的说明信息,却无法取得错误的原生码。如果要取得错误的原生码,那 么我们必须从E D B E n g i n e E r r o r这个例外对象中取得。但是由A D O产生例外对象并 不是从E D B E n g i n e E r r o r继承下来的。现在让我们为刚才的范例应用程序定义一个 O n P o s t E r r o r事件处理程序,并且在这个事件处理程序中撰写如下的程序代码: procedure TForm4.ADODataSet1PostError(DataSet: TDataSet; E: EDatabaseError; v a r Action: TDataAction); b e g i n i f (E i s EDBEngineError) t h e n S h o w M e s s a g e ( ' E是E D B E n g i n e E r r o r类型的错误' ) e l s e S h o w M e s s a g e ( E . M e s s a g e ) ; E n d ; 在O n P o s t E r r o r中我们判断它产生的例外对象是不是属于E D B E n g i n E r r o r例外 对象。如果是,我们就可以从例外对象中取得错误的原生码。如果不是,我们就 只能显示错误信息。 现在就执行这个范例应用程序。图3 - 1 7是同时执行范例应用程序以及MS SQL Server 7的Enterprise Manager的画面。在这里我们要让范例应用程序和E n t e r p r i s e M a n a g e r同时修改A - C 7 1 9 7 0 F这笔数据。因此当范例应用程序执行之后,在下图中 显示了先使用Enterprise Manager修改A - C 7 1 9 7 0 F这笔数据的m i n i t字段,把它的数 值从空白修改为“T”。 现在再回到范例应用程序。同样把m i n i t字段的数值从空白修改为“ G”,然后 点选D B N a v i g a t o r中的P o s t按钮,把这笔数据更新回数据库中。 此时范例应用程序显示了图3 - 1 9所示的错误画面: 第3章撰写使用A D O技术的应用系统(二) 1 1 9 下载 图3-17 执行范例应用程序以及MS SQL Server的Enterprise Manager 图3-18 先使用Enterprise Manager改变数据表中的 数据,再让范例应用程序修改同一笔数据 图3-19 范例应用程序的ADO发生修改数据的错误 1 2 0 Delphi 5.x ADO/MTS/COM+高级程序设计篇 下载 从上面的画面中我们看到,范例应用程序显示它无法在原先的E m p l o y e e数据 表中找到要被更新的数据。这是当然的,因为在范例应用程序更新数据之前我们 已经使用Enterprise Manager把E m p l o y e e数据表中的数据做了改变。但是从上图中 我们可以发现, A D O E x p r e s s组件在发生错误时产生的例外对象并不像B D E / I D A P I 一样是E D B E n g i n e E r r o r对象,因此我们无法像B D E / I D A P I的应用程序一样从 E D B E n g i n e E r r o r取得原生的错误代码。如果你检查封装A D O E x p r e s s组件的 A D O D B程序单元,也会发现A D O的错误是由E A D O E r r o r类别封装的。 { Errors } EADOError = c l a s s( E D a t a b a s e E r r o r ) ; 因此要取得A D O详细的错误信息,就必须通过A D O的E r r o r s和E r r o r对象。让 我们修改前面O n P o s t E r r o r的事件处理程序如下所示: p r o c e d u r e TForm4.ADODataSet1PostError(DataSet: TDataSet; E: EDatabaseError; v a r Action: TDataAction); v a r adoErrors : Errors; adoError : Error; iCount : Integer; b e g i n / /开始处理A D O的E r r o r s对象 adoErrors := ADOConnection1.Errors; f o r iCount := 0 t o adoErrors.Count - 1 d o // Iterate b e g i n adoError := adoErrors.Item[iCount]; lbADOErrors.Items.Add('Error Number : ' + IntToStr(adoError.Number)); lbADOErrors.Items.Add('Error Source : ' + adoError.Source); lbADOErrors.Items.Add('Error Description : ' + adoError.Description); lbADOErrors.Items.Add('Error HelpFile : ' + adoError.HelpFile); lbADOErrors.Items.Add('Error SQLState : ' + adoError.SQLState); lbADOErrors.Items.Add('Error NativeError : ' + I n t T o S t r ( a d o E r r o r . N a t i v e E r r o r ) ) ; e n d ; // for e n d ; A D O E x p r e s s的TA D O C o n n e c t i o n组件封装的E r r o r s属性就是A D O的E r r o r s集合 对象。因此要取得A D O的错误信息,程序员可以存取TA D O C o n n e c t i o n的E r r o r s属 第3章撰写使用A D O技术的应用系统(二) 1 2 1 下载 性值,然后再使用一个循环从E r r o r s集合对象中一一取出每一个E r r o r对象。在上面 的程序代码中先声明a d o E r r o r s这个对象变量,声明它为E r r o r s类型的变量,而 E r r o r s类型定义在A D O D B中: Errors = ADOInt.Errors; 因此E r r o r s类型事实上就是A D O的E r r o r s这个接口。 所以在上面的程序代码中,当我们通过TA D O C o n n e c t i o n的E r r o r s属性值取得 了E r r o r s接口之后,就可以通过它的d u a l接口E r r o r s D i s p来取得目前在E r r o r s集合对 象中有多少个A D O错误对象。并且通过E r r o r s D i s p的I t e m数组属性一一取出每一个 E r r o r接口,再从E r r o r D i s p接口取得所有错误的信息,包括原生数据库错误码。 现在让我们再次执行范例应用程序以及Enterprise Manager,如图3 - 2 0所示。 然后再次如刚才一样修改A - C 7 1 9 7 0 F这笔数据。 图3-20 再次使用Enterprise Manager改变数据表中的 数据,并且让范例应用程序修改同一笔数据 这一次当范例应用程序更新数据之后,我们可以看到图3 - 2 1所示的画面。 我们不但可以看到错误信息,也可以取得所有错误的详细信息并且显示在窗 体下方的列表框中。从图中我们看到,这个错误的数据库原生错误码是3 2。 从这个范例应用程序中我们了解了如何通过E r r o r s和E r r o r对象取得错误信息, A D O E x p r e s s通过使用d u a l接口而让程序员可以很容易地通过A D O I n t封装的E r r o r s, 1 2 2 Delphi 5.x ADO/MTS/COM+高级程序设计篇 下载 E r r o r s D i s p,E r r o r和E r r o r D i s p接口存取所有的错误信息。 图3-21 范例应用程序通过ADO封装的Errors和Error取得发生错误的信息 图3-22 范例应用程序通过ADO封装的Errors和Error取得发生错误的信息 在前面我们说过, A D O的错误分为两种,刚才显示的是属于P r o v i d e r和数据库 的错误。第二种错误则是A D O本身的错误。这种错误并不会把发生的错误原因封 第3章撰写使用A D O技术的应用系统(二) 1 2 3 下载 装在E r r o r s和E r r o r对象之中。为了让你了解这种错误,我们继续修改刚才的范例应 用程序,看看这种错误可能发生的情形。 让我们在这个范例应用程序中加入搜寻数据的能力。请在窗体中加入一个按 钮和两个T E d i t控件,用户可以在第一个T E d i t控件中输入要搜寻的字段名称,在第 二个T E d i t控件中输入要搜寻的数值,然后点选按钮开始搜寻。 “寻找字段数据”按钮的O n C l i c k事件处理程序非常简单。它使用了L o c a t e来搜 寻数据,并且调用S h o w A D O E r r o r s程序显示任何发生的错误。至于S h o w A D O E r r o r s 则是类似前面从E r r o r s对象取出E r r o r对象并且显示错误信息在列表框中的程序代码。 p r o c e d u r e Tform4.btnSearchFieldClick(Sender: Tobject); v a r bResult : Boolean; b e g i n bResult := ADODataSet1.Locate(edtFieldName.Text, edtFieldValue.Text, [loCaseInsensitive , loPartialKey]); S h o w A D O E r r o r s ; e n d ; 现在执行这个范例应用程序。让我们先使用e m p _ i d这个索引字段来搜寻数据。 由于e m p _ i d是一个有效的字段,而且我们输入的搜寻数值是一笔存在数据的索引 值,因此范例应用程序可以正确地找到这笔数据。如图3 - 2 3所示。 图3-23 执行范例应用程序并且搜寻emp_id字段的数据 1 2 4 Delphi 5.x ADO/MTS/COM+高级程序设计篇 下载 现在再让我们搜寻一个不存在的字段“ e m p _ s e c r e t s”看看会有什么结果。如图 3 - 2 4所示。由于这个字段根本不存在,因此这将会产生错误,那么S h o w A D O E r r o r s 是不是能够显示任何的错误信息呢? 图3-24 执行范例应用程序并且搜寻一个不存在字段的数据 事实上,当点选了“寻找字段数据”按钮之后,范例应用程序的列表框中并 不会显示任何的错误信息,范例应用程序只会显示如图3 - 2 5所示的错误信息。 图3-25 ADO封装的Errors和Error对象并没有包含任何 的错误信息,而是ADO的VCL拦截了错误 由于搜寻不存在字段的错误是属于A D O本身的错误,因此就像前面说明的一 样,这种错误并不会封装在E r r o r s集合对象中。因此S h o w A D O E r r o r s程序无法从 E r r o r s集合对象中取得任何的E r r o r对象。相反,这种错误会触发D e l p h i本身的错误 机制而显示错误信息。这个范例也证明了A D O的错误会和D e l p h i的例外处理机制 结合在一起。
处理错误
处理错误是应用程序要面对的工作之一,尤其是当修改了数据时,应用程序
一定要加以处理,否则可能会面临数据遗失或是发生数据完整性被破坏的情形。
如果你熟悉B D E / I D A P I,那么便可能知道如何处理B D E / I D A P I发生错误时的情形。
B D E / I D A P I在发生时程序员可以从E D B E n g i n e E r r o r例外对象中取出发生错误的原
因以及错误的原生错误码。但是当程序员使用A D O来开发应用程序,而且在应用
程序执行时发生A D O错误时,程序员就无法像B D E / I D A P I一样从E D B E n g i n e E r r o r
例外对象取得发生错误的原因。程序员必须从A D O 封装的E r r o r s 集合对象
( C o l l e c t i o n )中取出错误的原因。
既然E r r o r s称为集合对象,也就表示A D O封装的E r r o r s对象是一个包含许多其
他对象的容器对象( C o n t a i n e r )。E r r o r s包含的对象是称为E r r o r的对象,每一个E r r o r
对象在A D O处理数据存取发生错误时便会包含一个发生错误的信息。图3 - 1 5说明
了E r r o r s和E r r o r对象之间的关系:
图3-15 Errors对象和Error对象的关系
E r r o r对象包含了数个属性。下面的表格说明了这些属性以及它们的意义:
属性意义
N u m b e r 发生错误情形的编号
S o u r c e 这个属性会回传发生错误的对象名称或对象的P r o g I D。如果是A D O的
P r o v i d e r发生错误,那么这个属性便会包含发生错误的P r o v i d e r的标识符。
如果是A D O本身发生错误,那么这个属性便会包含” A D O D B”子字符
串
D e s c r i p t i o n 包含发生的错误的说明信息
H e l p F i l e 辅助文档的路径名称
H e l p C o n t e x t 在辅助文档中说明错误的Help Context数值
S Q L S t a t e 这个属性包含发生错误时由P r o v i d e r回传的5个字符长度的错误码
N a t i v e E r r o r 这个属性包含发生错误时数据库服务器产生的原生数据库错误代码
在这些属性中我想大部分的人对于N a t i v e E r r o r这个属性是最有兴趣的,当然
S Q L S t a t e也是非常重要的参考属性值。
A D O的错误可以分为两类:即A D O的错误以及P r o v i d e r的错误。其中P r o v i d e r
的错误是指应用程序执行了P r o v i d e r 没有提供的功能,或执行了目前无法由
P r o v i d e r执行的功能,或P r o v i d e r在和数据库服务器互动时发生的错误。所有由
P r o v i d e r或数据库产生的错误都会由P r o v i d e r封装到A D O的E r r o r对象中,再加入
E r r o r s集合对象中,因此程序员可以通过E r r o r s和E r r o r取得错误信息。而A D O的错
误则是指和P r o v i d e r无关的错误。由于A D O的错误和P r o v i d e r无关,因此A D O的错
误并不会加入到E r r o r s对象之中。这表示在发生A D O的错误时,即使程序员加到
E r r o r s 集合对象中,也没有任何的E r r o r 对象包含发生的错误信息。不过
A D O E x p r e s s组件已经把A D O的错误和V C L的组件架构整合在一起,因此程序员仍
然可以通过A D O E x p r e s s组件的错误处理程序来拦截相关的A D O错误,并且根据这
些错误来决定应用程序下一步的动作。
在D e l p h i中,程序员可以通过A D O E x p r e s s取得A D O封装的E r r o r s集合对象,
再从E r r o r s集合对象中一一取出E r r o r对象便可以了解应用程序在存取或修改数据时
发生了什么错误。在D e l p h i的A D O I n t程序单元中定义了E r r o r s集合对象和E r r o r对象
的定义。下面是这两个对象的接口声明:
Errors = interface ( _ C o l l e c t i o n )
[ ' { 0 0 0 0 0 5 0 1 - 0 0 0 0 - 0 0 1 0 - 8 0 0 0 - 0 0 A A 0 0 6 D 2 E A 4 } ' ]
function Get_Item(Index: OleVariant): Error; s a f e c a l l ;
1 1 6 Delphi 5.x ADO/MTS/COM+高级程序设计篇
下载
p r o c e d u r e Clear; s a f e c a l l ;
p r o p e r t y Item[Index: OleVariant]: Error r e a d Get_Item; d e f a u l t ;
e n d ;
E r r o r s D i s p = dispinterface
[ ' { 0 0 0 0 0 5 0 1 - 0 0 0 0 - 0 0 1 0 - 8 0 0 0 - 0 0 A A 0 0 6 D 2 E A 4 } ' ]
p r o p e r t y Item[Index: OleVariant]: Error readonly d i s p i d 0; d e f a u l t ;
p r o c e d u r e Clear; d i s p i d 1 6 1 0 8 0 9 3 4 5 ;
p r o p e r t y Count: Integer readonly d i s p i d 1 6 1 0 7 4 3 8 0 8 ;
f u n c t i o n _NewEnum: IUnknown; d i s p i d - 4 ;
p r o c e d u r e Refresh; d i s p i d 1 6 1 0 7 4 3 8 1 0 ;
e n d ;
E r r o r = i n t e r f a c e( I D i s p a t c h )
[ ' { 0 0 0 0 0 5 0 0 - 0 0 0 0 - 0 0 1 0 - 8 0 0 0 - 0 0 A A 0 0 6 D 2 E A 4 } ' ]
f u n c t i o n Get_Number: Integer; s a f e c a l l;
f u n c t i o n Get_Source: WideString; s a f e c a l l;
f u n c t i o n Get_Description: WideString; s a f e c a l l;
f u n c t i o n Get_HelpFile: WideString; s a f e c a l l;
f u n c t i o n Get_HelpContext: Integer; s a f e c a l l;
f u n c t i o n Get_SQLState: WideString; s a f e c a l l;
f u n c t i o n Get_NativeError: Integer; safecall;
p r o p e r t y Number: Integer r e a d G e t _ N u m b e r ;
p r o p e r t y Source: WideString r e a d G e t _ S o u r c e ;
p r o p e r t y Description: WideString r e a d G e t _ D e s c r i p t i o n ;
p r o p e r t y HelpFile: WideString r e a d G e t _ H e l p F i l e ;
p r o p e r t y HelpContext: Integer r e a d G e t _ H e l p C o n t e x t ;
p r o p e r t y SQLState: WideString r e a d G e t _ S Q L S t a t e ;
p r o p e r t y NativeError: Integer r e a d G e t _ N a t i v e E r r o r ;
e n d ;
E r r o r D i s p = dispinterface
[ ' { 0 0 0 0 0 5 0 0 - 0 0 0 0 - 0 0 1 0 - 8 0 0 0 - 0 0 A A 0 0 6 D 2 E A 4 } ' ]
p r o p e r t y Number: Integer readonly d i s p i d 1 6 1 0 7 4 3 8 0 8 ;
p r o p e r t y Source: WideString readonly d i s p i d 1 6 1 0 7 4 3 8 0 9 ;
p r o p e r t y Description: WideString readonly d i s p i d 0 ;
p r o p e r t y HelpFile: WideString readonly d i s p i d 1 6 1 0 7 4 3 8 1 1 ;
p r o p e r t y HelpContext: Integer readonly d i s p i d 1 6 1 0 7 4 3 8 1 2 ;
p r o p e r t y SQLState: WideString readonly d i s p i d 1 6 1 0 7 4 3 8 1 3 ;
p r o p e r t y NativeError: Integer readonly d i s p i d 1 6 1 0 7 4 3 8 1 4 ;
e n d ;
第3章撰写使用A D O技术的应用系统(二) 1 1 7
下载
从上面的接口声明中我们可以了解以到, E r r o r s和E r r o r都是d u a l接口的对象。
因此只要能够取得E r r o r s集合对象,再通过E r r o r s D i s p接口的I t e m属性数组便可以
使用一个f o r循环取得所有的E r r o r对象。接着就可以通过E r r o r对象存取它的属性,
例如N a t i v e E r r o r等。
让我们使用几个范例来说明如何从A D O E x p r e s s组件取得E r r o r s集合对象,再
从E r r o r s中取得E r r o r对象并取得发生错误的详细信息。图3 - 1 6是一个A D O范例应用
程序的主窗体。在这个范例应用程序中使用了TA D O C o n n e c t i o n、TA D O D a t a S e t连
接到MS SQL Server 7的E m p l o y e e数据表,至于窗体的下方则是一个列表框。稍后
的范例应用程序会在这个列表框中显示A D O的错误信息。让我们使用这个范例应
用程序来修改一笔E m p l o y e e e数据表中的数据。但是在范例应用程序把修改的数据
更新回数据库之前,我们会使用MS SQL Server的工具到E m p l o y e e数据表中先改变
这笔由范例应用程序修改的数据,让范例应用程序在更新时找不到原先的数据,
这样A D O便会发生错误。
图3-16 范例应用程序主窗体
由于目前我们希望A D O产生的错误是属于更新数据的错误,因此必须拦截P o s t
数据的错误。事实上, V C L已经为A D O E x p r e s s组件封装了数个处理错误的事件处
理程序,如下表志示。
事件处理程序名称说明
O n D e l e t e E r r o r 在A D O E x p r e s s组件试图删除数据时发生错误
O n E d i t E r r o r 在A D O E x p r e s s组件试图新增或是编辑数据时发生错误
O n P o s t E r r o r 在A D O E x p r e s s组件试图更新数据回数据库时发生错误
1 1 8 Delphi 5.x ADO/MTS/COM+高级程序设计篇
下载
程序员可以在这三个事件处理程序中处理A D O发生的错误。不过很不幸的是,
从这些事件处理程序中取得A D O的错误信息却不容易。这是因为这些事件处理程
序都有如下的函数原型:
t y p e TDataSetErrorEvent = p r o c e d u r e(DataSet: TDataSet; E:
EDatabaseError; v a r Action: TDataAction) of object;
由于这三个事件处理程序产生的例外对象是E D a t a b a s e E r r o r,因此程序员只能
取得错误的说明信息,却无法取得错误的原生码。如果要取得错误的原生码,那
么我们必须从E D B E n g i n e E r r o r这个例外对象中取得。但是由A D O产生例外对象并
不是从E D B E n g i n e E r r o r继承下来的。现在让我们为刚才的范例应用程序定义一个
O n P o s t E r r o r事件处理程序,并且在这个事件处理程序中撰写如下的程序代码:
procedure TForm4.ADODataSet1PostError(DataSet: TDataSet; E:
EDatabaseError; v a r Action: TDataAction);
b e g i n
i f (E i s EDBEngineError) t h e n
S h o w M e s s a g e ( ' E是E D B E n g i n e E r r o r类型的错误' )
e l s e
S h o w M e s s a g e ( E . M e s s a g e ) ;
E n d ;
在O n P o s t E r r o r中我们判断它产生的例外对象是不是属于E D B E n g i n E r r o r例外
对象。如果是,我们就可以从例外对象中取得错误的原生码。如果不是,我们就
只能显示错误信息。
现在就执行这个范例应用程序。图3 - 1 7是同时执行范例应用程序以及MS SQL
Server 7的Enterprise Manager的画面。在这里我们要让范例应用程序和E n t e r p r i s e
M a n a g e r同时修改A - C 7 1 9 7 0 F这笔数据。因此当范例应用程序执行之后,在下图中
显示了先使用Enterprise Manager修改A - C 7 1 9 7 0 F这笔数据的m i n i t字段,把它的数
值从空白修改为“T”。
现在再回到范例应用程序。同样把m i n i t字段的数值从空白修改为“ G”,然后
点选D B N a v i g a t o r中的P o s t按钮,把这笔数据更新回数据库中。
此时范例应用程序显示了图3 - 1 9所示的错误画面:
第3章撰写使用A D O技术的应用系统(二) 1 1 9
下载
图3-17 执行范例应用程序以及MS SQL Server的Enterprise Manager
图3-18 先使用Enterprise Manager改变数据表中的
数据,再让范例应用程序修改同一笔数据
图3-19 范例应用程序的ADO发生修改数据的错误
1 2 0 Delphi 5.x ADO/MTS/COM+高级程序设计篇
下载
从上面的画面中我们看到,范例应用程序显示它无法在原先的E m p l o y e e数据
表中找到要被更新的数据。这是当然的,因为在范例应用程序更新数据之前我们
已经使用Enterprise Manager把E m p l o y e e数据表中的数据做了改变。但是从上图中
我们可以发现, A D O E x p r e s s组件在发生错误时产生的例外对象并不像B D E / I D A P I
一样是E D B E n g i n e E r r o r对象,因此我们无法像B D E / I D A P I的应用程序一样从
E D B E n g i n e E r r o r取得原生的错误代码。如果你检查封装A D O E x p r e s s组件的
A D O D B程序单元,也会发现A D O的错误是由E A D O E r r o r类别封装的。
{ Errors }
EADOError = c l a s s( E D a t a b a s e E r r o r ) ;
因此要取得A D O详细的错误信息,就必须通过A D O的E r r o r s和E r r o r对象。让
我们修改前面O n P o s t E r r o r的事件处理程序如下所示:
p r o c e d u r e TForm4.ADODataSet1PostError(DataSet: TDataSet; E:
EDatabaseError; v a r Action: TDataAction);
v a r
adoErrors : Errors;
adoError : Error;
iCount : Integer;
b e g i n
/ /开始处理A D O的E r r o r s对象
adoErrors := ADOConnection1.Errors;
f o r iCount := 0 t o adoErrors.Count - 1 d o // Iterate
b e g i n
adoError := adoErrors.Item[iCount];
lbADOErrors.Items.Add('Error Number : ' + IntToStr(adoError.Number));
lbADOErrors.Items.Add('Error Source : ' + adoError.Source);
lbADOErrors.Items.Add('Error Description : ' + adoError.Description);
lbADOErrors.Items.Add('Error HelpFile : ' + adoError.HelpFile);
lbADOErrors.Items.Add('Error SQLState : ' + adoError.SQLState);
lbADOErrors.Items.Add('Error NativeError : ' +
I n t T o S t r ( a d o E r r o r . N a t i v e E r r o r ) ) ;
e n d ; // for
e n d ;
A D O E x p r e s s的TA D O C o n n e c t i o n组件封装的E r r o r s属性就是A D O的E r r o r s集合
对象。因此要取得A D O的错误信息,程序员可以存取TA D O C o n n e c t i o n的E r r o r s属
第3章撰写使用A D O技术的应用系统(二) 1 2 1
下载
性值,然后再使用一个循环从E r r o r s集合对象中一一取出每一个E r r o r对象。在上面
的程序代码中先声明a d o E r r o r s这个对象变量,声明它为E r r o r s类型的变量,而
E r r o r s类型定义在A D O D B中:
Errors = ADOInt.Errors;
因此E r r o r s类型事实上就是A D O的E r r o r s这个接口。
所以在上面的程序代码中,当我们通过TA D O C o n n e c t i o n的E r r o r s属性值取得
了E r r o r s接口之后,就可以通过它的d u a l接口E r r o r s D i s p来取得目前在E r r o r s集合对
象中有多少个A D O错误对象。并且通过E r r o r s D i s p的I t e m数组属性一一取出每一个
E r r o r接口,再从E r r o r D i s p接口取得所有错误的信息,包括原生数据库错误码。
现在让我们再次执行范例应用程序以及Enterprise Manager,如图3 - 2 0所示。
然后再次如刚才一样修改A - C 7 1 9 7 0 F这笔数据。
图3-20 再次使用Enterprise Manager改变数据表中的
数据,并且让范例应用程序修改同一笔数据
这一次当范例应用程序更新数据之后,我们可以看到图3 - 2 1所示的画面。
我们不但可以看到错误信息,也可以取得所有错误的详细信息并且显示在窗
体下方的列表框中。从图中我们看到,这个错误的数据库原生错误码是3 2。
从这个范例应用程序中我们了解了如何通过E r r o r s和E r r o r对象取得错误信息,
A D O E x p r e s s通过使用d u a l接口而让程序员可以很容易地通过A D O I n t封装的E r r o r s,
1 2 2 Delphi 5.x ADO/MTS/COM+高级程序设计篇
下载
E r r o r s D i s p,E r r o r和E r r o r D i s p接口存取所有的错误信息。
图3-21 范例应用程序通过ADO封装的Errors和Error取得发生错误的信息
图3-22 范例应用程序通过ADO封装的Errors和Error取得发生错误的信息
在前面我们说过, A D O的错误分为两种,刚才显示的是属于P r o v i d e r和数据库
的错误。第二种错误则是A D O本身的错误。这种错误并不会把发生的错误原因封
第3章撰写使用A D O技术的应用系统(二) 1 2 3
下载
装在E r r o r s和E r r o r对象之中。为了让你了解这种错误,我们继续修改刚才的范例应
用程序,看看这种错误可能发生的情形。
让我们在这个范例应用程序中加入搜寻数据的能力。请在窗体中加入一个按
钮和两个T E d i t控件,用户可以在第一个T E d i t控件中输入要搜寻的字段名称,在第
二个T E d i t控件中输入要搜寻的数值,然后点选按钮开始搜寻。
“寻找字段数据”按钮的O n C l i c k事件处理程序非常简单。它使用了L o c a t e来搜
寻数据,并且调用S h o w A D O E r r o r s程序显示任何发生的错误。至于S h o w A D O E r r o r s
则是类似前面从E r r o r s对象取出E r r o r对象并且显示错误信息在列表框中的程序代码。
p r o c e d u r e Tform4.btnSearchFieldClick(Sender: Tobject);
v a r
bResult : Boolean;
b e g i n
bResult := ADODataSet1.Locate(edtFieldName.Text,
edtFieldValue.Text, [loCaseInsensitive , loPartialKey]);
S h o w A D O E r r o r s ;
e n d ;
现在执行这个范例应用程序。让我们先使用e m p _ i d这个索引字段来搜寻数据。
由于e m p _ i d是一个有效的字段,而且我们输入的搜寻数值是一笔存在数据的索引
值,因此范例应用程序可以正确地找到这笔数据。如图3 - 2 3所示。
图3-23 执行范例应用程序并且搜寻emp_id字段的数据
1 2 4 Delphi 5.x ADO/MTS/COM+高级程序设计篇
下载
现在再让我们搜寻一个不存在的字段“ e m p _ s e c r e t s”看看会有什么结果。如图
3 - 2 4所示。由于这个字段根本不存在,因此这将会产生错误,那么S h o w A D O E r r o r s
是不是能够显示任何的错误信息呢?
图3-24 执行范例应用程序并且搜寻一个不存在字段的数据
事实上,当点选了“寻找字段数据”按钮之后,范例应用程序的列表框中并
不会显示任何的错误信息,范例应用程序只会显示如图3 - 2 5所示的错误信息。
图3-25 ADO封装的Errors和Error对象并没有包含任何
的错误信息,而是ADO的VCL拦截了错误
由于搜寻不存在字段的错误是属于A D O本身的错误,因此就像前面说明的一
样,这种错误并不会封装在E r r o r s集合对象中。因此S h o w A D O E r r o r s程序无法从
E r r o r s集合对象中取得任何的E r r o r对象。相反,这种错误会触发D e l p h i本身的错误
机制而显示错误信息。这个范例也证明了A D O的错误会和D e l p h i的例外处理机制
结合在一起。