zoukankan      html  css  js  c++  java
  • 多层数据库应用基于Delphi DataSnap方法调用的实现(二)更新数据集

     传统的数据集的读取和更新,是通过中间层的TDataSetProvider来完成的。TDataSetProvider负责从它上游的数据集读取数据生成Data包,再传给客户端;另一方面,在客户端提交更新时(TClientDataSet.ApplyUpdates),TDataSetProvider还负责解析上传的Delta包,并最终实现数据库的更新。现在在我们当前的方法调用方式下,不能再通过TDataSetProvider.ApplyUpdates来自动完成更新了,但是,我们还可以借用TDataSetProvider解析Delta数据包的功能,手动生成SQL语句来完成数据库的更新。手动更新看似复杂,实际上编码并不多,而且这种方式具有很大的灵活性,同时还解决了在传统方式下,多表联合查询不能完全自动更新的软肋。

    下面来看看示例代码:

    中间层代码

    ...
    interface

    uses
      SysUtils, Classes, DSServer, DBXOracle, FMTBcd, DB, SqlExpr, WideStrings, Provider,
      CodeSiteLogging, DBClient;

    type
    {$METHODINFO ON}
      TServerMethods1 = class(TDataModule)
        SQLConnection1: TSQLConnection;
        SQLDataSet1: TSQLDataSet;
        DataSetProvider1: TDataSetProvider;
        procedure DataSetProvider1BeforeUpdateRecord(Sender: TObject; SourceDS: TDataSet;
          DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind; var Applied: Boolean);
      private
        { Private declarations }
        FOnDeltaRecordUpdate: TBeforeUpdateRecordEvent;
        procedure UpdateEmployeesDelta(Sender: TObject; SourceDS: TDataSet;
        DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind;
          var Applied: Boolean);
      public
        { Public declarations }
        function GetEmployeeFullName(EmployeeId: Integer): string;
        function GetEmployees: TDataSet;
        function UpdateEmployees(EMPDelta: OleVariant): Boolean;
      end;
    {$METHODINFO OFF}
    ...

    implementation

    ...
    procedure TServerMethods1.DataSetProvider1BeforeUpdateRecord(Sender: TObject; SourceDS: TDataSet;
      DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind; var Applied: Boolean);
    begin
      if Assigned(FOnDeltaRecordUpdate) then
        FOnDeltaRecordUpdate(Sender, SourceDS, DeltaDS, UpdateKind, Applied);
    end;

    procedure TServerMethods1.UpdateEmployeesDelta(Sender: TObject; SourceDS: TDataSet;
      DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind; var Applied: Boolean);
    var
      SQLStr, Clause1, Clause2: string;
      I: Integer;
    begin
      Applied := False;

      case UpdateKind of
        {$REGION 'Process ukModify'}
        ukModify:
        begin
          with DeltaDS do
          begin
            for I := 0 to Fields.Count - 1 do
              if not Fields[I].IsNull then
                Clause1 := Clause1 + ', ' + Fields[I].FieldName + '=:' + Fields[I].FieldName;
            Clause1 := System.Copy(Clause1, 2, Length(Clause1) - 1);
            SQLStr := 'UPDATE EMPLOYEES SET ' + Clause1 + ' WHERE EMPLOYEE_ID = :EMPLOYEE_ID';
          end;
          with SQLDataSet1 do
          begin
            Close;
            CommandText := SQLStr;
            for I := 0 to DeltaDS.Fields.Count - 1 do
              if not DeltaDS.Fields[I].IsNull then
                ParamByName(DeltaDS.Fields[I].FieldName).Value := DeltaDS.Fields[I].Value;
            ParamByName('EMPLOYEE_ID').AsInteger := DeltaDS.FieldByName('EMPLOYEE_ID').OldValue;
            if (ExecSQL() = 0) then Abort;
          end;
        end;
        {$ENDREGION}
        {$REGION 'Process ukInsert'}
        ukInsert:
        begin
          for I := 0 to DeltaDS.Fields.Count - 1 do
          begin
            if not DeltaDS.Fields[I].IsNull then
            begin
              Clause1 := Clause1 + ', ' + DeltaDS.Fields[I].FieldName;
              Clause2 := Clause2 + ', :' + DeltaDS.Fields[I].FieldName;
            end;
          end;
          Clause1 := System.Copy(Clause1, 2, Length(Clause1) - 1);
          Clause2 := System.Copy(Clause2, 2, Length(Clause2) - 1);
          SQLStr := 'INSERT INTO EMPLOYEES(' + Clause1 + ') VALUES(' + Clause2 + ')';
          with SQLDataSet1 do
          begin
            Close;
            CommandText := SQLStr;
            for I := 0 to DeltaDS.Fields.Count - 1 do
              if not DeltaDS.Fields[I].IsNull then
                ParamByName(DeltaDS.Fields[I].FieldName).Value := DeltaDS.Fields[I].Value;
            if (ExecSQL() = 0) then Abort;
          end;
        end;
        {$ENDREGION}
        {$REGION 'Process ukDelete'}
        ukDelete:
        begin
          SQLStr := 'DELETE EMPLOYEES WHERE EMPLOYEE_ID = :EMPLOYEE_ID';
          with SQLDataSet1 do
          begin
            Close;
            CommandText := SQLStr;
            ParamByName('EMPLOYEE_ID').AsInteger := DeltaDS.FieldByName('EMPLOYEE_ID').OldValue;
            if (ExecSQL() = 0) then Abort;
          end;
        end;
        {$ENDREGION}
      end;

      Applied := True;
    end;

    function TServerMethods1.UpdateEmployees(EMPDelta: OleVariant): Boolean;
    var
      ErrorCount: Integer;
    begin
      //指定Employees的专有更新过程
      FOnDeltaRecordUpdate := UpdateEmployeesDelta;
      DataSetProvider1.ApplyUpdates(EMPDelta, 0, ErrorCount);
      Result := ErrorCount = 0;
    end;

    1、增加一个DataSetProvider1控件,让它与SQLDataSet1控件关联。
    2、申明一个事件句柄FOnDeltaRecordUpdate: TBeforeUpdateRecordEvent,然后在DataSetProvider1的BeforeUpdateRecord事件过程中,再嵌套一个TBeforeUpdateRecordEvent事件。
    3、申明和定义一个私有过程UpdateEmployeesDelta,其参数定义遵循TBeforeUpdateRecordEvent事件过程的定义,此过程专门用来处理Employees的更新。实际上,所有表的更新只有Update、Insert和Delete三种类型,针对当前传入的Delta包,可以手动构造出SQL语句。这里只有具有的表不一样,但处理流程都是完全一样的,所以,若有其他新表的更新,可以直接套用相关代码段,甚至可以做成一个专门的一个通用过程。
    4、申明和定义UpdateEmployees函数,此方法是暴露给客户端进行调用的。若客户端有多个表需要在一个事务中完成更新,则可以在此过程中手动启动事务,分别完成多个表Delta数据包的更新,最后提交或rollback。若有新的方法暴露给客户端来更新其他表的时候,只需要相应地再创建一个私有更新过程,并把此过程指针赋给FOnDeltaRecordUpdate即可。

    客户端代码:

        通过在客户端中的TSQLConection控件,可以生成DataSnap client class:

    ...

      TServerMethods1Client = class
      private
        FDBXConnection: TDBXConnection;
        FInstanceOwner: Boolean;
        FEchoStringCommand: TDBXCommand;
        FReverseStringCommand: TDBXCommand;
        FGetEmployeeFullNameCommand: TDBXCommand;
        FGetEmployeesCommand: TDBXCommand;
        FGetJobsCommand: TDBXCommand;
        FUpdateEmployeesCommand: TDBXCommand;
      public
        constructor Create(ADBXConnection: TDBXConnection); overload;
        constructor Create(ADBXConnection: TDBXConnection; AInstanceOwner: Boolean); overload;
        destructor Destroy; override;
        function GetEmployeeFullName(EmployeeId: Integer): string;
        function GetEmployees: TDataSet;
        function UpdateEmployees(EMPDelta: OleVariant): Boolean;
      end;
    ...

      在调用模块中,比如一个按钮事件中完成Employees被更新数据的提交,其中FMethodProxy为TServerMethods1Client的实例:

    ...

    procedure TForm2.Button2Click(Sender: TObject);
    begin
      if ClientDataSet1.ChangeCount > 0 then
      begin
        FMethodProxy.UpdateEmployees(ClientDataSet1.Delta);
      end;
    end;

    ...

    通过上述方式,中间层业务类模块,只需要三个控件,就可以完成现在和今后所有的数据读取和更新操作。更重要的是,由于每次方法调用之间是不需要客户端到中间层、中间层到数据库的两个连接始终保持并专用的,在实际调用方法的时候才需要连接。但在上述例子中,我们的类工厂(TDSServerClass)的LifeCycle属性采用的是缺省的Session方式,也就是说,一旦客户端请求了一次方法调用,则此客户端与中间层、中间层与数据库的连接就一直保持着,并且只能为此客户端专用,直到此客户端关闭连接为止。客户端与中间层的连接先不说,中间层与数据库的连接也是专用的话,那数据库连接资源的使用效率就太差了。为了解决此问题,我们可以在中间层引用对象池的机制。后文将关注对象池的问题,敬请等待续集。

  • 相关阅读:
    CMD命令行netsh添加防火墙规则
    C# 编写windows服务及服务的安装、启动、删除、定时执行任务
    C#释放资源文件dll或exe
    .net core 2.1 Razor 超快速入门
    正则表达式的先行断言(lookahead)和后行断言(lookbehind)
    C#从IE缓存读取图片
    实现ppt幻灯片播放倒计时
    C#自动化操作IE浏览器系列之一打开新的浏览器并导航到百度进行搜索
    MT【188】一个正切余切有关的恒等式
    MT【187】余弦的线性组合
  • 原文地址:https://www.cnblogs.com/jieke/p/3389705.html
Copyright © 2011-2022 走看看