服务器端:
TADOConnection
TADOQuery
TDataSetProvider
客户端:
TSocketConnection
TClientDataSet当客户端用ClientDataSet打开一个表后,进行修改并更新(通过DataSetProvider.ApplyUpdates),而在打开和更新之间,可能有另外的用户对该表进行过修改,然后,这个更新就会出错,提示“Record not found or changed by other user”。
请问,这个问题如何解决,按说这种用法应该很多,是不是我那个东东用得不当呢?

解决方案 »

  1.   

    to:fenger8293
    如果这样,我的更新就不成功,而所做的工作就会前功尽弃,不是我想要的
    在我和系统中,我希望以最后更新为准,而且很多时候是一个用户修改表的某几个字段,而其它用户修改表的另外一些字段,逻辑是并没有多少矛盾,只有很少时候是同时修改同一字段的,这时我希望是以后修改的为准,我的表又是有Key字段的。
      

  2.   

    从技术上说,最简单的方法是让使用Reconcile Error Dialog框,在TClientDataSet的OnConcileError事件中,把出错处理交由出错对话框去呈现,让用户自己去决定如何处理发生冲突的记录问题。如果用户业务对记录更新冲突已经有明确的规定,那设计人员也可在程序中,根据业务规则自行编码解决冲突问题,然后重新ApplyUpdates。如果还不行,那就只好呈现给用户自己决定了。
      

  3.   

    TDataSetProvider的属性UpdateMode的设置也非常重要。合理的设置,可以最大限度地减少记录更新发生冲突的情况。
      

  4.   

    还是不能解决,UpdateMode三个都试了,都会出错。晕死
      

  5.   

    DataSetProvider的Update机理不知如何,更新时定位记录如果都按所有字段来定位,肯定就会出现这种问题,表我已定义有Key,并且Provider的约束已设为True,还是出错,不知怎样才能使其只根据Key来定位更新的记录。
      

  6.   

    to: ganwen() 
      代码网上应该有很多的,我的代码很乱,很难整理啊,用的控件我上面已经列出来了,或者你看看Delphi的帮助吧。再详细深入些我也不行了,不然就不会有这一问了。to: ben_jiang(木头)
      写一个存储过程应该可以,但我的程序已经写好了,并且在用,只是偶尔出现这种问题,不想大改。另外,Delphi既然提供了这套控件用,就应该可以应负这种情况,还是想将这个问题搞明白。
      

  7.   

    现在发现是这样,我的数据库中的表是有KEY的,但通过TADOQuery查询出来后,就没有KEY了。
    我改用TADOTable代替TADOQuery,TDataSetProvider.UpdateMode := upWhereKeyOnly,就可以了。
    这TADOQuery怎么给查询结果DataSet加上KEY呢?解决这个问题应该就可以了
      

  8.   

    怎么给打开的TDataSet加上KEY呢??或者是IndexField??
      

  9.   

    用TADOTable可以将表的Key约束带入数据集,我本想用TADOTable代替TADOQuery来解决这个问题的,但这样我的程序修改比较多,很麻烦,风险也大。
    现在我希望的正常解决办法是:
    一、用TADOQuery查询打开数据表时就能将表的Key约束带进来
    二、手工给已打开的TDataSet补加上Key约束
      

  10.   

    晕死打了一大堆, 没了, 简单写下了DSP, 设 UpdateMode, 楼上说得没错, 设得也没错query,  add all fields 后选中是 key 的字段, 在 ProviderFlags 中选上 pfInKey, 其它几个 provider 的意义看看也就明了, 
    死都不愿 add all fields 的人也是有的, 那在 dsp 的 BeforeUpdateRecord 事件中 SourceDS.FieldByName('xx').ProviderFlag 去改也是可以的
      

  11.   

    谢谢 comanche(太可怕) ,问题已几近解决了。
      人总是很贪心的,我还希望更简单些去解决。
      现在我不想对服务端软件进行任何修改,而希望只修改客户端软件去解决这个问题。
      经过试验,在服务端通过修改TADOQuery的ProviderFlag,确实可以解决问题,但如果我服务端不变,而在客户端修改TClientDataSet的ProviderFlag,更新时却还是出现没有Key的错误。
      但是我坚信在客户端可以实现,因为服务端的TADOQuery和TDataSetProvider是公用的,很多查询都使用同一套控件,而不会出现任何问题,证明这个Key信息应该保存在TClientDataSet中的,只是,单单设置ProviderFlag可能还是不行,不知如何设置???
      

  12.   

    晕了, 这样作法我是之前想都没想过的, 那个 key 信息并没有发送回客户机, 所以单从服务端完全不改的角度上看是不可能的, 不过 CDS 有个 SetOptionParam 和 GetOptionParm 可以用在客户机这边 CDS.SetOptionParam('KeyField', 'KeyFieldName', true)
    在服务端这边的 OnUpdateData 事件中写到
    var
      KeyField: string;
    begin
      KeyField := DataSet.GetOptionalParam('KeyField');
      (Sender as TDataSetProvider).DataSet.FieldByName(KeyField).ProviderFlag ...这个就能达到你要求的适应性, 不过失去了程序的可读性和结构性, 从三层设计看是应该从服务端去控制客户机行为的, midas 虽然是不完全实现, 但也提供了约束/字典等功能, provider 可以共用但 Query 就不能共用, 这些功能是在 TDataSet 级别上提供的
      

  13.   

    to: comanche(太可怕) 
     不是吧,应该所有信息都送回客户端了吧,因为我的服务端只有一套TADOQuery, TDataSetProvider,而我的客户端有N多个TClientDataSet,都从服务端取得数据集,而能互不干扰,如果信息存在服务端,怎能做到多个数据集互不干扰呢?
     象你上面的改法,不如就直接在服务端数据打开时设置ProviderFlag更方便,更直观易懂。
      

  14.   

    莫挣了,我对这些控件一点兴趣都莫得,平时,如果要有多用户的情况下,我一般都不会采用这些东东,直接用API了  :)
      

  15.   

    是呵, 为直观嘛, 当然是直接设的好咯
    的确没有回送任何 key 的信息, Table 不说, 这个知道哪个是 key, Query 在打开时 ProviderFlag 是全设的, 更新又大多人用的是 upWhereAll, 所以呵呵好像大多人都能成功.知道你的现实情况, 一般说用 Borland 向导生成代码的话, 第一个 RDM(remote datamodule) 或 TDM (transactional datamodule) 是相当相当简单的, 放个 Connection 好像问题就怎决了, 但项目(或者说目标)扩大后就会发现 TDM/RDM 上放满了 DSP/Query, 是吧..这时共用的需要就突出了(这是个值得讨论的头晕问题), 到了最后你就会发现服务端的代码是没有任何逻辑, 所有的东东都在一起, 无法区分对像逻辑, 并不像三层介绍的那样美妙(至少我是这么认为的)广泛观察一下其它实现的三层逻辑后, 就会发现全是 IAppServer 的问题, 这个东东忽略了对像的所有细节而用记录表示所有内容, 嗯, 业务层/逻辑层不见得全是数据库, 就是数据库他本身也是对像, 像记录的一行本身就是一个对像... 有事写不了了, 这个问题你可以开新帖大家说话嘛
      

  16.   

    三层系统主要优点之一是易于维护,实现部分应该主要在后台(AppServer, DBServer)进行,否则用2层岂不是更快。
      

  17.   

    我现在就是用的三层结构啊,客户端用DCOMConnection,连接到服务端的SocketServer,通过GUID连到我的数据服务模块,服务模块中提供数据提取函数(如:GetServerDataSet),通过传入SQL语句,由TADOQuery执行得到数据集,由TDataSetProvider.Data返回给客户端,再赋给TClientDataSet.Data。服务端只用一套TADOQuery和TDataSetProvider,而客户端多个TClientDataSet都是通过传入不同的SQL语句调用相同的数据提取函数GetServerDataSet。这种做法对一般的数据存取都没问题,只是在几个用户同时修改更新数据时出现问题(当然这种做法的更新方法只能是upAnyWhere)。 通过编小程序试验,在服务端的TADOQuery取得数据后,手工加入pfInKey,TDataSetProvider.UpdateMode设为upWhereKeyOnly,可以解决问题。因为服务端共用的一套控件而没有任何问题,所以我相信所有数据和约束都应该通过Data回传给了客户端的TClientDataSet了;
     如果服务端用TADOTable代替TADOQuery,然后TDataSetProvider.UpdateMode设为upWhereKeyOnly,也可以解决更新冲突问题。
     所以我想我的问题只是因为TADOQuery没有返回表的KEY,从而更新不成功,如果手工在客户端补加上KEY,这TClientDataSet就能和通过TADOTable返回的一样,或者象手工在服务器端增加KEY一样,从而能够更新成功。(也许我的概念不是很清楚,有些地方或许叫“中间层”,但相信大家都能明白的)
      

  18.   

    没有人知道如何给TClientDataSet加上KEY约束吗??
      

  19.   

    to: comanche(太可怕)
     恕我愚钝,我不太明白你的意思。难道我这不是三层结构?我听说这就是三层结构啊!
     不管怎么样,我想我现在如果能解决在TClientDataSet上手工加上KEY的问题,我的整个问题就好解决了。
      

  20.   

    又重看了 Provider 原码, 实在是找不出它在 Cds 里加入了任何的信息
      

  21.   

    但是,为什么我做的试验小程序,一套TADOQuery和TDataSetProvider(TDataSetProvider.UpdateMode设为upWhereKeyOnly),两个TClientDataSet,在TADODataSet用SQL打开表后,程序中手工给ProviderFlag加上 pfInKey,两个TClientDataSet打开后交叉更新(当然是通过TDataSetProvider来更新),都不会出现问题,又用第三个TADOQuery通过SQL形式修改表中的数据,也没问题。难道这两个TClientDataSet不是保存了KEY信息?那KEY保存在哪里?如保存在TDataSetProvider或TADOQuery中,另一个表打开时会什么不会被冲掉?
     源码我没看,只是从做的实验中猜测。
      

  22.   

    基本上答案已经在上面的回答中了
    首先:保证你的UpdateMode,是按主健更新;
    其次:你的CLIENTDATASET的各个字段的PROVIDERFLAGS要设定正确,特别是不要忘记了设主健;
    最后:比较重要的一点是D在这里有个小问题,客户端设的PROVIDERFLAG,设定不会被传到服务器端,这也是很多人莫名奇妙的原因,你最好自己想办法把它们传过去;如上面
     KeyField := DataSet.GetOptionalParam('KeyField');
      (Sender as TDataSetProvider).DataSet.FieldByName(KeyField).ProviderFlag 
    这一类的代码来实现;
      

  23.   

    我也遇到过楼猪的情况, 刚才开始也以为多用户更新冲突. 查了N个小时才知道, 原来数据库中有一个日期型字段, 我导入数据的时候用到了GetDate()函数, 结果在delphi中更新的时候就出错. 解决办法我就不说了, 不知道楼猪是不是这样的情况.
      

  24.   

    这面是我做实验的程序代码,其中的DataSetProvider1.UpdateMode设为:upWhereKeyOnlyunit Unit1;
    {表的定义:
    CREATE TABLE [TB1] (
    [aa] [int] NOT NULL ,
    [bb] [int] NULL ,
    [cc] [char] (32) COLLATE Chinese_PRC_CI_AS NULL ,
    [dd] [varchar] (256) COLLATE Chinese_PRC_CI_AS NULL ,
     PRIMARY KEY  CLUSTERED
    (
    [aa]
    )  ON [PRIMARY]
    ) ON [PRIMARY]CREATE TABLE [TB2] (
    [aa] [tinyint] NOT NULL ,
    [bb] [char] (16) COLLATE Chinese_PRC_CI_AS NULL ,
    [cc] [smallint] NULL ,
     PRIMARY KEY  CLUSTERED
    (
    [aa]
    )  ON [PRIMARY]
    ) ON [PRIMARY]
    }//{$define usetable}interfaceuses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls, DBClient, Provider, DB, ADODB;type
      TForm1 = class(TForm)
        ADOConnection1: TADOConnection;
        ADOQuery1: TADOQuery;
        DataSetProvider1: TDataSetProvider;
        ClientDataSet1: TClientDataSet;
        Button1: TButton;
        Edit1: TEdit;
        Button2: TButton;
        Button3: TButton;
        Button4: TButton;
        ClientDataSet2: TClientDataSet;
        Button5: TButton;
        ADOTable1: TADOTable;
        Button6: TButton;
        Button7: TButton;
        Button8: TButton;
        procedure Button1Click(Sender: TObject);
        procedure FormCreate(Sender: TObject);
        procedure Button2Click(Sender: TObject);
        procedure Button3Click(Sender: TObject);
        procedure Button4Click(Sender: TObject);
        procedure Button5Click(Sender: TObject);
        procedure Button7Click(Sender: TObject);
        procedure Button6Click(Sender: TObject);
        procedure Button8Click(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;var
      Form1: TForm1;implementation{$R *.dfm}procedure TForm1.FormCreate(Sender: TObject);
    begin
      ADOConnection1.Connected := True;
    end;//打开第一个表
    procedure TForm1.Button1Click(Sender: TObject);
    begin
    {$ifdef usetable}
      ADOTable1.TableName := 'TB1';
      DataSetProvider1.DataSet := ADOTable1;
    {$else}
      ADOQuery1.SQL.Clear;
      ADOQuery1.SQL.Add('select * from TB1 order by aa');
      ADOQuery1.Open;
      ADOQuery1.Fields[0].ProviderFlags := ADOQuery1.Fields[0].ProviderFlags + [pfInKey];
      DataSetProvider1.DataSet := ADOQuery1;
    {$endif}
      ClientDataSet1.Data := DataSetProvider1.Data;
    end;//修改第一个表
    procedure TForm1.Button2Click(Sender: TObject);
    begin
      if ClientDataSet1.RecordCount > 0 then
      begin
        ClientDataSet1.First;
        ClientDataSet1.Edit;
        ClientDataSet1.FieldByName('cc').AsString := FormatDateTime('hh:mm:ss', now);
        ClientDataSet1.Post;
      end;
    end;//更新第一个表
    procedure TForm1.Button3Click(Sender: TObject);
    var
      ie: Integer;
    begin
      if ClientDataSet1.ChangeCount > 0 then
      begin
        ADOQuery1.SQL.Text := 'select * from TB1 where 1=2 order by aa';
        DataSetProvider1.DataSet := ADOQuery1;
        DataSetProvider1.ApplyUpdates(ClientDataSet1.Delta, 0, ie);
        if ie=0 then
          ClientDataSet1.MergeChangeLog;
        Edit1.Text := IntToStr(ie);
      end;
    end;//打开第二个表
    procedure TForm1.Button4Click(Sender: TObject);
    var
      ii: Integer;
    begin
    {$ifdef usetable}
      ADOTable1.TableName := 'TB2';
      DataSetProvider1.DataSet := ADOTable1;
    {$else}
      ADOQuery1.SQL.Clear;
      ADOQuery1.SQL.Add('select * from TB2 order by aa');
      ADOQuery1.Open;
    //(1)BEGIN 有了这句,就怎么更新都可以了
      ADOQuery1.Fields[0].ProviderFlags := ADOQuery1.Fields[0].ProviderFlags + [pfInKey];
    //(1)END
      DataSetProvider1.DataSet := ADOQuery1;
    {$endif}
      ClientDataSet2.Data := DataSetProvider1.Data;
    {//(2)BEGIN 如果没有以上(1)语句,而增加下面这些语句,更新时都会因找不到KEY出错
      ClientDataSet2.IndexDefs.Add('aa','aa',[ixPrimary, ixUnique, ixDescending]);
      ClientDataSet2.Fields[0].ProviderFlags := ClientDataSet2.Fields[0].ProviderFlags + [pfInKey];
      ClientDataSet2.AddIndex('aaIndex', 'aa', [ixUnique], '');
      ClientDataSet2.IndexName := 'aaIndex';
    //(2)END}
      ii := ClientDataSet2.IndexFieldCount;
    end;//修改第二个表
    procedure TForm1.Button5Click(Sender: TObject);
    begin
      if ClientDataSet2.RecordCount > 0 then
      begin
        ClientDataSet2.First;
        ClientDataSet2.Edit;
        ClientDataSet2.FieldByName('bb').AsString := FormatDateTime('hh:mm:ss', now);
    //    ClientDataSet1.FieldByName('cc').AsString := 'abcd';
        ClientDataSet2.Post;
      end;
    end;//更新第二个表
    procedure TForm1.Button6Click(Sender: TObject);
    var
      ie: Integer;
    begin
        if ClientDataSet2.ChangeCount > 0 then
        begin
          ADOQuery1.SQL.Text := 'select * from TB2 where 1=2 order by aa';
          DataSetProvider1.DataSet := ADOQuery1;
          DataSetProvider1.ApplyUpdates(ClientDataSet2.Delta, 0, ie);
          if ie=0 then
            ClientDataSet2.MergeChangeLog;
          Edit1.Text := IntToStr(ie);
        end;
    end;//SQL直接修改第一个表
    procedure TForm1.Button7Click(Sender: TObject);
    begin
      ADOQuery1.SQL.Text := 'update TB1 set cc=''' + FormatDateTime('hh:mm:ss', now) + ''' where aa=1';
      ADOQuery1.ExecSQL;
    end;//SQL直接修改第二个表
    procedure TForm1.Button8Click(Sender: TObject);
    begin
      ADOQuery1.SQL.Text := 'update TB2 set bb=''' + FormatDateTime('hh:mm:ss', now) + ''' where aa=1';
      ADOQuery1.ExecSQL;
    end;end.如果使用ADOTable1打开表,或是用ADOQuery1打开,在打开后设置ProviderFlags,按什么顺序打开和更新这两个表,都一点问题都没有查询分析器查看数据也是正确的。但如果在ClientDataSet2中才设置ProviderFlags或再试图增加KEY就是不成功(即不用(1)语句而用(2)之类的语句)。
      

  25.   

    我的表并没有默认值啊,只有一个字段设为PRIMARY KEY,请看我上面的表定义