问题,见标题

解决方案 »

  1.   

    李维书中写的:
    处理错误
    处理错误是应用程序要面对的工作之一,尤其是当修改了数据时,应用程序
    一定要加以处理,否则可能会面临数据遗失或是发生数据完整性被破坏的情形。
    如果你熟悉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的错误信息却不容易。这是因为这些事件处理程
    序都有如下的函数原型:
      

  2.   


    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的例外处理机制
    结合在一起。