Delphi MIDAS 的重大BUG / 李颖(300分)

李颖

Unregistered / Unconfirmed
GUEST, unregistred user!
耐心看下去,多少有点启发......
目的:
三层结构中客户端取数一般是采用
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 ClientDataSetdo
begin
Close;
CommandText := 'Select xxx ......';
Open;
end;

// for Delphi 4:
手头找不到D4了,因此不知道D4是否支持TClientDataSet.CommandText,
我们以前是这样做的:
Client端程序:
with ClientDataSetdo
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 ClientDataSet1do
begin
Close;
CommandText := 'Select * from Table1';
Open;
end;

with ClientDataSet2do
begin
Close;
CommandText := 'Select * from Table2';
Open;
end;

上面的代码中ClientDataSet与数据库表的对应关系如下:
ClientDataSet1 =====> "Table1"
ClientDataSet2 =====> "Table2"
注意顺序为先打开表1,再打开表2
然后我们修改ClientDataSet1中的数据,再调用
ClientDataSet1.ApplyUpdates(-1)方法提交数据,却发现无论如何都成功不了,
奇怪!
同样的数据直接在两层C/S结构的程序中可以成功地写入表中,可以肯定数据没问题
程序绝对正确,同样的程序早就调试通过了,现在却突然不能提交数据了,
而且,奇怪的是,错误很稳定,100%提交失败,系统重起N次也没影响
 
分析:
打开Delphi 源代码 provider.pas ,搜索ApplyUpdates之类的东西,看究竟是如何实现的,
最后看到了根源,如下:
// Delphi 源代码 provider.pas
procedure TDataSetResolver.DoUpdate(Tree: TUpdateTree);
begin
with Treedo
begin
...
Source.Edit;
...
end;
end;

procedure TDataSetResolver.DoDelete(Tree: TUpdateTree);
begin
with Treedo
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
能提交数据成功
 
李兄:我做过一个试验,
Server端:TDataSetProvider TTable, TTableName由接口函数指定。
Client端:TClientDataSet。
启动多个客户端的exe打开编辑不同TTable成功。
这时实际上它是启动了多个 RemoteDataModule 的 Instance.
一个TDataSetProvider应该只能连接一个 Dataset。
也许您应该想办法解决的创建多个 RemoteDataModule 的 Instance。
 
我认为 一个TDataSetProvider应该只能连接一个 Dataset。
如你用多个 TQuery ,可让每个TQuery都导出一个接口.
 
Delphi的多个数据集问题是靠RemoteDataModule提供的,
两个ClientDataSet连的是同一个线程的DataSetProvider,必然产生冲突,
可以让ClientDataSet分别连两个不同的RemoteServer,
或可解决.....
 
对我一般来说是一个Tquery对一个Tdatasetprovider对一个Tclientdataset.
我看了许多书都是这么用的。
yysun说的创建多个 RemoteDataModule 的 Instance。我非常赞成,
我决的企业物件对象就是要一个对象用一个RemoteDataModule
 
@_@

Delphi5已作了改动,详见李维《实战DELPHI5》
 
援引提问者{{
这样一来,我们前面提出的方案就彻底崩溃了,根本原因就在于:
TDataSetProvider的根本就是为单个客户端设计的,完全没有
考虑到同时访问多个数据集的问题, 实际运行中永远只有最后
打开的一个数据集能够保持存在,因而也就只有最后打开的一个
ClientDatSet 能提交数据成功}}
我觉得该方案失败不能归咎于TDataSetProvider,当
Tclientdataset提交时,实际用到的是client端数据集更
改数据包(即Tclientdataset.Delta),其中也包括了数据
表及字段信息,试想TDataSetProvider得到的表及字段信
息与其最后保留的信息不符,它如何能正确提交呢?
建议牺牲灵活性,让一个数据集(可以是多表关联得到的
结果集)对应一个TDataSetProvider.

 
哈哈,李大虾,怎么这个问题你现在才发现???
说穿了好简单,在提交任一个表之前把打开表的SQL语句传回给TQuery就可以了
(但不执行)。提交总是对的。这不算什么BUG。
 
高手华山论DELPHI
我只能来观摩观摩
 
delphi5中有一個重要屬性:
即: server端Tdatasetprovider的Resoluetodata要設True
 
这也是BUG?看一下DEMO里有这样用的吗?
不明白什么方案要这样设计
 
上次没贴完,继续:
==============================================
结论:
要解决应该很简单,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、注意,错误只出在"同时"访问数据时,因此轮流使用还是可以的
唉!!真是悲哀啊!!!
 
to 歪歪孙:
>> 李兄:我做过一个试验,
>> Server端:TDataSetProvider TTable, TTableName由接口函数指定。
>> Client端:TClientDataSet。
>> 启动多个客户端的exe打开编辑不同TTable成功。
>> 这时实际上它是启动了多个 RemoteDataModule 的 Instance.
>> 一个TDataSetProvider应该只能连接一个 Dataset。
>> 也许您应该想办法解决的创建多个 RemoteDataModule 的 Instance。
看来大家对我的观点有点误解,我不是说在多个客户端的情况,
这种问题用多实例的RemoteServer就可以搞定了,
2 lha、y_zl:
>> 建议牺牲灵活性,让一个数据集(可以是多表关联得到的
>> 结果集)对应一个TDataSetProvider.
最初的愿望是想通过一个Provider就可以存取任意数据,当然是通过SQL了,
不只是指定表名那么简单
灵活性是绝对不能牺牲的,否则如果表多,查询逻辑复杂,就完蛋了,
因此你们的建议无法接受。。。。
 
又少贴了一部分,补上:
=============================================
调试:
程序和数据都没问题,实在想不出错在哪里,只好用最低级的方式了,
打开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端实际使用的表!!
 
2 adminis、rixin:
看来你们明白我的意思了,我去试一下你们的方法,如果正确的话就给分

adminis:
所谓“在提交任一个表之前把打开表的SQL语句传回给TQuery就可以了”怎么实现?
是否ClientDataSet调用ApplyUpdates之前先调用
DataReqesut('Select xxx ....')??
可是这样一来,如果Server端再次执行这段SQL,它得到的是新数据,
不能保证数据同步啊,
我是说,如果多用户情况下,数据被其他人更新过了,
那么Server在提交数据时得到的数据可能与最初发送到Client的数据不一致,
那么很可能找不到某些记录,导致提交失败
你可以试试看,用你的方案写一段程序:
1、Client取数
2、在别的程序中删除一些记录
3、Client修改记录
4、提交
如果3中修改的记录实际上已经在2中被删除了,
那么上面说的情况可能就会发生了,你认为呢?
ps: 纯粹是猜测,没实验过,不如你去试一下??
 
TMD!怎么老是丢数据??气死我了。

抱歉各位,上面贴的东西老是丢,只好不断重发,顺序全乱了,
正确顺序是:
"目的" => "发现Bug" => "调试" => "分析" => "结论"
 
顶部