在中间层BeforeUpdateRecord事件中,修改了Delta的内容(用于自动生成主键),并设置Applied为false,成功更新了数据库,但如何让客户端保持同步(不通过FetchAll或close+open),回答正确给1000分

解决方案 »

  1.   

    通过OwnerData传递返回值ClientDataSet1.Edit;
    ClientDataSet1.FieldByName('Field1').Value:=OwnerData;
    ClientDataSet1.Post;
    ClientDataSet1.MergeChangeLog;
      

  2.   

    假如有很多地方需要在应用服务器上修改,是否需要将整个修改后的Delta回传给客户端,给一给代码参考,不胜感激
      

  3.   

    不知道你的应用是怎么样的,我通常只把DataSetProvider对象的Options属性
    包含poPropogateChanges,VCL会自动把修改的东西传回Client的ClientDataSet,无需代码。
      

  4.   

    you can refresh tclientdataset,dataset after your update.But to make another client get the new data,you must refresh it too.I think there are no method to inform all client that the data has been changed. So the try block is very important when the mulities is used in a system.sorry for I can not write in Chinese now.
      

  5.   

    请注意,字段对象的NewValue、OldValue的用法!!!!!看看帮助怎么讨论的,你就会明白了。
    再给你一个技巧,VarIsEmpty函数判断是不是客户端修改并提交了值。NULL不能表明的,给你我的一个程序片断:是服务器的程序,
    function TApplyTableTable.BeforeInsert(SourceDS: TDataSet; DeltaDS: TCustomClientDataSet):Boolean;
    begin
      Result:=inherited BeforeInsert(SourceDS,DeltaDS);
      DeltaDS.FieldByName('ApplyDate').NewValue:=Now;
      DeltaDS.FieldByName('ApplyMan').NewValue:=CurUserID;
      Owner.Users.AddLink(Self,CurUserID);
      DeltaDS.FieldByName('CheckMan').NewValue:=NULL;
      if not VarIsEmpty(DeltaDS.FieldByName('EquipmentID').NewValue) then
        if (not VarIsNull(DeltaDS.FieldByName('EquipmentID').NewValue)) then
        begin
          if not Owner.AssetsData.CheckID(DeltaDS.FieldByName('EquipmentID').NewValue) then
            raise Exception.Create(ErrAddAssets);
        end else
          raise Exception.Create(ErrAddAssets);
    end;function TApplyTableTable.BeforeModify(SourceDS: TDataSet; DeltaDS: TCustomClientDataSet):Boolean;
    begin
      Result:=inherited BeforeModify(SourceDS,DeltaDS);
      if not VarIsEmpty(DeltaDS.FieldByName('EquipmentID').NewValue) then
        if (not VarIsNull(DeltaDS.FieldByName('EquipmentID').NewValue)) then
        begin
          if not Owner.AssetsData.CheckID(DeltaDS.FieldByName('EquipmentID').NewValue) then
            raise Exception.Create(ErrAddAssets);
        end else
          raise Exception.Create(ErrAddAssets);
      if not VarIsEmpty(DeltaDS.FieldByName('Status').NewValue) then
      begin
        if (DeltaDS.FieldByName('Status').NewValue=2) then
        begin
          if VarIsEmpty(DeltaDS.FieldByName('EquipmentID').NewValue) then
          begin
            if not VarIsNull(DeltaDS.FieldByName('EquipmentID').OldValue) then
            begin
              if not Owner.AssetsData.CheckID(DeltaDS.FieldByName('EquipmentID').OldValue) then
                raise Exception.Create(ErrAddAssets)
              else
                Owner.AssetsData.AddLink(Self,DeltaDS.FieldByName('EquipmentID').OldValue);
            end else
              raise Exception.Create(ErrAddAssets);
          end else
            Owner.AssetsData.AddLink(Self,DeltaDS.FieldByName('EquipmentID').NewValue);
          DeltaDS.FieldByName('CheckDate').NewValue:=Now;
          if VarIsValid(DeltaDS.FieldByName('CheckMan').OldValue) and (DeltaDS.FieldByName('CheckMan').OldValue<>CurUserID) then
          begin
            Owner.Users.DelLink(Self,DeltaDS.FieldByName('CheckMan').OldValue);
            Owner.Users.AddLink(Self,CurUserID);
          end;
          DeltaDS.FieldByName('CheckMan').NewValue:=CurUserID;
        end else if (DeltaDS.FieldByName('Status').NewValue=1) then
        begin
          if VarIsEmpty(DeltaDS.FieldByName('EquipmentID').NewValue) then
            if not VarIsNull(DeltaDS.FieldByName('EquipmentID').OldValue) then
            begin
              if not Owner.AssetsData.CheckID(DeltaDS.FieldByName('EquipmentID').OldValue) then
                raise Exception.Create(ErrAddAssets);
            end else
              raise Exception.Create(ErrAddAssets);
          DeltaDS.FieldByName('ApplyDate').NewValue:=Now;
          if VarIsValid(DeltaDS.FieldByName('CheckMan').OldValue) then
          begin
            Owner.Users.DelLink(Self,DeltaDS.FieldByName('CheckMan').OldValue);
          end;
          DeltaDS.FieldByName('CheckMan').NewValue:=NULL;
          if VarIsValid(DeltaDS.FieldByName('ApplyMan').OldValue) then
          begin
            if (DeltaDS.FieldByName('ApplyMan').OldValue<>CurUserID) then
            begin
              Owner.Users.DelLink(Self,DeltaDS.FieldByName('ApplyMan').OldValue);
              Owner.Users.AddLink(Self,CurUserID);
            end;
          end else
            Owner.Users.AddLink(Self,CurUserID);
          DeltaDS.FieldByName('ApplyMan').NewValue:=CurUserID;
        end else if (DeltaDS.FieldByName('Status').NewValue=3) then
        begin
          DeltaDS.FieldByName('CheckDate').NewValue:=Now;
          if VarIsValid(DeltaDS.FieldByName('CheckMan').OldValue) and (DeltaDS.FieldByName('CheckMan').OldValue<>CurUserID) then
          begin
            Owner.Users.DelLink(Self,DeltaDS.FieldByName('CheckMan').OldValue);
            Owner.Users.AddLink(Self,CurUserID);
          end;
          DeltaDS.FieldByName('CheckMan').NewValue:=CurUserID;
        end else
        begin
          if VarIsValid(DeltaDS.FieldByName('CheckMan').OldValue) then
          begin
            Owner.Users.DelLink(Self,DeltaDS.FieldByName('CheckMan').OldValue);
          end;
          DeltaDS.FieldByName('CheckMan').NewValue:=NULL;
          DeltaDS.FieldByName('ApplyDate').NewValue:=Now;
          if VarIsValid(DeltaDS.FieldByName('ApplyMan').OldValue) then
          begin
            if (DeltaDS.FieldByName('ApplyMan').OldValue<>CurUserID) then
            begin
              Owner.Users.DelLink(Self,DeltaDS.FieldByName('ApplyMan').OldValue);
              Owner.Users.AddLink(Self,CurUserID);
            end;
          end else
            Owner.Users.AddLink(Self,CurUserID);
          DeltaDS.FieldByName('ApplyMan').NewValue:=CurUserID;
        end;
      end;
    end;function TApplyTableTable.BeforeDelete(SourceDS: TDataSet; DeltaDS: TCustomClientDataSet):Boolean;
    begin
      Result:=inherited BeforeDelete(SourceDS,DeltaDS);
      if not Result then
      begin
        if VarIsValid(DeltaDS.FieldByName('CheckMan').NewValue) then
          Owner.Users.DelLink(Self,DeltaDS.FieldByName('CheckMan').NewValue);
        if VarIsValid(DeltaDS.FieldByName('ApplyMan').NewValue) then
          Owner.Users.DelLink(Self,DeltaDS.FieldByName('ApplyMan').NewValue);
      end;
    end;
      

  6.   

    对不起,没看清楚你的题目,我说的那种情况时在Applied设为True的情况,
    Applied设为False的情况没试过,Sorry
      

  7.   

    我是提问者,非常感谢大家参与,我所说的那种情况主要是用了二层(当然还有更多层的情况)的巢状数据(也就是通常的主从结构),在客户端生成新的主从记录的时候我并没有生成主键来关联主从关系的每个主从表(虽然这样,但是Delphi在依然可以关联它们,^_^),现在我要在应用程序服务器上在BeforeUpdateRecord的事件UpdateKind为ukInsert的时候生成主键,主表的主键好生成,再通过主表的DataSetField的NestedDataSet来访问从表,遍历它们更新它与主表关联的键,但现在问题出来了,如何在客户端刷新它们呢(通过再传递Delta到客户端吗,客户端再MergeChangeLog吗,这个Delta如何取出来,因为在UpdateRecord中,SourceDS为相应的要修改的数据集,而Delphi只有在OnUpdateData事件中可以取出整个客户端要更新的Delta,要是有一个AfterUpateData事件就好)
      另外,有人问应用服务器更新了就可以,为什么还要传递到客户端呢,我假设有一笔数据要提交,在中间层生成主键,日期,我要在应用服务器上更新成功后,在客户端并打印出来,我肯定需要回传这笔数据
      

  8.   

    3种办法:1、服务器端建立主从关系,这样做还有一个好处,更新都在主表的对应的Provider的事件中,Provider可以选择级连更新,这样在客户端无论是住表还是从表提交,都会同时更新这两个表,并且返回正确的值,而且这种更新是在一个事务中的。缺点是,服务器每次都要为每一个主表的记录生成从表,检索的数据量很大。具体设置,服务器端仅仅设置普通的主从关系,客户端的主表addField会发现多了一个DataSetField类型的字段,这就是对应的从表,从表的不要设置任何RemotServer和Provider,仅仅设置DataSetField为那个字段就可以了。2、在客户端建立主从关系,我们讨论最通用的情况,不考虑在服务器端使用BDE时的特殊情况。客户端的主从表无论谁更改都要同时更新,需要自己扩展服务器端,提供同时提交两个数据包的ApplyUpdata的方法,客户端同时提交两个更新包,这个更新包用ClientDataSet.Delta,服务器端在一个事务中分别用对应的Provider提交送来的更新包,DataSetProvider.ApplyUpdate返回值就是更新过后的Delta数据包,回传到客户端以后,用ClientDataSet.Reconcile处理这些返回的包就可以同步数据了。3、主表更新以后同时返回从表的记录集,放在OwnerData中,这个数据包的格式比较难弄,你可以用对应从表的Provider生成一个,客户端获得以后,可以给Data属性赋值。
      

  9.   

    To BlueTrees:首先你的第一个方法,并不是方法,我本来就是这样设置的,并且有三个嵌,你并没有提出令人信服的解决方案
    第二个方法并不十分理想,增加程序的复杂性,并且出现错误到底处理ReconcileError的时候并不好处理,你逃避了本质性的问题,我在服务端修改Delta,如何回传这个Delta(看来我只有这样做了)
    第三个如果返回从表的记录集,那么网络的传输量你考虑过吗(假设有10000......条 ),另外三层关联,四层关联怎么办
      

  10.   

    对于你的质疑,我无话可说。如果你觉得一次返回所有的数据网络承受不了,那么你就需要自己扩展服务器端了,对于Com+的服务器端需要的代码是很多的,因为Com+服务器不能保留中间状态,那么你不想一次返回全部数据,那么必须自己写代码,最好自己写Provider。我的建议:使用第二个方案,不会增加程序的复杂性,多表更新控制在一个事务中的代码十分简单,Delphi5开发人指南上面的例子,大概60行,我自己写了一对组件用于多表更新,全部代码大概200行。事实上,Delphi没有提供多个表的更新在一个事务中完成的办法,必须自己扩展。我自己写的组件,完全兼容ReconcileError的处理,一点问题都没有。你觉得麻烦,只是你比较懒。而且这个方法,按照开发人员指南的说法,客户端的主从表关系是服务器压力最小的。这个方法对于将来有可能的控制网络流量而进行的工作的影响是最小的。如果你觉得我的建议不值一提,那么我不再说了。
      

  11.   

    还有,关联层次过多是你设计不恰当造成的,你应当在某些时候使用LookUp字段,计算字段来减少关联,通常的关联只有2层,更多的关联应该用LookUp字段和计算字段联合处理。
      

  12.   

    小弟去年刚从c/s转到midas上来,虽然不太熟midas,但是也有遇到master/detail的情况,
    也是要处理由应用服务器端生成主键值,补全客户端没有填完整的字段值,也都是在BeforeUpdateRecord里处理,今晚试了一下Applied := False的情况(只试了UpdateKind = ukInsert的情况),也和Applied := True的情况没有什么异样。VCL也是自动将变动反馈给
    Reconcile例程啊。我还特意看了一下Provider单元的代码,PropogateChanges好像和Applied没什么关系,你只要往字段NewValue属性赋值就行了。实在不知道你们遇到了什么麻烦。不过如果你的需求确实解决不了,可以用蜗牛大佬的第二个方案,简单、可靠,易于控制,就像在搞C/S的时候干的那样。至于蜗牛大老说的"关联层次过多是你设计不恰当造成的,你应当在某些时候使用LookUp字段,计算字段来减少关联",我不是很理解。我的数据库结构中也有3套表的嵌套情形达到了
    3层的,不知道有什么好的办法能解决。说实在,3层主从关系处理起来其实挺麻烦的。不知道
    蜗牛大老所说的“某些时候使用LookUp字段,计算字段来减少关联”,具体是什么办法。因为今晚出去玩, 再加上搞这个Applied := True,看VCL代码,现在已经很晚了,又喝了一点酒,明天再请教两位大佬吧。以下是今晚测试所用到的RemoteDataModule的单元的简单代码。
      

  13.   

    type
      TIDMapping = class(TObject)
        ClientID, ServerID: Integer;
      end;  TIDPooler = class(TObject)
      private
        fList: TList;
      public
        destructor Destroy; override;
        
        procedure AddIDPair(const aClientID, aServerID: Integer);
        function FindByClient(const aClientID: Integer): TIDMapping;
        procedure Clear;
      end;  TTestMasterDetail = class(TRemoteDataModule, ITestMasterDetail)
        procedure DataSetProvider1BeforeUpdateRecord(Sender: TObject;
          SourceDS: TDataSet; DeltaDS: TCustomClientDataSet;
          UpdateKind: TUpdateKind; var Applied: Boolean);
        procedure RemoteDataModuleCreate(Sender: TObject);
        procedure RemoteDataModuleDestroy(Sender: TObject);
        procedure DataSetProvider1BeforeApplyUpdates(Sender: TObject;
          var OwnerData: OleVariant);
      private
        { Private declarations }
        fIDPooler: TIDPooler;    //generate id for t_master.id
        function GetID: Integer;
        //generate id for t_detail.id2
        function GetID2: Integer;
      protected
        class procedure UpdateRegistry(Register: Boolean; const ClassID, ProgID: string); override;
      public
        { Public declarations }
      end;implementation{$R *.DFM}class procedure TTestMasterDetail.UpdateRegistry(Register: Boolean; const ClassID, ProgID: string);
    begin
      if Register then
      begin
        inherited UpdateRegistry(Register, ClassID, ProgID);
        EnableSocketTransport(ClassID);
        EnableWebTransport(ClassID);
      end else
      begin
        DisableSocketTransport(ClassID);
        DisableWebTransport(ClassID);
        inherited UpdateRegistry(Register, ClassID, ProgID);
      end;
    end;{
      table structures (mssql 2000 style):
      ----------------------------------
      create table t_id (
        id_name       varchar(16)       not null        primary key,
        id_value      integer           not null
      )
      go  insert into t_id values ( 'T_MASTER' , 1 )
      insert into t_id values ( 'T_DETAIL' , 1 )
      go  create table t_master (
        id            integer           not null        primary key,
        create_date   datetime          not null,
        last_modify_date  datetime      not null
      )
      go  create table t_detail (
        id            integer           not null,
        id2           integer           not null,
        optional_field  varchar(10)             ,
        primary key ( id, id2 )
        --, constraint fk_detail foreign key ...
      )
      go  sql code of p_gen_id , no exception check :)
      ----------------------------------  create procedure p_gen_id
          @id_name        varchar(16),
          @id_value       integer             output
      as
        select @id_value = id_value from t_id where id_name = UPPER(@id_name)
        update t_id set id_value = id_value + where id_name = UPPER(@id_Name)
      go
    }function TTestMasterDetail.GetID: Integer;
    begin
      ADOStoredProc1.Parameters.ParamByName('@id_name').Value := 'T_MASTER';
      ADOStoredProc1.ExecProc;
      Result := ADOStoredProc1.Parameters.ParamValues['@id_value'];
    end;function TTestMasterDetail.GetID2: Integer;
    begin
      ADOStoredProc1.Parameters.ParamByName('@id_name').Value := 'T_DETAIL';
      ADOStoredProc1.ExecProc;
      Result := ADOStoredProc1.Parameters.ParamValues['@id_value'];
    end;procedure TTestMasterDetail.RemoteDataModuleCreate(Sender: TObject);
    begin
      fIDPooler := TIDPooler.Create;
      ADOStoredProc1.Parameters.ParamByName('@id_value').Direction := pdOutput;
    end;procedure TTestMasterDetail.RemoteDataModuleDestroy(Sender: TObject);
    begin
      fIDPooler.Free;
    end;procedure TTestMasterDetail.DataSetProvider1BeforeApplyUpdates(
      Sender: TObject; var OwnerData: OleVariant);
    begin
      fIDPooler.Clear;
    end;procedure TTestMasterDetail.DataSetProvider1BeforeUpdateRecord(
      Sender: TObject; SourceDS: TDataSet; DeltaDS: TCustomClientDataSet;
      UpdateKind: TUpdateKind; var Applied: Boolean);
    var
      ClientID, ServerID: Integer;
      IDMapping: TIDMapping;
      DetailDataSet: TDataSet;  function IsMasterTable: Boolean; //overwrite this function
      var
        I: Integer;
      begin
        for I := 0 to DeltaDS.FieldCount-1 do
          if DeltaDS.Fields[I] is TDataSetField then
          begin
            Result := True;
            Exit;
          end;    Result := False;
      end;begin
      Applied := False;  case UpdateKind of
        ukInsert:
        begin
          if IsMasterTable then
          begin
            ClientID := DeltaDS.FieldByName('id').OldValue;
            if fIDPooler.FindByClient(ClientID) = nil then
            begin
              ServerID := GetID;
              DeltaDS.FieldByName('id').NewValue := ServerID;
              DeltaDS.FieldByName('create_date').NewValue := Now;
              fIDPooler.AddIDMapping(ClientID, ServerID);
            end;
          end
          else
          begin
            ClientID := DeltaDS.FieldByName('id').OldValue;
            IDMapping := fIDPooler.FindByClient(ClientID);
            if IDMapping = nil then
            begin
              //only insert detail record
              //the id field value is ready, simply ignore it
            end
            else
              DeltaDS.FieldByName('id').NewValue := IDMapping.ServerID;
              
            //assume one applyudpates only one master
            DeltaDS.FieldByName('id2').NewValue := GetID2;
          end;
        end;    ukModify:
        begin
          //assume update primary key is not permitted, so only update the field of last_modify_date
          if IsMasterTable then DeltaDS.FieldByName('last_modify_date').NewValue := Now;
        end;    ukDelete:
        begin
          //
        end;
      end;
    end;{ TIDPooler }procedure TIDPooler.AddIDMapping(const aClientID, aServerID: Integer);
    var
      IDMapping: TIDMapping;
    begin
      if fList = nil then fList := TList.Create;
      IDMapping := TIDMapping.Create;
      IDMapping.ClientID := aClientID; IDMapping.ServerID := aServerID;
      fList.Add(IDMapping);
    end;procedure TIDPooler.Clear;
    begin
      if fList <> nil then
      begin
        while fList.Count > 0 do
        begin
          TIDMapping(fList[0]).Free;
          fList.Delete(0);
        end;
      end;
    end;destructor TIDPooler.Destroy;
    begin
      Clear;
      fList.Free;
      inherited;
    end;function TIDPooler.FindByClient(const aClientID: Integer): TIDMapping;
    var
      I: Integer;
    begin
      if fList <> nil then
      begin
        for I := 0 to fList.Count-1 do
        begin
          Result := TIDMapping(fList[I]);
          if Result.ClientID = aClientID then Exit;
        end;
      end;
      Result := nil;
    end;
      

  14.   

    To All:我所讲的多层关联并不是指lookup字段,我从来不再服务器端设置Lookup字段我都是再客户端来设置,我所指的是Master-Detail-SubDetail等,我不明白为你什么说在客户端设置主从关系是压力最小的,它的传送量虽小但它传送了两次, 加起来还不是一样但到底是哪一种方法好,我再测试一下,
    To DbExpress,Applied设为True,是指我已经修改了数据不需要Provider再去修改,我设为false的原因是我只是修改了Delta,并没有修改后端数据,还是需要Provider去提交,
    非常感谢大家对我的帮助与参与,,也许我有用辞不当的地方,大家互相学习是我们最终的目的 Bye(我上网时间比较少,现在才回)
      

  15.   

    服务器端主从关系的情况,传送的数据量:主表+从表的符合条件数据
    计算量:主表的纪录数量*从表的记录数,如果存在第三个从表,那么,还要*第三个从表的记录数量。因为每次主表请求数据的时候,都要检索一遍从表并且将符合条件的从表记录集打包。客户端主从关系的情况传送的数据量:主表+从表的符合条件的数据,(从表的PacketRecords设置为0)
    计算量:一条增加了Where的SQL语句检索从表,计算压力全部交给了DBMS,DBMS做SQL检索事很快的。还有,在服务器端的设置的NewValue,如果要回传客户端,那么对应的Provider的Options属性当中包含poPropogateChanges,包含poAutoRefresh在很多情况下没有用处,老的版本的delphi这个选项是无效的。poPropogateChanges总是能够回传服务器端做的修改。
      

  16.   

    关于Applied你说得不错,我测试的正是你的Applied := False的情况。
    你为什么不看看我的代码是否这样做了呢?蜗牛大佬也回复了4次,挺长的。而且他的方案都是可行的,有实际应用环境的,也可以说是经典的处理方法,大佬您和我现在使用的都是他说的第一套方案,至于说Delta的回传,就是昨晚小弟所为您效劳的。对于MIDAS的Provider/Resolver Architecture,不知大家是否同意,它本来也只是面对简单应用的,用户应该根据实际情况确定具体的实现方法。如果你要使你的服务器适应更高负荷的工作,你有必要微调这个P/R架构,做大量细致的工作,限制客户端的操作... ...
    但是你不能向一个解决方案提出超出它胜任范围的要求。不能凭空地说多少层嵌套,多少万条从表纪录。
      

  17.   

    Thank to BlueTrees and dbExpress