服务器端:
TADOConnection
TADOQuery
TDataSetProvider
客户端:
TSocketConnection
TClientDataSet当客户端用ClientDataSet打开一个表后,进行修改并更新(通过DataSetProvider.ApplyUpdates),而在打开和更新之间,可能有另外的用户对该表进行过修改,然后,这个更新就会出错,提示“Record not found or changed by other user”。
请问,这个问题如何解决,按说这种用法应该很多,是不是我那个东东用得不当呢?
TADOConnection
TADOQuery
TDataSetProvider
客户端:
TSocketConnection
TClientDataSet当客户端用ClientDataSet打开一个表后,进行修改并更新(通过DataSetProvider.ApplyUpdates),而在打开和更新之间,可能有另外的用户对该表进行过修改,然后,这个更新就会出错,提示“Record not found or changed by other user”。
请问,这个问题如何解决,按说这种用法应该很多,是不是我那个东东用得不当呢?
如果这样,我的更新就不成功,而所做的工作就会前功尽弃,不是我想要的
在我和系统中,我希望以最后更新为准,而且很多时候是一个用户修改表的某几个字段,而其它用户修改表的另外一些字段,逻辑是并没有多少矛盾,只有很少时候是同时修改同一字段的,这时我希望是以后修改的为准,我的表又是有Key字段的。
代码网上应该有很多的,我的代码很乱,很难整理啊,用的控件我上面已经列出来了,或者你看看Delphi的帮助吧。再详细深入些我也不行了,不然就不会有这一问了。to: ben_jiang(木头)
写一个存储过程应该可以,但我的程序已经写好了,并且在用,只是偶尔出现这种问题,不想大改。另外,Delphi既然提供了这套控件用,就应该可以应负这种情况,还是想将这个问题搞明白。
我改用TADOTable代替TADOQuery,TDataSetProvider.UpdateMode := upWhereKeyOnly,就可以了。
这TADOQuery怎么给查询结果DataSet加上KEY呢?解决这个问题应该就可以了
现在我希望的正常解决办法是:
一、用TADOQuery查询打开数据表时就能将表的Key约束带进来
二、手工给已打开的TDataSet补加上Key约束
死都不愿 add all fields 的人也是有的, 那在 dsp 的 BeforeUpdateRecord 事件中 SourceDS.FieldByName('xx').ProviderFlag 去改也是可以的
人总是很贪心的,我还希望更简单些去解决。
现在我不想对服务端软件进行任何修改,而希望只修改客户端软件去解决这个问题。
经过试验,在服务端通过修改TADOQuery的ProviderFlag,确实可以解决问题,但如果我服务端不变,而在客户端修改TClientDataSet的ProviderFlag,更新时却还是出现没有Key的错误。
但是我坚信在客户端可以实现,因为服务端的TADOQuery和TDataSetProvider是公用的,很多查询都使用同一套控件,而不会出现任何问题,证明这个Key信息应该保存在TClientDataSet中的,只是,单单设置ProviderFlag可能还是不行,不知如何设置???
在服务端这边的 OnUpdateData 事件中写到
var
KeyField: string;
begin
KeyField := DataSet.GetOptionalParam('KeyField');
(Sender as TDataSetProvider).DataSet.FieldByName(KeyField).ProviderFlag ...这个就能达到你要求的适应性, 不过失去了程序的可读性和结构性, 从三层设计看是应该从服务端去控制客户机行为的, midas 虽然是不完全实现, 但也提供了约束/字典等功能, provider 可以共用但 Query 就不能共用, 这些功能是在 TDataSet 级别上提供的
不是吧,应该所有信息都送回客户端了吧,因为我的服务端只有一套TADOQuery, TDataSetProvider,而我的客户端有N多个TClientDataSet,都从服务端取得数据集,而能互不干扰,如果信息存在服务端,怎能做到多个数据集互不干扰呢?
象你上面的改法,不如就直接在服务端数据打开时设置ProviderFlag更方便,更直观易懂。
的确没有回送任何 key 的信息, Table 不说, 这个知道哪个是 key, Query 在打开时 ProviderFlag 是全设的, 更新又大多人用的是 upWhereAll, 所以呵呵好像大多人都能成功.知道你的现实情况, 一般说用 Borland 向导生成代码的话, 第一个 RDM(remote datamodule) 或 TDM (transactional datamodule) 是相当相当简单的, 放个 Connection 好像问题就怎决了, 但项目(或者说目标)扩大后就会发现 TDM/RDM 上放满了 DSP/Query, 是吧..这时共用的需要就突出了(这是个值得讨论的头晕问题), 到了最后你就会发现服务端的代码是没有任何逻辑, 所有的东东都在一起, 无法区分对像逻辑, 并不像三层介绍的那样美妙(至少我是这么认为的)广泛观察一下其它实现的三层逻辑后, 就会发现全是 IAppServer 的问题, 这个东东忽略了对像的所有细节而用记录表示所有内容, 嗯, 业务层/逻辑层不见得全是数据库, 就是数据库他本身也是对像, 像记录的一行本身就是一个对像... 有事写不了了, 这个问题你可以开新帖大家说话嘛
如果服务端用TADOTable代替TADOQuery,然后TDataSetProvider.UpdateMode设为upWhereKeyOnly,也可以解决更新冲突问题。
所以我想我的问题只是因为TADOQuery没有返回表的KEY,从而更新不成功,如果手工在客户端补加上KEY,这TClientDataSet就能和通过TADOTable返回的一样,或者象手工在服务器端增加KEY一样,从而能够更新成功。(也许我的概念不是很清楚,有些地方或许叫“中间层”,但相信大家都能明白的)
恕我愚钝,我不太明白你的意思。难道我这不是三层结构?我听说这就是三层结构啊!
不管怎么样,我想我现在如果能解决在TClientDataSet上手工加上KEY的问题,我的整个问题就好解决了。
源码我没看,只是从做的实验中猜测。
首先:保证你的UpdateMode,是按主健更新;
其次:你的CLIENTDATASET的各个字段的PROVIDERFLAGS要设定正确,特别是不要忘记了设主健;
最后:比较重要的一点是D在这里有个小问题,客户端设的PROVIDERFLAG,设定不会被传到服务器端,这也是很多人莫名奇妙的原因,你最好自己想办法把它们传过去;如上面
KeyField := DataSet.GetOptionalParam('KeyField');
(Sender as TDataSetProvider).DataSet.FieldByName(KeyField).ProviderFlag
这一类的代码来实现;
{表的定义:
CREATE TABLE [TB1] (
[aa] [int] NOT NULL ,
[bb] [int] NULL ,
[cc] [char] (32) COLLATE Chinese_PRC_CI_AS NULL ,
[dd] [varchar] (256) COLLATE Chinese_PRC_CI_AS NULL ,
PRIMARY KEY CLUSTERED
(
[aa]
) ON [PRIMARY]
) ON [PRIMARY]CREATE TABLE [TB2] (
[aa] [tinyint] NOT NULL ,
[bb] [char] (16) COLLATE Chinese_PRC_CI_AS NULL ,
[cc] [smallint] NULL ,
PRIMARY KEY CLUSTERED
(
[aa]
) ON [PRIMARY]
) ON [PRIMARY]
}//{$define usetable}interfaceuses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, DBClient, Provider, DB, ADODB;type
TForm1 = class(TForm)
ADOConnection1: TADOConnection;
ADOQuery1: TADOQuery;
DataSetProvider1: TDataSetProvider;
ClientDataSet1: TClientDataSet;
Button1: TButton;
Edit1: TEdit;
Button2: TButton;
Button3: TButton;
Button4: TButton;
ClientDataSet2: TClientDataSet;
Button5: TButton;
ADOTable1: TADOTable;
Button6: TButton;
Button7: TButton;
Button8: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure Button5Click(Sender: TObject);
procedure Button7Click(Sender: TObject);
procedure Button6Click(Sender: TObject);
procedure Button8Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;var
Form1: TForm1;implementation{$R *.dfm}procedure TForm1.FormCreate(Sender: TObject);
begin
ADOConnection1.Connected := True;
end;//打开第一个表
procedure TForm1.Button1Click(Sender: TObject);
begin
{$ifdef usetable}
ADOTable1.TableName := 'TB1';
DataSetProvider1.DataSet := ADOTable1;
{$else}
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add('select * from TB1 order by aa');
ADOQuery1.Open;
ADOQuery1.Fields[0].ProviderFlags := ADOQuery1.Fields[0].ProviderFlags + [pfInKey];
DataSetProvider1.DataSet := ADOQuery1;
{$endif}
ClientDataSet1.Data := DataSetProvider1.Data;
end;//修改第一个表
procedure TForm1.Button2Click(Sender: TObject);
begin
if ClientDataSet1.RecordCount > 0 then
begin
ClientDataSet1.First;
ClientDataSet1.Edit;
ClientDataSet1.FieldByName('cc').AsString := FormatDateTime('hh:mm:ss', now);
ClientDataSet1.Post;
end;
end;//更新第一个表
procedure TForm1.Button3Click(Sender: TObject);
var
ie: Integer;
begin
if ClientDataSet1.ChangeCount > 0 then
begin
ADOQuery1.SQL.Text := 'select * from TB1 where 1=2 order by aa';
DataSetProvider1.DataSet := ADOQuery1;
DataSetProvider1.ApplyUpdates(ClientDataSet1.Delta, 0, ie);
if ie=0 then
ClientDataSet1.MergeChangeLog;
Edit1.Text := IntToStr(ie);
end;
end;//打开第二个表
procedure TForm1.Button4Click(Sender: TObject);
var
ii: Integer;
begin
{$ifdef usetable}
ADOTable1.TableName := 'TB2';
DataSetProvider1.DataSet := ADOTable1;
{$else}
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add('select * from TB2 order by aa');
ADOQuery1.Open;
//(1)BEGIN 有了这句,就怎么更新都可以了
ADOQuery1.Fields[0].ProviderFlags := ADOQuery1.Fields[0].ProviderFlags + [pfInKey];
//(1)END
DataSetProvider1.DataSet := ADOQuery1;
{$endif}
ClientDataSet2.Data := DataSetProvider1.Data;
{//(2)BEGIN 如果没有以上(1)语句,而增加下面这些语句,更新时都会因找不到KEY出错
ClientDataSet2.IndexDefs.Add('aa','aa',[ixPrimary, ixUnique, ixDescending]);
ClientDataSet2.Fields[0].ProviderFlags := ClientDataSet2.Fields[0].ProviderFlags + [pfInKey];
ClientDataSet2.AddIndex('aaIndex', 'aa', [ixUnique], '');
ClientDataSet2.IndexName := 'aaIndex';
//(2)END}
ii := ClientDataSet2.IndexFieldCount;
end;//修改第二个表
procedure TForm1.Button5Click(Sender: TObject);
begin
if ClientDataSet2.RecordCount > 0 then
begin
ClientDataSet2.First;
ClientDataSet2.Edit;
ClientDataSet2.FieldByName('bb').AsString := FormatDateTime('hh:mm:ss', now);
// ClientDataSet1.FieldByName('cc').AsString := 'abcd';
ClientDataSet2.Post;
end;
end;//更新第二个表
procedure TForm1.Button6Click(Sender: TObject);
var
ie: Integer;
begin
if ClientDataSet2.ChangeCount > 0 then
begin
ADOQuery1.SQL.Text := 'select * from TB2 where 1=2 order by aa';
DataSetProvider1.DataSet := ADOQuery1;
DataSetProvider1.ApplyUpdates(ClientDataSet2.Delta, 0, ie);
if ie=0 then
ClientDataSet2.MergeChangeLog;
Edit1.Text := IntToStr(ie);
end;
end;//SQL直接修改第一个表
procedure TForm1.Button7Click(Sender: TObject);
begin
ADOQuery1.SQL.Text := 'update TB1 set cc=''' + FormatDateTime('hh:mm:ss', now) + ''' where aa=1';
ADOQuery1.ExecSQL;
end;//SQL直接修改第二个表
procedure TForm1.Button8Click(Sender: TObject);
begin
ADOQuery1.SQL.Text := 'update TB2 set bb=''' + FormatDateTime('hh:mm:ss', now) + ''' where aa=1';
ADOQuery1.ExecSQL;
end;end.如果使用ADOTable1打开表,或是用ADOQuery1打开,在打开后设置ProviderFlags,按什么顺序打开和更新这两个表,都一点问题都没有查询分析器查看数据也是正确的。但如果在ClientDataSet2中才设置ProviderFlags或再试图增加KEY就是不成功(即不用(1)语句而用(2)之类的语句)。