在编写分布式应用系统时,书上都是这么说的: 在远程应用服务器的远程数据模块中,加入TDCOMConnection、TDataSet、TDataSetProvider连接数据库取出需要的数据,在客户端放一个TClientDataSet从应用服务器中取得数据,然后再通过赋值给界面上控件来显示。但如果以OO原则,从企业逻辑付封装在企业对象中,客户端再通过这个企业对象从数据库取出数据。
我想问的是:
1、应用服务器中如何定义什企业对象,如何把TDataSet、TDataSetProvider封装起来,又是如何暴露给客户端的?
2、客户端又是如何取得企业对象,并调用基方法的???如果以查询MIS中客户列表为例,应该如何设计与实现???先谢了

解决方案 »

  1.   

    其实你添加一个远程数模块时,这个数据模块的类就可以看作是一个企业逻辑封装。
    1、如果你要客户端连获取这个数据的步骤:
    应用程序服务器的远程书记模块上:加一个TQuery(加其它的比如TAdoquery也可以),该控件的SqL语句为select * from 客户列表,当然连接到数据库的连接设置也要设,再加一个TDataSetProvider,其数据集指向前面的Tquery.编译运行一下!
    客户端:加一个TDCOMConnection,并连接到前面的应用程序服务器,加一个TClientDataSet的Privder属性指向提供者,这时打开数据集TClientDataSet就可以获取数据,加TDatasource指向TClientDataSet,加DBGrid执向TDatasource就可以显示数据了。
      

  2.   

    只要弄明白它的原理就很容易理解怎样做,DataSnap有5个主要的部分,
    以下为示意图,<->表示调用和返回:
    ClientDataSet 
      <-> RemoteConnection 
        <-> IAppServer 
          <-> Provider 
            <-> Resolver & DataAccess Component
            
    其中IAppServer,由应用服务器程序的TRemoteDataModule对象来实现,
    ClientDataSet发出IAppServer接口提供的方法的调用,然后RemoteConnection
    处理客户端和应用服务器端的通讯,把这个请求转发给IAppServer,IAppServer
    再根据ProviderName参数,选择合适的Provider对象,然后将请求实际发给Provider对象
            
    Provider有DataSetProvider,我只用过DataSetProvider,姑且以它说明,
    每当Provider对象放在TRemoteDataModule上面时,它通过IProviderContainer接口向
    TRemoteDataModule注册,声明自己是一个Provider对象实际上我们可以吧Provider也看作一个接口,它具有和IAppServer接口差不多的接口,
    但是它不直接向外披露自己打开Midas.pas和Provider单元比较一下就知道这是IAppServer接口的声明, ClientDataSet正是调用这些方法请求读写数据的:  IAppServer = interface(IDispatch)
        ['{1AEFCC20-7A24-11D2-98B0-C69BEB4B5B6D}']
        function  AS_ApplyUpdates(const ProviderName: WideString; Delta: OleVariant;
                                  MaxErrors: Integer; out ErrorCount: Integer; var OwnerData: OleVariant): OleVariant; safecall;
        function  AS_GetRecords(const ProviderName: WideString; Count: Integer; out RecsOut: Integer;
                                Options: Integer; const CommandText: WideString;
                                var Params: OleVariant; var OwnerData: OleVariant): OleVariant; safecall;
        function  AS_DataRequest(const ProviderName: WideString; Data: OleVariant): OleVariant; safecall;
        function  AS_GetProviderNames: OleVariant; safecall;
        function  AS_GetParams(const ProviderName: WideString; var OwnerData: OleVariant): OleVariant; safecall;
        function  AS_RowRequest(const ProviderName: WideString; Row: OleVariant; RequestType: Integer;
                                var OwnerData: OleVariant): OleVariant; safecall;
        procedure AS_Execute(const ProviderName: WideString; const CommandText: WideString;
                             var Params: OleVariant; var OwnerData: OleVariant); safecall;
      end;
    Provider不直接处理请求,它也转发请求给Resolver,对于TDataSetProvider的Resolver来说,Resolver有两种,
    一种时DataSetResolver,一种是SQLResolverTDataSetProvider对象有两个属性,DataSet, ResolverToDataSet
    根据ResolverToDataSet属性决定TDataSetProvider使用何种ResolverDataSetResolver则是完全由Provider的DataSet属性所指向的DataSet来处理,
    比如取数据时,它就打开这个DataSet,
    更新的时候就移到合适的位置,然后Edit,Post,依靠这个DataSet本身的来处理
    数据请求,你可以在这个DataSet上面设置一些事件来控制数据的读写,就像CS
    程序一样,当TDataSetProvider.ResolveToDataSet为True时,使用这种Resolver,
    这时候要求DataSet完全实现IProviderSupport接口SQLResolver是这样一种Resolver对象,它根据请求自动产生合适
    的SQL语句发送到数据库后台来读取或更新数据,它需要DataSet的IProviderSupport
    接口来获得一些元数据(比如表名、字段名等)来产生合适的数据,并最后
    通过DataSet的IProviderSupport接口将SQL提交到数据库后台,TDataSetProvider
    默认使用这种Resolver不论何种Resolver,都必须根据TDataSetProvider.DataSet这个属性来工作
    并且这个DataSet必须实际支持IProviderSupport接口,虽然TDataSet这个基类
    本身声明了IProviderSupport支持,实际上它并没有有用的IProviderSupport实现至于如何披露企业对象给客户端,主要是通过Provider名称,IAppServer有个AS_GetProviderNames
    方法,ClientDataSet一旦确定了RemoteServer以后,它就可以通过RemoteConnection请求IAppServer
    返回它所披露的Provider列表了我理解是这样,企业对象和Provider还是有层次上的差别,Provider着重于数据处理,
    提供一个数据访问接口,而企业对象可能有数据处理,但是它超越了数据处理,它有更高级的方法,
    它面向更高级的访问者,而Provider只有ApplyUpdates,XXXRequest,Execute这样的数据处理接口,没有那么灵活,但是要处理象你的"以查询MIS中客户列表"的问题,DataSnap的方案还是挺便利的,方法前面回复人: hawksoft(明月清风) ( ) 信誉:105 已经举例说明了,很简单是吧至于ClientDataSet如何具体访问IAppServer的,最好还是参看源码,其实不甚了解也是可以的,
    只要知道它是这样工作的,知道它有一个RemoteServer属性,而这个RemoteServer对象有一个
    IAppServer,所有的数据请求最后转化到指定的Provider上就可以了说了这么多,好像言不及意,但实际上你明白这些原理,就很容易把握应该做怎样利用DataSnap作应用服务器
      

  3.   

    如果采用MIDAS和ClientDataSet这种开发方式,如何实现界面、业务逻辑、数据连接的逻辑分离、物理分离?
      

  4.   

    复:alphax(无之心)、hawksoft(明月清风)
    你们误解了我的意思。 关于DataSnap的机制,我略懂一二,也知道如何用TSqlConnection/DataSet/DataProvider/DComConnection/ClientDataSet/DBGrid等组件显示和修改数据。 我是想了解如何使OO的设计思想在应用程序服务器加入企业对象,然后再披露这些企业对给客户端,客户端通调用这些企业对象中方法来获得数据及更新数据。 还是以查询客户列表为例,看了一些关于分布式系统的书,我的理解是: 在应用程序服务器中定义一个对象TPortfolio,其中有方法GetCustomers,这个方法封装了DataSet、DataSetProvider向客户端提供客户列表的数据集。  在客户端通过 Var aPortfolio:TPortfolio ; aPortfolio.GetCustomers来获得数据。。  不知我这样理解OO应用在企业解决方案,是否正确???如果这办法是可行的,那么客户端如何取得应用服务器的企业对象及其方法?
      

  5.   

    果然是言不及意,大吉利是!也是可以的,关键是你自己需要嘛要得到对象接口,简单的办法就是利用ClientDataSet.DataRequest举例,调用时:
    var
      Portfolio: IPortfolio;
      Obj: OleVariant;
      Data: OleVariant;Obj := ClientDataSet.AppServer.AS_DataRequest('$CMD$CreatePortfolio', YourParams);
    Portfolio := IDispatch(Obj) as IPortfolio;Data := Portfolio.GetCustomers();应用服务器截获这个请求返回一个IPortfolio接口不过我以前做多层一直都是直接用ClientDataSet取数据的,很少再定一个假对象,觉得没什么大用
      

  6.   

    复:alphax(无之心)如果直接用ClientDataSet取数据会简单多了,但是应用程序服务器中也只能杂乱地放置一些DataSetProvider供客户端直接调用,就不是OOP思想设计应用程序服务器了,也不存在什么企业对象了。。那么如何在应用服务器中实现业务逻辑????我公司有个MRP,听软件公司说,他只在在应用程序服务器放置DataSetProvider提供数据,我想其所有业务逻辑分别在客户端及数据库中实现,这个是胖客户端的表现,我公司的客户端软件大小就有7M。 
    现时大多数中小型软件公司或企业都是用这种方案开发应用系统吗???
    Obj := ClientDataSet.AppServer.AS_DataRequest('$CMD$CreatePortfolio', YourParams);  这个不明,请帮忙解释thanks
      

  7.   

    对于三层结构而言,你在应用服务器上定义一个类TPortfolio,该类处理企业的业务逻辑GetCustomers,你想在客户端利用本地创建类并调用其方法,Var aPortfolio:TPortfolio 
    aPortfolio = TPortfolio.create;采用aPortfolio.GetCustomers调用来达到目的,是不可能的,因为你实例化的是本地类,不是远程应用服务器上的类。在三层种,客户端跟应用服务器只能通过远程调用来实现,是无法保留类的调用状态的,如果你想通过远程调用你应用服务器上的TPortfolio类的方法GetCustomers,你必须定义一个从IAppserver继承的接口IPortfolio,该接口包含GetCustomers方法,你的类方法必须实现该接口,同时因为远程服务其实是一种COM服务,所以你还必须实现Com服务接口。具体的你可以参看Delphi为你自动实现的接口,一般是你的远程数据仓库名前面加一个I字母。在服务器端为客户端定义类其实是没有意义的。所以一般情况下你直接在Delphi自动为你生成的接口下,添加新的方法,Delpi会在该接口的实现类,也就是你的远程数据仓库窗体里面实现该方法,你直接在客户端通过连接(SocketConnect,DCOMConnect等)对象的AppServer接口来调用GetCustomers方法就行了。但需要注意的是,通过这种增加接口方法来调用的方式是有限的,你的返回值是有限的,如果你想返回数据集,对象是不行的(如果高手说可以,请指教),我觉得如果你采用Delphi的这种三层模式,还是需要按alphax(无之心) 说得那样,利用ClientDataset返回数据集。如果你一定要这样做,建议你采用WebServices来进行。
    下面是我理解得Delphi三层结构中,客户端调用服务器方法得过程:
    1、客户端打开连接(SocketConnect,DCOMConnect,HttpConnect)
    2、如果是是使用SocketConnect连接,则首先连接服务器的scktsrvr程序(Borland自带),由改程序调用连接指定的Com服务,如果该服务没有实例化,则创建COM对象,启动服务。DCOMConnect连接则由Windows自带的DCOM服务自己管理,HttpConnect连接需要通过Borland自带的一个dll来处理。
    3、客户端发送的调用方法指令GetCustomers,则启动代理服务程序(COM服务,scktsrvr等)向你实际的应用服务器发出调用指令,并由代理程序把结果返回给客户端。
    当然Delphi提供的这种三层在开发上确实比较快,但灵活性还是比较欠缺,所以现在做三层,我比较倾向于WebServices技术。它的灵活性和通用性都要好!如果你中间层用Webservices,客户端可以采用支持Webservices调用的任何语言开发。好处当然不只这一点,有兴趣可以看看!
      

  8.   

    另外采用调用接口方法(函数)的方式返回值(如果是对象)也仅仅是个Copy,服务器端是不会为你保留类的状态的,假设你返回一个TPortfolio对象,你在客户端调用GetCustomers也是意义的。
    具体这种方式能不能返回对象(包括数据集对象),我没试过。
      

  9.   

    另外采用调用接口方法(函数)的方式返回值(如果是对象)也仅仅是个Copy,服务器端是不会为你保留类的状态的,假设你返回一个TPortfolio对象,你在客户端调用GetCustomers也是无意义的。
    具体这种方式能不能返回对象(包括数据集对象),我没试过。
      

  10.   

    多谢hawksoft(明月清风) alphax耐心解答,我觉得自已在这方面知识不扎实,打算找本COM、COM+的书看看,而后再来考虑这问题现在Delphi发展到.Net版本,还有必要去了解COM吗???