在中间层BeforeUpdateRecord事件中,修改了Delta的内容(用于自动生成主键),并设置Applied为false,成功更新了数据库,但如何让客户端保持同步(不通过FetchAll或close+open),回答正确给1000分
解决方案 »
- ServerSocker用线程方式 ,ClientSocket用阻塞模式,ServerSocket在收到信息后如何进行回复?在线等
- 如何在win98下运行网络程序
- Delphi7 Client 端使用 SocketConnection,如何捕捉并修改"Error reading from socket"错误消息?
- (100送给你)c/s结构程序,oracle客户端可不可以不装?
- 请教filter中文过滤问题!
- 问题很简单,但是还是不会,帮帮忙
- 我的程序错在那?
- 请问如何实现本地文件夹与远程文件夹的文件同步,Thanks!!!
- delphi 自带help例子中关于线程的问题
- 在线等待,写条SQL语句
- 地址
- 李维《ADO、MTS、COM+高级程序设计》第三章,TOleContainer控件能显示存在数据库Blob字段中的mpg文件吗?
ClientDataSet1.FieldByName('Field1').Value:=OwnerData;
ClientDataSet1.Post;
ClientDataSet1.MergeChangeLog;
包含poPropogateChanges,VCL会自动把修改的东西传回Client的ClientDataSet,无需代码。
再给你一个技巧,VarIsEmpty函数判断是不是客户端修改并提交了值。NULL不能表明的,给你我的一个程序片断:是服务器的程序,
function TApplyTableTable.BeforeInsert(SourceDS: TDataSet; DeltaDS: TCustomClientDataSet):Boolean;
begin
Result:=inherited BeforeInsert(SourceDS,DeltaDS);
DeltaDS.FieldByName('ApplyDate').NewValue:=Now;
DeltaDS.FieldByName('ApplyMan').NewValue:=CurUserID;
Owner.Users.AddLink(Self,CurUserID);
DeltaDS.FieldByName('CheckMan').NewValue:=NULL;
if not VarIsEmpty(DeltaDS.FieldByName('EquipmentID').NewValue) then
if (not VarIsNull(DeltaDS.FieldByName('EquipmentID').NewValue)) then
begin
if not Owner.AssetsData.CheckID(DeltaDS.FieldByName('EquipmentID').NewValue) then
raise Exception.Create(ErrAddAssets);
end else
raise Exception.Create(ErrAddAssets);
end;function TApplyTableTable.BeforeModify(SourceDS: TDataSet; DeltaDS: TCustomClientDataSet):Boolean;
begin
Result:=inherited BeforeModify(SourceDS,DeltaDS);
if not VarIsEmpty(DeltaDS.FieldByName('EquipmentID').NewValue) then
if (not VarIsNull(DeltaDS.FieldByName('EquipmentID').NewValue)) then
begin
if not Owner.AssetsData.CheckID(DeltaDS.FieldByName('EquipmentID').NewValue) then
raise Exception.Create(ErrAddAssets);
end else
raise Exception.Create(ErrAddAssets);
if not VarIsEmpty(DeltaDS.FieldByName('Status').NewValue) then
begin
if (DeltaDS.FieldByName('Status').NewValue=2) then
begin
if VarIsEmpty(DeltaDS.FieldByName('EquipmentID').NewValue) then
begin
if not VarIsNull(DeltaDS.FieldByName('EquipmentID').OldValue) then
begin
if not Owner.AssetsData.CheckID(DeltaDS.FieldByName('EquipmentID').OldValue) then
raise Exception.Create(ErrAddAssets)
else
Owner.AssetsData.AddLink(Self,DeltaDS.FieldByName('EquipmentID').OldValue);
end else
raise Exception.Create(ErrAddAssets);
end else
Owner.AssetsData.AddLink(Self,DeltaDS.FieldByName('EquipmentID').NewValue);
DeltaDS.FieldByName('CheckDate').NewValue:=Now;
if VarIsValid(DeltaDS.FieldByName('CheckMan').OldValue) and (DeltaDS.FieldByName('CheckMan').OldValue<>CurUserID) then
begin
Owner.Users.DelLink(Self,DeltaDS.FieldByName('CheckMan').OldValue);
Owner.Users.AddLink(Self,CurUserID);
end;
DeltaDS.FieldByName('CheckMan').NewValue:=CurUserID;
end else if (DeltaDS.FieldByName('Status').NewValue=1) then
begin
if VarIsEmpty(DeltaDS.FieldByName('EquipmentID').NewValue) then
if not VarIsNull(DeltaDS.FieldByName('EquipmentID').OldValue) then
begin
if not Owner.AssetsData.CheckID(DeltaDS.FieldByName('EquipmentID').OldValue) then
raise Exception.Create(ErrAddAssets);
end else
raise Exception.Create(ErrAddAssets);
DeltaDS.FieldByName('ApplyDate').NewValue:=Now;
if VarIsValid(DeltaDS.FieldByName('CheckMan').OldValue) then
begin
Owner.Users.DelLink(Self,DeltaDS.FieldByName('CheckMan').OldValue);
end;
DeltaDS.FieldByName('CheckMan').NewValue:=NULL;
if VarIsValid(DeltaDS.FieldByName('ApplyMan').OldValue) then
begin
if (DeltaDS.FieldByName('ApplyMan').OldValue<>CurUserID) then
begin
Owner.Users.DelLink(Self,DeltaDS.FieldByName('ApplyMan').OldValue);
Owner.Users.AddLink(Self,CurUserID);
end;
end else
Owner.Users.AddLink(Self,CurUserID);
DeltaDS.FieldByName('ApplyMan').NewValue:=CurUserID;
end else if (DeltaDS.FieldByName('Status').NewValue=3) then
begin
DeltaDS.FieldByName('CheckDate').NewValue:=Now;
if VarIsValid(DeltaDS.FieldByName('CheckMan').OldValue) and (DeltaDS.FieldByName('CheckMan').OldValue<>CurUserID) then
begin
Owner.Users.DelLink(Self,DeltaDS.FieldByName('CheckMan').OldValue);
Owner.Users.AddLink(Self,CurUserID);
end;
DeltaDS.FieldByName('CheckMan').NewValue:=CurUserID;
end else
begin
if VarIsValid(DeltaDS.FieldByName('CheckMan').OldValue) then
begin
Owner.Users.DelLink(Self,DeltaDS.FieldByName('CheckMan').OldValue);
end;
DeltaDS.FieldByName('CheckMan').NewValue:=NULL;
DeltaDS.FieldByName('ApplyDate').NewValue:=Now;
if VarIsValid(DeltaDS.FieldByName('ApplyMan').OldValue) then
begin
if (DeltaDS.FieldByName('ApplyMan').OldValue<>CurUserID) then
begin
Owner.Users.DelLink(Self,DeltaDS.FieldByName('ApplyMan').OldValue);
Owner.Users.AddLink(Self,CurUserID);
end;
end else
Owner.Users.AddLink(Self,CurUserID);
DeltaDS.FieldByName('ApplyMan').NewValue:=CurUserID;
end;
end;
end;function TApplyTableTable.BeforeDelete(SourceDS: TDataSet; DeltaDS: TCustomClientDataSet):Boolean;
begin
Result:=inherited BeforeDelete(SourceDS,DeltaDS);
if not Result then
begin
if VarIsValid(DeltaDS.FieldByName('CheckMan').NewValue) then
Owner.Users.DelLink(Self,DeltaDS.FieldByName('CheckMan').NewValue);
if VarIsValid(DeltaDS.FieldByName('ApplyMan').NewValue) then
Owner.Users.DelLink(Self,DeltaDS.FieldByName('ApplyMan').NewValue);
end;
end;
Applied设为False的情况没试过,Sorry
另外,有人问应用服务器更新了就可以,为什么还要传递到客户端呢,我假设有一笔数据要提交,在中间层生成主键,日期,我要在应用服务器上更新成功后,在客户端并打印出来,我肯定需要回传这笔数据
第二个方法并不十分理想,增加程序的复杂性,并且出现错误到底处理ReconcileError的时候并不好处理,你逃避了本质性的问题,我在服务端修改Delta,如何回传这个Delta(看来我只有这样做了)
第三个如果返回从表的记录集,那么网络的传输量你考虑过吗(假设有10000......条 ),另外三层关联,四层关联怎么办
也是要处理由应用服务器端生成主键值,补全客户端没有填完整的字段值,也都是在BeforeUpdateRecord里处理,今晚试了一下Applied := False的情况(只试了UpdateKind = ukInsert的情况),也和Applied := True的情况没有什么异样。VCL也是自动将变动反馈给
Reconcile例程啊。我还特意看了一下Provider单元的代码,PropogateChanges好像和Applied没什么关系,你只要往字段NewValue属性赋值就行了。实在不知道你们遇到了什么麻烦。不过如果你的需求确实解决不了,可以用蜗牛大佬的第二个方案,简单、可靠,易于控制,就像在搞C/S的时候干的那样。至于蜗牛大老说的"关联层次过多是你设计不恰当造成的,你应当在某些时候使用LookUp字段,计算字段来减少关联",我不是很理解。我的数据库结构中也有3套表的嵌套情形达到了
3层的,不知道有什么好的办法能解决。说实在,3层主从关系处理起来其实挺麻烦的。不知道
蜗牛大老所说的“某些时候使用LookUp字段,计算字段来减少关联”,具体是什么办法。因为今晚出去玩, 再加上搞这个Applied := True,看VCL代码,现在已经很晚了,又喝了一点酒,明天再请教两位大佬吧。以下是今晚测试所用到的RemoteDataModule的单元的简单代码。
TIDMapping = class(TObject)
ClientID, ServerID: Integer;
end; TIDPooler = class(TObject)
private
fList: TList;
public
destructor Destroy; override;
procedure AddIDPair(const aClientID, aServerID: Integer);
function FindByClient(const aClientID: Integer): TIDMapping;
procedure Clear;
end; TTestMasterDetail = class(TRemoteDataModule, ITestMasterDetail)
procedure DataSetProvider1BeforeUpdateRecord(Sender: TObject;
SourceDS: TDataSet; DeltaDS: TCustomClientDataSet;
UpdateKind: TUpdateKind; var Applied: Boolean);
procedure RemoteDataModuleCreate(Sender: TObject);
procedure RemoteDataModuleDestroy(Sender: TObject);
procedure DataSetProvider1BeforeApplyUpdates(Sender: TObject;
var OwnerData: OleVariant);
private
{ Private declarations }
fIDPooler: TIDPooler; //generate id for t_master.id
function GetID: Integer;
//generate id for t_detail.id2
function GetID2: Integer;
protected
class procedure UpdateRegistry(Register: Boolean; const ClassID, ProgID: string); override;
public
{ Public declarations }
end;implementation{$R *.DFM}class procedure TTestMasterDetail.UpdateRegistry(Register: Boolean; const ClassID, ProgID: string);
begin
if Register then
begin
inherited UpdateRegistry(Register, ClassID, ProgID);
EnableSocketTransport(ClassID);
EnableWebTransport(ClassID);
end else
begin
DisableSocketTransport(ClassID);
DisableWebTransport(ClassID);
inherited UpdateRegistry(Register, ClassID, ProgID);
end;
end;{
table structures (mssql 2000 style):
----------------------------------
create table t_id (
id_name varchar(16) not null primary key,
id_value integer not null
)
go insert into t_id values ( 'T_MASTER' , 1 )
insert into t_id values ( 'T_DETAIL' , 1 )
go create table t_master (
id integer not null primary key,
create_date datetime not null,
last_modify_date datetime not null
)
go create table t_detail (
id integer not null,
id2 integer not null,
optional_field varchar(10) ,
primary key ( id, id2 )
--, constraint fk_detail foreign key ...
)
go sql code of p_gen_id , no exception check :)
---------------------------------- create procedure p_gen_id
@id_name varchar(16),
@id_value integer output
as
select @id_value = id_value from t_id where id_name = UPPER(@id_name)
update t_id set id_value = id_value + where id_name = UPPER(@id_Name)
go
}function TTestMasterDetail.GetID: Integer;
begin
ADOStoredProc1.Parameters.ParamByName('@id_name').Value := 'T_MASTER';
ADOStoredProc1.ExecProc;
Result := ADOStoredProc1.Parameters.ParamValues['@id_value'];
end;function TTestMasterDetail.GetID2: Integer;
begin
ADOStoredProc1.Parameters.ParamByName('@id_name').Value := 'T_DETAIL';
ADOStoredProc1.ExecProc;
Result := ADOStoredProc1.Parameters.ParamValues['@id_value'];
end;procedure TTestMasterDetail.RemoteDataModuleCreate(Sender: TObject);
begin
fIDPooler := TIDPooler.Create;
ADOStoredProc1.Parameters.ParamByName('@id_value').Direction := pdOutput;
end;procedure TTestMasterDetail.RemoteDataModuleDestroy(Sender: TObject);
begin
fIDPooler.Free;
end;procedure TTestMasterDetail.DataSetProvider1BeforeApplyUpdates(
Sender: TObject; var OwnerData: OleVariant);
begin
fIDPooler.Clear;
end;procedure TTestMasterDetail.DataSetProvider1BeforeUpdateRecord(
Sender: TObject; SourceDS: TDataSet; DeltaDS: TCustomClientDataSet;
UpdateKind: TUpdateKind; var Applied: Boolean);
var
ClientID, ServerID: Integer;
IDMapping: TIDMapping;
DetailDataSet: TDataSet; function IsMasterTable: Boolean; //overwrite this function
var
I: Integer;
begin
for I := 0 to DeltaDS.FieldCount-1 do
if DeltaDS.Fields[I] is TDataSetField then
begin
Result := True;
Exit;
end; Result := False;
end;begin
Applied := False; case UpdateKind of
ukInsert:
begin
if IsMasterTable then
begin
ClientID := DeltaDS.FieldByName('id').OldValue;
if fIDPooler.FindByClient(ClientID) = nil then
begin
ServerID := GetID;
DeltaDS.FieldByName('id').NewValue := ServerID;
DeltaDS.FieldByName('create_date').NewValue := Now;
fIDPooler.AddIDMapping(ClientID, ServerID);
end;
end
else
begin
ClientID := DeltaDS.FieldByName('id').OldValue;
IDMapping := fIDPooler.FindByClient(ClientID);
if IDMapping = nil then
begin
//only insert detail record
//the id field value is ready, simply ignore it
end
else
DeltaDS.FieldByName('id').NewValue := IDMapping.ServerID;
//assume one applyudpates only one master
DeltaDS.FieldByName('id2').NewValue := GetID2;
end;
end; ukModify:
begin
//assume update primary key is not permitted, so only update the field of last_modify_date
if IsMasterTable then DeltaDS.FieldByName('last_modify_date').NewValue := Now;
end; ukDelete:
begin
//
end;
end;
end;{ TIDPooler }procedure TIDPooler.AddIDMapping(const aClientID, aServerID: Integer);
var
IDMapping: TIDMapping;
begin
if fList = nil then fList := TList.Create;
IDMapping := TIDMapping.Create;
IDMapping.ClientID := aClientID; IDMapping.ServerID := aServerID;
fList.Add(IDMapping);
end;procedure TIDPooler.Clear;
begin
if fList <> nil then
begin
while fList.Count > 0 do
begin
TIDMapping(fList[0]).Free;
fList.Delete(0);
end;
end;
end;destructor TIDPooler.Destroy;
begin
Clear;
fList.Free;
inherited;
end;function TIDPooler.FindByClient(const aClientID: Integer): TIDMapping;
var
I: Integer;
begin
if fList <> nil then
begin
for I := 0 to fList.Count-1 do
begin
Result := TIDMapping(fList[I]);
if Result.ClientID = aClientID then Exit;
end;
end;
Result := nil;
end;
To DbExpress,Applied设为True,是指我已经修改了数据不需要Provider再去修改,我设为false的原因是我只是修改了Delta,并没有修改后端数据,还是需要Provider去提交,
非常感谢大家对我的帮助与参与,,也许我有用辞不当的地方,大家互相学习是我们最终的目的 Bye(我上网时间比较少,现在才回)
计算量:主表的纪录数量*从表的记录数,如果存在第三个从表,那么,还要*第三个从表的记录数量。因为每次主表请求数据的时候,都要检索一遍从表并且将符合条件的从表记录集打包。客户端主从关系的情况传送的数据量:主表+从表的符合条件的数据,(从表的PacketRecords设置为0)
计算量:一条增加了Where的SQL语句检索从表,计算压力全部交给了DBMS,DBMS做SQL检索事很快的。还有,在服务器端的设置的NewValue,如果要回传客户端,那么对应的Provider的Options属性当中包含poPropogateChanges,包含poAutoRefresh在很多情况下没有用处,老的版本的delphi这个选项是无效的。poPropogateChanges总是能够回传服务器端做的修改。
你为什么不看看我的代码是否这样做了呢?蜗牛大佬也回复了4次,挺长的。而且他的方案都是可行的,有实际应用环境的,也可以说是经典的处理方法,大佬您和我现在使用的都是他说的第一套方案,至于说Delta的回传,就是昨晚小弟所为您效劳的。对于MIDAS的Provider/Resolver Architecture,不知大家是否同意,它本来也只是面对简单应用的,用户应该根据实际情况确定具体的实现方法。如果你要使你的服务器适应更高负荷的工作,你有必要微调这个P/R架构,做大量细致的工作,限制客户端的操作... ...
但是你不能向一个解决方案提出超出它胜任范围的要求。不能凭空地说多少层嵌套,多少万条从表纪录。