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

请看Delphi帮助里的定义
Use TDataSetProvider to provide data from a dataset to a client dataset and to resolve updates from a client dataset back to that dataset or to its underlying database. TDataSetProvider is usually placed in the application server of a multi-tiered application. It serves as a data broker between <a> remote database server and <a> client dataset in the desktop client application.
 
问题一实际上已经解决了,只是D4不支持COMMANDTEXT
问题二估计是解决不了了,我想个变通的方法就是:
放可能多个并发访问的provider
因为不能实现
CD1.OPEN;CD2.OPEN,CD1.SAVE
但是,可以实现:
CD1.OPEN,CD1.SAVE.CD2.OPEN,CD2.SAVE...CDN.OPEN,CDN.SAVE
不过,这样一来好象会比较乱.
 
CJ:
所谓变通其实行不通啊,难道Client端用到多个表的时候,还要先排队??
怎么控制?再说,Close、Open、Close、Open....
重复N次后数据不是传N遍??性能狂降
这种方法还不如用多个Provider呢,
最多分析一下需求,需要同时存取的用不同的Provider
其他的仍然用同一个
我现在还是寄希望于rixin说的:
>> delphi5中有一個重要屬性:
>> 即: server端Tdatasetprovider的Resoluetodata要設True
还没实验,不知可否


 
看到你提的解决方案是维护那么多的DataSet,真是吓了我一小跳。
我不是说很清楚了吗,只传过去,不执行。也就是说,传你的SQL和
Delta过去,ApplyUpdates不在客户端,而是在服务端进行。这样
可以保证你能用一个Query和Provider,同时有无数的ClientDataSet连进来,
随便查询和更新。
我刚来大富翁不久,好不容易为李大虾解决一个难题,您可记得多赏点分,
鼓励鼓励。
 
所以我说:
>不过,这样一来好象会比较乱.
Resoluetodata我也没实验过,不过看帮助好象不是那个意思?
adminis:我还是不明白传递INSERT/UPDATE过去?
 
adminis的意思是在客户端写SQL,并且传给服务端
并且在服务端执行ApplyUpdates??
去试试看
 
=========================================================
2 rixin:
你的方法不行,测试结果如下:
TDataSetProvider.ResolveToDataSet设置为True后,只打开一个表也提交不了
用SQL Trace只看到开表的SQL,根本看不到提交数据的SQL
我看Delphi帮助关于TDataSetProvider.ResolveToDataSet中说:
============================================================
<B> 设置ResolveToDataSet属性以确定数据如何提交。
当ResolveToDataSet为True, Resolver属性将被设置为一个TDataSetResolver组件,
再由这个TDataSetResolver组件将数据直接提交到DataSet属性指定的数据集中.
这种方法主要适用于以下情况:
1、应用程序使用了DataSet组件的事件
<font color=red> 2、DatSet没有直接从Database Server得到数据,比如一个ClientDataSet</font>
当ResolveToDataSet为False, Resolver属性将被设置为一个TSQLResolver组件,
这个TSQLResolver组件将数据直接提交到DataSet连接的数据库.
这种方式省去了使用DataSet来提交数据的中间步骤,因此效率更高。
<B>
============================================================
按我的理解,上面的红字表示:
ResolveToDataSet属性设置为True后,
数据将写到DataSet中,而不是直接提交到数据库,
因此无法提交大概是因为TQuery.RequestLive属性为False的缘故。
将TQuery.RequestLive属性设置为True后,运行结果和前面说的完全一样,
仍然只能提交最后打开的表,前面打开的表根本提交不了,
而且结果更糟:
当ResolveToDataSet为False时,数据直接用SQL语句提交到数据库,因此失败后不会有错误提示
当ResolveToDataSet为True时,数据被写入Server端的DataSet,结果导致"Field xxx not found"
Exception发生在Server端,同时也显示在Client端,极难看!
结论:ResolveToDataSet属性只用来控制数据提交方式,是直接用SQL写到数据库,还是写到DataSet
而前面的问题是TDataSetProvider无法确定当前提交的目标表,两个问题之间根本没关系
 
Delphi帮助关于TDataSetProvider.ResolveToDataSet中说:
============================================================
<B> 设置ResolveToDataSet属性以确定数据如何提交。
当ResolveToDataSet为True, Resolver属性将被设置为一个TDataSetResolver组件,
再由这个TDataSetResolver组件将数据直接提交到DataSet属性指定的数据集中.
这种方法主要适用于以下情况:
1、应用程序使用了DataSet组件的事件
<font color=red> 2、DatSet没有直接从Database Server得到数据,比如一个ClientDataSet</font>
当ResolveToDataSet为False, Resolver属性将被设置为一个TSQLResolver组件,
这个TSQLResolver组件将数据直接提交到DataSet连接的数据库.
这种方式省去了使用DataSet来提交数据的中间步骤,因此效率更高。
</B>
============================================================
看到上面的红字部分受到启发,觉得可以用这种方法来实现数据路由控制,
实现对分布式数据库的访问,注意,是数据本身分布在网络上,比如几个独立服务器上
这种情况很普遍,例如多个收费站连接到一个城域网,
而各收费站的数据可能不是立即提交到同一个DB Server
而是先保存到本地的DB Server,当日工作结束后再提交
这种情况下,如果要实时访问那些没有提交到中央DB Server而存在于各个收费站本地DB Server的数据时
就涉及到分布式数据库的访问问题了
如果用普通的三层结构,有两种方法:
1、Client端判断数据位置,访问相应的Server
2、Server端判断数据位置,访问相应的DB Server
这两种方法都不好,系统结构本身没有解决数据路由问题,而需要程序自身去查找,实现起来很困难
解决方法应该是增加一个数据路由层,
这个数据路由层负责判断数据来源,然后用TClientDataSet从DB Server中取数,返回给Client
Client提交后,Provider将数据直接写入Server端的TClientDataSet再提交到DB Server中
实际上就是四层结构:

------------ ------------
| DB Server | .... | DB Server |
| 1 | | N |
------------ ------------
|| AppServer(1) || AppServer(N)
------------ ------------
| TDataSet | | TDataSet |
| || | .... | || |
| TProvider | | TProvider |
------------ ------------
|| ||
=======================
||
---------------------
| ClientDataSet |
App Server | || |
| Provider |
---------------------
||
||
------------
| Client |
------------
我想这种思路应该可以很好地解决分布式数据库访问问题
 
抱歉,各位,题外话说得太多,而且有段html代码写错了,抱歉抱歉
问题其实已经很清楚了,关键在于Server在提交数据时把最后打开的表作为目标表
2 CJ:
>> 我还是不明白传递INSERT/UPDATE过去?
对数据的修改不需要自己生成,Server会根据Delta部分自动确定,
只需要让Server知道该修改哪个表就一切都解决了
2 adminis:
现在看来只有你说得有道理,不过你好象是说自己在Server端实现ApplyUpdates?
是不是太过分了点?
我还是希望能用正常的方式,只要能让Server修改正确的表就行了
在D4中,我们以前的方法是通过DataRequest方法发送SQL语句
那么现在是否在ApplyUpdates前再调用一次同样的DataRequest方法?
但是我怎么知道原来取数的SQL语句呢?把取数时的SQL照抄过来?
那么程序也太不通用了...
在D5中,我们是用CommandText属性指定SQL语句的,
是否可用DataRequest(CommandText)的形式实现,
然后Server端的Provider在OnDataRequest中写
Query.SQL.Text := Input
这样可以吗?
我会去实验一下,如果后一种方法可行,那么就这么办了,
不过又带来一个新的问题:
DataRequest方法已经被我用来传递当前登录用户及口令了,
这个冲突恐怕只好增加一个参数来作为标志,表示当前DataRequest到底要干什么
这样很不舒服,不过也只好这么办了
2 all:
还有其他方法吗?
<font color=red>只要在Client端提交数据时能让Server端知道该修改哪个表,就一切OK!!</font>
 
听教受益!哈哈!
 
李大虾:
看看这段代码就明白了:
ADOQuery1.SQL.Text := SQLSTR;
//注意下面没有OPEN
DataSetProvider1.ApplyUpdates(Delta,MaxError,ErrorCount);
其中SQLSTR、Delta是从客户端传过来的,ErrorCount是要返回客户端的。
每个表提交时把打开它的SQL和做的改动一起传给服务端,让服务端做真正
的提交工作。ErrorCount>0表示提交失败。
可以跟踪一下后台的SQL,每次提交都会提交给 ADOQuery1.SQL.Text 中牵涉的
那个数据表。明白了吧?
试一试这段代码,你的问题立刻就可以得到解决。
 
好象有道理
试试去
 
接着听... ...
 
adminis,数据提交的问题已解决,
我是这么干的:
在ClientDataSet.BeforeApplyUpdates中传入SQL语句
在Provider.BeforeApplyUpdates中将得到的SQL语句赋值给TQuery.SQL.Text
程序如下:
Client端:
procedure xxxx.ClientDataSetBeforeApplyUpdates(Sender: TObject;
var OwnerData: OleVariant);
begin
with Sender as TClientDataSetdo
OwnerData := CommandText;
end;

Server端:
procedure xxxx.ProviderBeforeApplyUpdates(Sender: TObject;
var OwnerData: OleVariant);
begin
qrDataProvider.SQL.Text := OwnerData;
end;
 
2 adminis:
问题解决了,关键只要让Provider知道该更改哪个表就可以了,
你的方案绕得太远了,我觉得我的方法更简洁,你认为呢?
主要还是受你启发,所以300分给你了
2 all:
其他同志也有分,<a href=DispQ.asp?LID=228926>请到这里来拿</a>,感谢参与!
 
纯粹是题外话:
========================================================
又看了一下Delphi的源代码,
//unit provider.pas
procedure TSQLResolver.InitTreeData(Tree: TUpdateTree);
begin
....
TableName := VarToStr(Tree.Delta.GetOptionalParam(szTABLE_NAME));
if TableName = '' then
TableName := <font color=red>IProviderSupport(Tree.Source).PSGetTableName</font>;
Provider.DoGetTableName(Tree.Source, TableName);
if TableName <> '' then
Info.QuotedTable := GetQuotedTableName(Info.IsSQLBased, Info.QuoteChar, TableName);
....
end;

这一段就是TSQLResolver确定目标表的代码,其中Tree.Source定义为TDataSet,
估计就等于TDataSetProvider.DataSet,我们这里是一个TQuery
在DBTables.pas中可以看到TQuery.PSGetTableName方法如下:
function TQuery.PSGetTableName: string;
begin
Result := GetTableNameFromSQL(SQL.Text);
end;
其中GetTableNameFromSQL定义在DBCommon.pas中,
各位,这下发现宝贝了!!!!
这个单元里包括一些SQL语法分析程序,定义相当完备,
比如这个GetTableNameFromSQL函数分析一段SQL语句的目标表名,
只用了20行就搞定了,是不是很厉害??
这个单元没有包括在Delphi Help中,也不算很复杂,interface部分只有230行左右
快去看看吧,肯定有收获
 
顶部