Delphi MIDAS 的重大BUG ---DelphiBBS

Delphi MIDAS 的重大BUG ---DelphiBBS (2006-04-17 13:17:52)
目的:
   三层结构中客户端取数一般是采用
     TClientDataSet  =====>  TDataSetProvider + TDataSet
   有些人使用TDataSetProvider + TTable,然后在设计期间设置TTable的属性
   缺点很明显:
       1、不能使用SQL实现复杂的查询
       2、TTable的属性在设计期间完全确定了,也就只能只能打开固定的一个表,
          要使用多个表怎么办?只好在RemoteDataModule中放无数的TDataSetProvider和TTable这种做法不能满足我们的要求,我们需要的是:
     1、在Client端使用SQL语句打开数据集
     2、Provider应该能访问任意数据,
        最好做到多个ClientDataSet同时使用一个Provider访问不同的数据Delphi的帮助里没有例子,不过我们还是很容易就找到了实现方法:
     Server端:TDataSetProvider + TQuery
     Client端:TClientDataSet
     // for Delphi 5:
         TDataSetProvider.Options属性中应该包括poAllowCommandText,
         允许ClientDataSet使用SQL命令
     Client端程序:
     with ClientDataSet do
     begin
       Close;
       CommandText := 'Select xxx ......';
       Open;
     end;
     // for Delphi 4:
     手头找不到D4了,因此不知道D4是否支持TClientDataSet.CommandText,
     我们以前是这样做的:
     Client端程序:
     with ClientDataSet do
     begin
       Close;
       DataRequest('Select xxx ......');
       Open;
     end;
     Server端程序:
     TDataSetProvider.OnDataRequest(Sender: TObject; Input: OleVariant): OleVariant;
     begin
       // 这里的Input就是客户端的ClientDataSet调用DataRequest方法时传递的参数
       // 将它赋值给Query.SQL.Text就可以了
       Query.SQL.Text := Input;
     end;
     好了,用上面的方法我们在Client端使用SQL了,也使得Provider与数据集无关,
     Server端只需要公布一个Provider就可以供Client端的多个ClientDataSet使用了,看起来真不错,问题似乎得到了完美的解决  :))
---------------------------------------------------------------------
发现了BUG: :<<<
  按上面的方法,Server端使用一个DataSetProvider
  Client端使用两个TClientDataSet,都使用Server端的同一个Provider,分别打开两个表,如下:
  with ClientDataSet1 do
  begin
    Close;
    CommandText := 'Select * from Table1';
    Open;
  end;
  with ClientDataSet2 do
  begin
    Close;
    CommandText := 'Select * from Table2';
    Open;
  end;

  上面的代码中ClientDataSet与数据库表的对应关系如下:
     ClientDataSet1  =====>  "Table1"
     ClientDataSet2  =====>  "Table2"

  注意顺序为先打开表1,再打开表2
  然后我们修改ClientDataSet1中的数据,再调用
     ClientDataSet1.ApplyUpdates(-1)方法提交数据,却发现无论如何都成功不了,奇怪!
  同样的数据直接在两层C/S结构的程序中可以成功地写入表中,可以肯定数据没问题
  程序绝对正确,同样的程序早就调试通过了,现在却突然不能提交数据了,而且,奇怪的是,错误很稳定,100%提交失败,系统重起N次也没影响
---------------------------------------------------------------------
调试:
  程序和数据都没问题,实在想不出错在哪里,只好用最低级的方式了,
  打开M$ SQL Trace,睁大眼睛看提交到SQL Server上的所有SQL语句,然后,在三层结构下提交数据、在两层C/S结构下提交数据,比较SQL语句有什么不同,这下更奇怪了,程序中Insert一条记录,ApplyUpdates后看到的SQL语句是
     insert into xxxx(xx,xx,...) values(xx,xx,...)
  拿出来执行,返回的错误信息居然是'invalid column name xxxxxx',字段不存在????
  Select * from xxxx 看到字段明明在那里嘛!
  难道SQL有毛病?? 删掉表,重建,再来一遍错误一模一样,奇怪!
  重复N遍之后,终于发现,Insert语句中操作的表居然是Table2!!!
  但是程序中打开的是Table1,这么说来,MIDAS在提交数据时操作的目标表错了,跟本就不是Client端实际使用的表!
---------------------------------------------------------------------
分析:
  打开Delphi 源代码 provider.pas ,搜索ApplyUpdates之类的东西,看究竟是如何实现的,最后看到了根源,如下:
  // Delphi 源代码 provider.pas
  procedure TDataSetResolver.DoUpdate(Tree: TUpdateTree);
  begin
    with Tree do
    begin
      ...
      Source.Edit;
      ...
    end;
  end;

  procedure TDataSetResolver.DoDelete(Tree: TUpdateTree);
  begin
    with Tree do
    begin
      ...
      Source.Delete
      ...
    end;
  end;

  procedure TDataSetResolver.DoInsert(Tree: TUpdateTree);
  begin
    Tree.Source.Append;
    ...
  end;

  其中TUpdateTree.Source定义为
  TUpdateTree = class(TObject)
    ...
    property Source: TDataSet read FSourceDS;
    ...
  end;

  水平有限,具体如何没看完,不过根据前面调试的结果,和上面的源代码结合起来,
  估计这个Source: TDataSet最终就等于TDataSetProvider.DataSet
  这样一来,如果我们用同一个Provider同时打开多个表,只有最后打开的ClientDataSet可以保证Server端的TDataSetProvider.DataSet和它对应着相同的数据集二前面打开的ClientDataSet就根本不能提交了,  这样一来,我们前面提出的方案就彻底崩溃了,根本原因就在于:
  TDataSetProvider的根本就是为单个客户端设计的,完全没有考虑到同时访问多个数据集的问题,实际运行中永远只有最后打开的一个数据集能够保持存在,因而也就只有最后打开的一个ClientDatSet能提交数据成功
---------------------------------------------------------------------
结论:
  要解决应该很简单,TDataSetProvider应做以下修改:
  1、建立一个内部的DataSet队列
  2、ClientDataSet打开数据集时,创建一个新的DataSet,加入到DataSet队列中
  3、ClientDataSet关闭时,从DataSet队列中删除对应的DataSet
  4、创建新DataSet时,可以根据TDataSetProvider.DataSet属性来确定使用的实际DataSet类
     也就是说,如果DataSet指定为一个TQuery,那么就创建一个TQuery;
     如果是一个TTable,那么就创建一个TQuery
     这点很容易实现
  5、创建新DataSet后,应该将TDataSetProvider.DataSet对应的TDataSet的全部属性赋值给新DataSet
  6、定义一套规范,将ClientDataSet与DataSet队列中的TDataSet一一对应
  7、取数、传递数据包、提交数据的代码都不需要变动,只需要根据ClientDataSet
     在DataSet队列中找到对应的TDataSet,其余的工作完全一样
  说起来简单,这种事情我们做不了,恐怕应该由Borland自己来干了!
  写到最后,得出的结论基本上是悲剧性的:
  1、最初提出的方案基本可行
  2、但是绝对不能让多个ClientDataSet同时使用同一个Provider访问数据
     否则将只有最后打开的ClientDataSet能提交数据成功
  3、如果有多个ClientDataSet都需要修改数据集并提交的话,那么就不能只用一个Provider了,只好在RemoteServer中公布几个Provider
  4、注意,错误只出在"同时"访问数据时,因此轮流使用还是可以的

  唉!!真是悲哀啊!!!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值