procedure TComPlus.OneMethod(const Param: WideString);
begin
try
FConnection.ConnectionString := '……'; // FConnection 为 TADOConnection
……
FDataSet.CommandText := 'Select * FROM OneTable'; // FDataSet 为 TADODataSet
FDataSet.Open;
FDataSet.Edit;
FDataSet.FieldByName('OneField').Value := Param;
FDataSet.Post;
SetComplete;
except
SetAbort;
raise;
end;
end; TComPlus 设置为“必需事务”。此时运行两个客户端,并让它们几乎同时调用 TComPlus.OneMethod 来访问数据库,其中第一个客户端调用是正常的,但是另一个会有“未指定的错误”的错误提示。
为了找到出现此问题的原因,我试着把 TComPlus 设置为“支持事务”,这时不会出现错误提示,不过 TComPlus.OneMethod 对数据进行了修改,肯定应该受到事务的保护,设置为“支持事务”显然不恰当。
后来我试着把 FDataSet.Post;这句给注释掉,也不会出现错误提示,但是这样的话只改变了缓存中的数据,后台数据库的数据不会被更改。
最后我还试着直接写 update 语句通过 TADOQuery 直接操作数据库,也不行!
通过上面的测试,我想可能错误的原因是 COM+ 管理事务时对数据库进行锁定,排斥了其他的数据修改访问。但是如果这样的话,系统的效率不是很低? 到底错误的真正原因是什么呢?如何解决呢? 各位兄弟是否也遇到过这种问题? 望指点!
begin
try
FConnection.ConnectionString := '……'; // FConnection 为 TADOConnection
……
FDataSet.CommandText := 'Select * FROM OneTable'; // FDataSet 为 TADODataSet
FDataSet.Open;
FDataSet.Edit;
FDataSet.FieldByName('OneField').Value := Param;
FDataSet.Post;
SetComplete;
except
SetAbort;
raise;
end;
end; TComPlus 设置为“必需事务”。此时运行两个客户端,并让它们几乎同时调用 TComPlus.OneMethod 来访问数据库,其中第一个客户端调用是正常的,但是另一个会有“未指定的错误”的错误提示。
为了找到出现此问题的原因,我试着把 TComPlus 设置为“支持事务”,这时不会出现错误提示,不过 TComPlus.OneMethod 对数据进行了修改,肯定应该受到事务的保护,设置为“支持事务”显然不恰当。
后来我试着把 FDataSet.Post;这句给注释掉,也不会出现错误提示,但是这样的话只改变了缓存中的数据,后台数据库的数据不会被更改。
最后我还试着直接写 update 语句通过 TADOQuery 直接操作数据库,也不行!
通过上面的测试,我想可能错误的原因是 COM+ 管理事务时对数据库进行锁定,排斥了其他的数据修改访问。但是如果这样的话,系统的效率不是很低? 到底错误的真正原因是什么呢?如何解决呢? 各位兄弟是否也遇到过这种问题? 望指点!
Server(Com+):
procedure Twritedata.writedata(const SQLCmd: WideString);
var FDS: TADODataSet;
begin
try
FDS := TADODataSet.Create(nil);
FDS.ConnectionString := 'File Name=c:\LocalDB.udl';
FDS.CommandText := SQLCmd;
try
FDS.Open;
FDS.Append ;
FDS.FieldByName('SQLCmd').Value := SQLCmd;
FDS.Post ;
Sleep(8000); //此处设置延时
SetComplete;
except
SetAbort;
raise;
end;
finally
FDS.Close;
FDS.Free;
end;
end;Client:
procedure TForm1.Button1Click(Sender: TObject);begin
wd := coWriteData.CreateRemote('');
wd.writedata(Edit1.Text);
end;procedure TForm1.FormActivate(Sender: TObject);
begin
wd := coWriteData.CreateRemote('');
end;
同时开三个Client(我没有网络与多余的机子调试:( )
用tmApartment或tmBoth报错'Transaction(Process ID 54) was deadlocked on {lock} resources with another process and has been chosen as the deadlock victim. Rerun the transaction.'数据只有一到二个可以存进去,
改为tmSingle后无错都可以存,不过是不时同时进行.
Server:
procedure Twd.writedata(Delta: OleVariant);
var ErrorCount: integer;
begin
try
try
FDS.Open; //dsp(TDataSetProvider)连FDS(TADODataSet)
dsp.ApplyUpdates(Delta,0,ErrorCount);
Sleep(8000);
SetComplete;
except
SetAbort;
raise;
end;
finally
FDS.Close;
end;
end;
Client:procedure TForm1.Button1Click(Sender: TObject);
var Datas: OleVariant;
begin
cds.Open ;
cds.Append ;
cds.Fields[1].Value := Edit1.Text ; //cds为TClientDataSet,通过DCOMConnection直接连Server端
Datas := cds.Delta; wd.writedata(Datas);
end;procedure TForm1.FormActivate(Sender: TObject);
begin
wd := Cowd.CreateRemote('');
end;
看来多层还通过TDataSetProvider更新数比较好
1. 使用ezService开发分布式数据库应用,可以大幅度简化应用服务器的开发,无须在建立COM+/SOAP Server应用上花费任何时间,也不需要费心管理数据库事务,只要具备熟练运用SQL的能力,理解SQL参数匹配规则即可写出复杂的分布式应用服务,使得入门级程序员也可以轻松负担服务开发任务。
2. ezService高级服务允许按照类pascal语法规则自由书写脚本,实现复杂业务逻辑,新版本可以支持自Borland Delphi 7导出的大量函数和对象。同时提供了对COM的直接支持,可以通过引用COM组件,与外部系统进行复杂的交互操作。
3. ezService内核为COM+,支持连接池(connection pooling)和对象池(object pooling)机制,自动支持分布式事务。
4. ezService使用ADO提供程序连接数据库管理系统,凡是提供良好的OLE-DB驱动的DBMS均可支持(目前已经在SQL Server和Oracle 8/9上通过用户验证)。
5. ezService使用名为ESDL(ezService定义语言)的(类似WSDL)XML发布文档,ESDL可以对外界发布ezService所开发服务的全部功能接口,使得第三方开发者也可以方便的了解服务,快速进行二次开发而无需了解服务细节。
6. 支持SOAP协议,提供一个ISAPI类型的Web Service,一个ASP.NET Web Service,可以直接将服务功能发布到Internet/Intranet,无须额外编程。
7. 未授权的ezService服务具备与授权版本完全相同的功能,仅会在执行时随机锁定3个用户身份验证帐号,其他功能不受影响。
8. 由于使用了COM+/SOAP技术,ezService可以被主流开发工具轻松调用,发行版本附带了可应用于Borland Delphi 7的一组VCL,使开发员可以迅速访问ezService服务。在Visual Studio .NET开发环境中也可以轻松使用类似技术。其他介绍:
ezService主要定位在以下四个方面:简化中小型分布式关系型计算服务开发、规范服务管理、创建完好设计契约、加速服务客户程序开发。集快速服务设计、服务即时发布、安全管理、跨系统协作于一身,是中小型分布式关系型数据库应用开发的一揽子解决方案,适应于快速搭建完备可靠的应用系统。 ezService所开发的应用服务定义,可以被立即登记到服务注册表中,无需任何繁琐设置即可被ezService客户程序以及支持COM+/SOAP的开发工具所访问。 ezServiceWeb Service服务提供者同时也是ezService系统平台的对外集成接口,遵循其WSDL声明,即可在各种外部系统中生成访问其数据的SOAP客户程序,通过完备的XML请求/响应定义,外部系统可以实现对ezService系统的数据读写访问。 新版本的ezService SE更换了脚本引擎,全面支持下列Delphi单元的绝大多数函数和类:
- System
- SysUtils
- Windows
- Classes
- Types
- TypInfo
- Variants
- VarUtils
- DateUtils
- DB
- ADODB
- ADOInt
- DBClient
- FMTBcd
- IniFiles
- MaskUtils
- Math
- Registry
- SqlTimSt
- StrUtils
- SysConst
具备更强大的扩展能力。更多内容,请访问作者主页:http://www.ezService.org
目前主页新增了论坛系统,欢迎访问留言:
http://www.ezService.org/dvbbs
今天上午,我试着用 TADOCommand 直接写 Update 语句操作数据库好像就不会有问题,但是为什么 TADODataSet 的 Post 方法就不行呢?
这种问题又不太容易通过调试程序来解决,疑惑 & 郁闷中……
看了一下Provider中的源码,如下
function TCustomResolver.ApplyUpdates(const Delta: OleVariant; MaxErrors: Integer;
out ErrorCount: Integer): OleVariant;
var
XmlMode: LongWord;
Status: Integer;
DataPacket: TDataPacket;
begin
BeginUpdate;
try
FUpdateTree.InitDelta(Delta);
try
Provider.DoOnUpdateData(FUpdateTree.Delta);
FPrevResponse := rrSkip;
if MaxErrors = -1 then MaxErrors := MaxInt;
FMaxErrors := MaxErrors;
FErrorCount := 0;
FUpdateTree.DoUpdates;
ErrorCount := FErrorCount;
if FUpdateTree.HasErrors then
begin
Status := FUpdateTree.ErrorDS.DSBase.GetProp(dspropXML_StreamMode, @XMLMode);
if (Status <> 0) or (XMLMode = 0) then
Result := FUpdateTree.ErrorDS.Data
else
begin
FUpdateTree.ErrorDS.Check(FUpdateTree.ErrorDS.DSBase.StreamDS(DataPacket));
DataPacketToVariant(DataPacket, Result);
end;
end else
Result := Null;
finally
FUpdateTree.Clear;
end;
finally
EndUpdate;
end;
end;上面的BeginUpdate,EndUpdate引起我的注意。它们的源码如下procedure TDataSetResolver.BeginUpdate;
begin
FOpened := not Provider.DataSet.Active;
if FOpened then
begin
Provider.DataSet.Open;
FBook := '';
end else
FBook := Provider.DataSet.Book;
end;procedure TDataSetResolver.EndUpdate;
begin
if FOpened then
begin
Provider.DataSet.Close;
FOpened := False;
end else
begin
if (Length(FBook) > 0) and
Provider.DataSet.BookValid(@FBook[1]) then
Provider.DataSet.Book := FBook;
end;
end;受它的启示,我将上面的第一个例子改变一下
加一个DataModule(dm)放一个ADODataSet(FDS),不用动态创建的。
Server端如下(Client端不变)type
Twritedata = class(TMtsAutoObject, Iwritedata)
protected
procedure writedata(const SQLCmd: WideString); safecall;
public
procedure Initialize;override;
destructor Destroy;override;
end;implementationuses ComServ, Unit3;destructor Twritedata.Destroy;
begin
dm.Free;
inherited;end;procedure Twritedata.Initialize;
begin
inherited;
dm := Tdm.Create(nil);
end;procedure Twritedata.writedata(const SQLCmd: WideString);
begin
try
if dm.FDS.Active then
begin
raise EOleException.Create('E',0,'','',0);
SetAbort;
Exit;
end;
dm.FDS.CommandText := SQLCmd;
try
dm.FDS.Open;
dm.FDS.Append ;
dm.FDS.FieldByName('SQLCmd').Value := SQLCmd;
dm.FDS.Post ;
Sleep(8000);
SetComplete;
except
SetAbort;
raise;
end;
finally
dm.FDS.Close;
end;
end;initialization
TAutoObjectFactory.Create(ComServer, Twritedata, Class_writedata,
ciMultiInstance, tmApartment);
end.
同样测试时开三个Client程序结果为第一个提交的一定成功,第二个提示我抛出的错误(E),第三个提示一个AV错误,之后提示RPC服务错误。越来越困惑,也越来越有趣 :)
to:sz1008
你提交的是单个记录,如果是批量提交呢?客户端选中多条记录,点击按钮批量保存,客户端、服务器端该如何写呢?如果客户端不用ApplyUpdates提交,而是自己写提交过程,该怎么实现呢?
大家帮忙哦,讨论讨论吧。
我想对于多线程对象的访问,必须维护对象的状态使之不发生冲突(因此书上建议使用无状态对象),而当用ADODataSet打开一个数据对象时,系统会保存这个数据对象相关资料(数据集,当前指针等)多线程调用时会产生冲突。而ADOCommand直接传递SQL语句不会,DataSetProvider则由Delphi维护所以这两个都可以用Apartment,Neutral线程.不知这样理解对不对,希望能有高手给出一个权威正确的解释:(to decem(天地) :
提取Client的DataSet的Delta,传回Server,再用Server的DataSetProvider.ApplyUpdate更新数据
unit Unit3;{$WARN SYMBOL_PLATFORM OFF}interfaceuses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ComServ, ComObj, VCLCom, StdVcl, bdemts, DataBkr, DBClient,
MtsRdm, Mtx, Project2_TLB, DB, ADODB;type
TTest = class(TMtsDataModule, ITest)
ADOConnection1: TADOConnection;
ADOQuery1: TADOQuery;
private
{ Private declarations }
protected
class procedure UpdateRegistry(Register: Boolean; const ClassID, ProgID: string); override;
function Test(ss: OleVariant): OleVariant; safecall;
public
{ Public declarations }
end;var
Test: TTest;implementation{$R *.DFM}class procedure TTest.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;function TTest.Test(ss: OleVariant): OleVariant;
begin
try
try
ADOQuery1.Close;
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add('select * from test');
ADOQuery1.Open;
ADOQuery1.Edit;
ADOQuery1.FieldByName('test').Value:= ss;
ADOQuery1.Post;
self.SetComplete;
Result:= 0;
except
Result:= 9;
self.SetAbort;
end;
finally
ADOQuery1.Close;
end;
end;initialization
TComponentFactory.Create(ComServer, TTest,
Class_Test, ciMultiInstance, tmBoth);
end.客户端:
procedure TForm1.Button1Click(Sender: TObject);
var
aa: ITest;
i: Integer;
begin
aa:= CoTest.Create;
for i:= 0 to 1000 do begin
edit1.Text:= (inttostr(aa.Test(inttostr(i))));
edit2.Text:= inttostr(i);
application.ProcessMessages;
sleep(200);
end;
aa:= nil;
end;
同时开启10个客户端程序,其中提交的事务和回滚的事务比差不多为7:3 启用com+对象池(最小10个)好像能够减少错误,但没有出现楼主开两个客户端发生异常的问题
COM+锁定级别较高,且只有在1.1版本(WINXP)中可以更改。所以要避免多人同时更新的“牺牲品”问题,只有在ADOCnn建立连接后手动发给数据库一条SQL,改变其锁定级别。
我实现的代码如下: ACnnString := TRegisterReader.GetDBConnection(AppName);
if ADOCnn.ConnectionString <> ACnnString then
ADOCnn.ConnectionString := ACnnString;
ADOCnn.Connected := True;
ADOCnn.Execute('SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED'); 因为我将所有的代码都封装到类里了,所以在其它任何地方都没有再设置。还有我是通过数据的逻辑状态进行校验,如果你没有该步骤,希望你能加上。没有任何人工校验,又避免了数据库的自动保护,数据的完整性就不能保证了。 我的分有三千多了,一直没时间散,有时间我开个贴,请留意。可惜我站上的朋友很少,不然我快结婚了就有人祝贺了,唉……
http://expert.csdn.net/Expert/topic/2240/2240049.xml?temp=.0811426
你在 TTest.Test 中捕获了异常,但是没有重新抛出啊!这个时候客户端当然不会出现异常。
To shsunb(我怕来不及)兄:
老兄的方法果然有用!多谢多谢!
对于你所说的“通过数据的逻辑状态进行校验,…… 没有任何人工校验,又避免了数据库的自动保护,数据的完整性就不能保证了”是不是意味着:降低了事务隔离级别,就会出现诸如读“脏”数据之类的并发问题,此时数据的完整性就需要自己来加以保护。你所说的是这个意思吗?
另外老兄大婚在即,小弟在此恭喜恭喜了!
频繁不同时对同一数据修改 (大量的系统应用是这样的)
这种情况下,搂住所说的情况不会发生。频繁同时对同一数据修改 (我没真正做过这样的系统,我假象一个吧,每个客户端对该数据做登记,一个客户应用一次,该数据做加一操作)
在这种情况下,我觉得应当对该数据做串行操作,就是专门做一个com+对象,始终存在(有且仅有一个),所有客户端激活的com+对象向此对象提请求,由这个对象根据请求依次串行的对该数据进行操作。cb中的dcom对象好像就是这样的(不像delphi中的是并行)个人想法,希望大家讨论
对Oracle来说,ADO支持的驱动就有好几种,而哪种好使这就要具体情况具体分析.