"Unable to find record. No key specified"错误分析及解决办法

 用midas已经很久了,昨天看了一些midas的源码,有点心得,下面写一下如何开发一个请求响应模式的midas系统


系统的结构

    client端的ClientDataset直接连接Server端的Provider,可以构成最简单的Midas系统,但是这种系统的缺点是很明显的,

1.需要在Client上注册appserver,就算你发现了可以写注册表,解决这个问题,仍然增加了部署的复杂性

2.系统内部传输的数据,将被完全屏蔽,你基本上无法做任何的调整对于大型系统来说,你更新了一条数据,可能roundtrip跑了几个来回,而请求响应将是一次roundtrip,

3.ClientDataset和provider的连接,你有两种解决方式,a,一个业务,一个Cp对(ClientDataset,Provider),麻烦吧,而且是有状态的.b.只用一个cp对,更改ClientDataset.CommandText,来返回不同的数据,这样不能池化appServer,性能严重影响,

于是这里推荐的模型是

client端使用ClientDataset来做一个本地缓存表,界面的数据由ClientDataset来提供,而ClientDataset的数据,仅通过调用WebService也好,Com+组件也好的方法,来得到数据,当用户更新时,本地传送ClientDataset的Delta给后端,做更新.

server端使用ClientDataset->Provider->Dataset来取得数据,返回ClientDataset的data给调用者,然后立刻释放或关闭,成为无状态的appserver,更新时,将调用者传过来的dealta,付给ClientDataset并做更新,然后也立刻释放或关闭.

server端的实现

GetData应用很简单,没有特别之处,重要的时在UpdateData的情况,以一个例子为例,我们取得table1的数据,返回给客户端,客户端可以对数据进行cud操作(Create,Update,Delete),然后一次性提交更改,(叹,midas的delta真是好,我一直觉得这个是完美的),

我们看看Server在UpdateData时应该如何写代码,其中的关键有

1.Provider的ResolveToDataset为true还是false

2.Provider的UpdateMode

3.TField的ProviderFlags的设置

ResolveToDataset确定当你调用ClientDataset.ApplyUpdates时,是由ClientDataset来生成sql语句,还是由AdoQuery来生成,

如果为true的时候,将由AdoQuery来生成,一开始,我的设置是adoQuery.Sql没有设置,任何东西,而是在ClientDataset.CommandText中设置了’select * from table1 where 1=0′,此处的1=0 是为了打开ClientDataset时不需要取得任何数据,此时将竟能执行Create操作,生成正确的Insert语句.update和delete操作均会报”Unable to find record.  No key specified”错误,究其原因,是因为使用adoquery来做更新,但是此时用了where 1=0,adoquery中没有任何数据,而adoquery的更新(即dataset的更新)是基于存在的数据来生成sql语句,于是出现该错误,当然一个解决方式是where 1=1,这样,汗,你不会写这样的代码吧,也可以解析delta来只取得需要做更新的记录,但是这样写代码太累了,于是将其设置为false,

设置为false没有问题嘛?,当设置成false时,有clientdataset来做更新,其就算时where 1=0 也能生成正确的cud的sql语句,但是看看updatemode设置的不同,会产生什么效果,

1.upWhereAll

这样在构建sql语句时,将生成where 字段1=值1 and 字段2=值2…..and 字段n=值n的查询语句来做单条记录的更新,当然这种方式没有问题,但是如果表中有datetime类型字段并且该字段不是null的化,你将找不到任何记录,(我是在sqlserver2000上发现这个错误的,也许在其他数据库上并没有这个问题),需要舍弃

2.upWhereChanged

当你更新了Col1和col2时,生成”where col1=原值1 and col2=原值2″,看到问题了嘛?如果你的这个表中有这两个字段完全一样的多条记录,那么后果是多么的可怕.当然一个字段的情况也是这样,看看帮助有什么

upWhereAll All columns (fields) are used to locate the record.
upWhereChanged Only key field values and the original value of fields that have changed are used to find the record.
upWhereKeyOnly Only key fields are used to find the record.

他可以将key field包含进where语句中,什么是key field呢?,keyfield是其ProviderFlag集合中包含ptInKey的TField,而当你使用sql语句打开一个表后,所有的Field的ProviderFlag是[ptInWhere,ptInUpdate],如果我们将打开表的主键(他是唯一的)设置成KeyField,那么上述的where语句将是”where primarykey = 主键字段 and col1=原值1 and col2=原值2 “,那么数据的定位将没有任何问题,我的测试结果完全说明了这个问题,于是我在更新时多传入一个参数,即要更新的表的主键字段名,但是问题仍然没有解决,它的sql语句并没有”primarykey = 主键字段”这一段,于是我迷惑了,开始看Providers单元的源码,终于发现问题之所在,我设置的是ClientDataset的Field,而provider来生成sql语句是参考的是其Dataset属性设置的TDataset的Field,即AdoQuery,于是我修正了这个错误,看到正确的where子句被提交到数据库,爽..

with FProvider.DataSet.FieldByName( pkField ) do 
            ProviderFlags := ProviderFlags + [ pfInKey ]; 


3.upWhereKeyOnly

再看这个,Only key fields are used to find the record,如果你将表的主键设置成了keyfield,那么要其他字段干什么呢?我将provider的updateMode设置成这个,当更新的时候,where子句的是”where primarykey = 主键字段”,呵呵这样蛮好,再测试,成功,

下面是我的Update函数

procedure TBaseDatabase.UpdateData( const pkField, theSql, theDelta: string ); 
    procedure SetDateTimeFieldProviderFlags( theField: TField ); 
    begin 
        if theField.DataType in [ ftDate, ftTime, ftDateTime ] then 
            theField.ProviderFlags := theField.ProviderFlags - [ pfInWhere ]; 
    end; 
begin 
    with FCds do 
    begin 
        FProvider.DataSet.Close; //    关闭adoQuery
        Close; 
        CommandText := theSql; 
        Open; 
        XMLData := theDelta; //        客户端传过来的更新,是midas的delta封包
        //        ShowMessageEx(IntToStr(FProvider.DataSet.FieldCount)); 

                //日期型字段是不能定位记录的主要原因,将其provierFlags设置 
        //        ForEachFieldInDataset( FCds, @SetDateTimeFieldProviderFlags ); 
        DoDatasetOpen; //打开adoquery以获得Fields信息
      //设置其主键字段为keyField
        with FProvider.DataSet.FieldByName( pkField ) do 
            ProviderFlags := ProviderFlags + [ pfInKey ]; 

        ApplyUpdates( 0 ); 
    end; 
end; 

其中的Delta可以包括客户端的任意多次修改,如,create两条记录,update一条记录,delete了5条记录.

这是一个单表的操作,也可以扩展成主从表的操作,一次性传送主从表的多条记录,例如可以定义

procedure TBaseDatabase.UpdateDatas( const pkField: string ;const theSqls, theDeltas: ArrayString ); 

我喜欢将server端写成com+来执行,再com+和client中再加一层WebService来获得远程的支持,欢迎大家多提宝贵意见

这些问题用了一天的时间解决.

posted @ 2009-04-30 19:11  小宇飞刀  阅读(233)  评论(0编辑  收藏  举报