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+ 管理事务时对数据库进行锁定,排斥了其他的数据修改访问。但是如果这样的话,系统的效率不是很低?  到底错误的真正原因是什么呢?如何解决呢? 各位兄弟是否也遇到过这种问题? 望指点!

解决方案 »

  1.   

    Com+应该不会对数据库锁定,可能是别的原因先帮你up一下
      

  2.   

    这是我遇到的情况(不知是否和你一样)
    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后无错都可以存,不过是不时同时进行.
      

  3.   

    重新写了一下,不用DataSet直接写数据库,通过DataSetProvider更新,tmApartment线程模型,没有错误
    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更新数比较好
      

  4.   

    *  ezService一直在更新中。请从http://www.ezService.org/download/ezService.exe下载最新版本。        ezService是一个致力于简化分布式计算服务开发的框架。  简要介绍:
    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
     
      

  5.   

    sz1008 兄使用一种变通的方法解决了问题,不过我还是感到很疑惑,为什么会出现这种问题?
    今天上午,我试着用 TADOCommand 直接写 Update 语句操作数据库好像就不会有问题,但是为什么 TADODataSet 的 Post 方法就不行呢?
    这种问题又不太容易通过调试程序来解决,疑惑 & 郁闷中……
      

  6.   

    顺着楼主的问题一直试下去,发现越来越有意思。
    看了一下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服务错误。越来越困惑,也越来越有趣 :)
      

  7.   

    to yousoft(悠游在线):   我原来尝试着改过线程模式(Apartment、Neutral),都不行! 而且我这是多用户并发访问,跟关闭掉中间层连接应该没什么关系吧。
      

  8.   

    问题引申一下,我目前要实现的是记录的批量提交
    to:sz1008  
    你提交的是单个记录,如果是批量提交呢?客户端选中多条记录,点击按钮批量保存,客户端、服务器端该如何写呢?如果客户端不用ApplyUpdates提交,而是自己写提交过程,该怎么实现呢?
    大家帮忙哦,讨论讨论吧。
      

  9.   

    to  leapmars(流铭) :
      我想对于多线程对象的访问,必须维护对象的状态使之不发生冲突(因此书上建议使用无状态对象),而当用ADODataSet打开一个数据对象时,系统会保存这个数据对象相关资料(数据集,当前指针等)多线程调用时会产生冲突。而ADOCommand直接传递SQL语句不会,DataSetProvider则由Delphi维护所以这两个都可以用Apartment,Neutral线程.不知这样理解对不对,希望能有高手给出一个权威正确的解释:(to decem(天地) :
      提取Client的DataSet的Delta,传回Server,再用Server的DataSetProvider.ApplyUpdate更新数据
      

  10.   

    to sz1008(COM+学习ing):    我想你的理解可能不对。Delphi 的 TADODataSet、TADOCommand 组件其实在内部都是创建 ADO 相关的 COM 对象,它们的线程模型都是 Apartment。不同用户创建的 ADO 对象,应该分属于不同的 Apartment(因为用户是处于不同的 Apartment 中的),因此它们相互之间应该不会存在冲突。至于 DataSetProvider,它对于 MIDAS 数据包的读写处理,其实也是通过创建相关的 COM 对象(如 DatapacketRead、DatapacketWrite、DSBase、DSCursor,在 midas.dll 中实现)完成的,这些 COM 对象的线程模型也是 Apartment,不同用户创建的这些对象,也应该分属于不同的 Apartment,所以也应该不会存在冲突。
      

  11.   

    服务端:
    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个)好像能够减少错误,但没有出现楼主开两个客户端发生异常的问题
      

  12.   

    不要太客气。
        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');    因为我将所有的代码都封装到类里了,所以在其它任何地方都没有再设置。还有我是通过数据的逻辑状态进行校验,如果你没有该步骤,希望你能加上。没有任何人工校验,又避免了数据库的自动保护,数据的完整性就不能保证了。    我的分有三千多了,一直没时间散,有时间我开个贴,请留意。可惜我站上的朋友很少,不然我快结婚了就有人祝贺了,唉……
      

  13.   

    我的散分贴:
    http://expert.csdn.net/Expert/topic/2240/2240049.xml?temp=.0811426
      

  14.   

    To rwdx(任我独行之浪迹天涯)兄:
        你在 TTest.Test 中捕获了异常,但是没有重新抛出啊!这个时候客户端当然不会出现异常。
    To shsunb(我怕来不及)兄:
        老兄的方法果然有用!多谢多谢!
        对于你所说的“通过数据的逻辑状态进行校验,…… 没有任何人工校验,又避免了数据库的自动保护,数据的完整性就不能保证了”是不是意味着:降低了事务隔离级别,就会出现诸如读“脏”数据之类的并发问题,此时数据的完整性就需要自己来加以保护。你所说的是这个意思吗?
        另外老兄大婚在即,小弟在此恭喜恭喜了!
      

  15.   

    我在 winxp 的“组件服务”中改了 COM+ 组件的事务隔离级别后,就不需要在代码中手动改变隔离级别了。呵呵,果然是 COM+ 锁定数据库惹的祸!问题解决,多谢大家了!我还等几天看看其他人还有没有什么想法或意见要发表,然后就会结贴!
      

  16.   

    我的一点想法:我觉着上面的代码都是对同一表中的同一纪录的同一字段做修改,这种情况下,如果有多个客户端产生多个com+对象,应当是有成功的,有不成功的。我把应用分为两种:
    频繁不同时对同一数据修改 (大量的系统应用是这样的)
     这种情况下,搂住所说的情况不会发生。频繁同时对同一数据修改   (我没真正做过这样的系统,我假象一个吧,每个客户端对该数据做登记,一个客户应用一次,该数据做加一操作)
     在这种情况下,我觉得应当对该数据做串行操作,就是专门做一个com+对象,始终存在(有且仅有一个),所有客户端激活的com+对象向此对象提请求,由这个对象根据请求依次串行的对该数据进行操作。cb中的dcom对象好像就是这样的(不像delphi中的是并行)个人想法,希望大家讨论
      

  17.   

    我觉得你用Borland公司的ADO开发,很有可能出问题,原因就是驱动程序选择不当.比如
    对Oracle来说,ADO支持的驱动就有好几种,而哪种好使这就要具体情况具体分析.