1. DATASNAP 历史
作为MIDAS起始于Delphi3,Delphi4是MIDAS II,Delphi5中是MIDASIII,而后基于COM远程数据模块方式使 Delphi2010中构建于D2009架构之上,并对此架构做了进一步的扩展,包括使用两个向导来创建新的 1.1 DATASNAP范例数据位置 本白页中我建议您使用Demo和范例来学习.虽然Delphi支持很多数据库系统,使用DBX4,ADO dbGo,或 2. DATASNAP目标:如何获取数据 DataSnap2010支持三种不同的Windows方式:VCL窗体,Windows服务和控制台应用程序.本节中我们 下面会创建一个DataSnap服务端和客户端,我们将讲解 将讨论不同的传输协议(TCP,HTTP)的好处及传输效率.并讨论DataSnap服务对象的不同生命期选项 2.1. DATASNAP 服务端范例 在Object Repository中有两个不同的DataSnap服务向导:一个是生成基于Windows的Datasnap服务 启动了Delphi2010,点击File.New.Other,你会在Object Repository中看到DataSnap服务向导中 双击第一个(后面的两个在下面的小结中讲解),弹出如下对话框: 界面中第一部分是控制项目类型的.默认可以生成可视化的带有主窗体的VCL窗体应用程序.第二个
第二部分是选择使用的Datasnap服务的通讯协议.和DanaSnap2009相比,我们可以看到多了一个 第三部分已经为我们配置好了,如果我们要提供一个服务方法类,我们可以选择它的基 现在是从DSServer.pas中贴出来的一小段代码,来说明TDSServerModule和 TDSServerModuleBase = class(TProviderDataModule) public procedure BeforeDestruction; override; destructor Destroy; override; end; {$MethodInfo ON} TDSServerModule = class(TDSServerModuleBase) end; {$MethodInfo OFF} 当无法确定时就使用TDSServerModule选项作为基类. 2.1.1. 创建多目标项目组-- VCL 窗体项目 如上面所说,这里创建多目标的Datasnap服务项目组.首先创建一个VCL窗体应用程序作为Datasnap 默认创建了一个叫做Project1.dproj的项目,并带有三个单元文 稍后我们将向项目组添加控制台应用程序和Window服务应用程序.首先我们来检查一下项目,并编 ServerContainerUnitDemo的引用部分应该向下面代码所示: implementation uses Windows, ServerMethodsUnitDemo; {$R *.dfm} procedure TServerContainer1.DSServerClass1GetClass( DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass); begin PersistentClass := ServerMethodsUnitDemo.TServerMethods1; end; end. 2.1.1.1. SERVERCONTAINERUNITDEMO 打开ServerContainerUnitDemo单元,将会看到不少于五个组件:一个TDSServer,一个 前面两个一直会存在,其他的三个则是根据选择的通讯协议生成的. 2.1.1.1.1. TDSSERVER TDSServer组件只有四个属性,AutoStart,HideDSAdmin,Name和Tag.AutoStart属性默认设置为
TDSAdminMethods = class public const CreateServerClasses = 'DSAdmin.CreateServerClasses'; const CreateServerMethods = 'DSAdmin.CreateServerMethods'; const FindClasses = 'DSAdmin.FindClasses'; const FindMethods = 'DSAdmin.FindMethods'; const FindPackages = 'DSAdmin.FindPackages'; const GetPlatformName = 'DSAdmin.GetPlatformName'; const GetServerClasses = 'DSAdmin.GetServerClasses'; const GetServerMethods = 'DSAdmin.GetServerMethods'; const GetServerMethodParameters = 'DSAdmin.GetServerMethodParameters'; const DropServerClasses = 'DSAdmin.DropServerClasses'; const DropServerMethods = 'DSAdmin.DropServerMethods'; const GetDatabaseConnectionProperties = 'DSAdmin.GetDatabaseConnectionProperties'; end; TDSServer组件有五个事件:OnConnect,OnDisconnect,OnError,OnPrepare和OnTrace.我们可以 OnConnect,OnDisconnect,OnError和OnPrepare事件有一个继承于TDSEventObject的参数,包含 OnTrace事件有一个TDBXTraceInfo类型的参数.注意由于这个OnTrace事件处理程序也会包含一 在OnConnect事件处理中,我们可以通过ChannelInfo来查看连接信息,例如(使用自定义的函数 procedure TServerContainer1.DSServer1Connect( DSConnectEventObject: TDSConnectEventObject); begin LogInfo('Connect ' + DSConnectEventObject.ChannelInfo.Info); end; 在OnTrace事件处理程序中我们可以使用TraceInfo.Message中的信息记录服务端正在做什么. function TServerContainer1.DSServer1Trace(TraceInfo: TDBXTraceInfo): CBRType; begin LogInfo('Trace ' + TraceInfo.CustomCategory); LogInfo(' ' + TraceInfo.Message); Result := cbrUSEDEF; // take default action end; 注意,在客户端也可以使用连接到TSQLConnection组件的TSQLMonitor组件来跟踪DataSnap服务 一个跟踪日志输出如下所示: 17:05:55.492 Trace 17:05:55.496 read 136 bytes:{"method":"reader_close","params":[1,0]}
"TServerMethods1.AS_GetRecords"]} 17:05:55.499 Prepare 如你所见,TraceInfo.Message中包括了传输信息的字节数和被调用的方法名称等信息. 2.1.1.1.2. TDSSERVERCLASS TDSServerClass组件将服务端特定的类发布给远程客户端(使用动态方法调用). TDSServerClass组件有一个Server属性指向TDSServer组件.其他除了Name和Tag外的重要属 TDSServerClass有四个事件:OnCreateInstance,OnDestroyInstance(当实例创建和注销时触 procedure TServerContainer1.DSServerClass1GetClass( DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass); begin PersistentClass := ServerMethodsUnitDemo.TServerMethods1; end; 注意:当我们重命名了自动生成的代码单元ServerMethodsUnit1为 2.1.1.1.3. TDSTCPSERVERTRANSPORT TDSTCPServerTransport组件负责在DataSnap服务端和客户端进行通讯,使用TCP/IP协议. TDSTCPServerTransport组件有五个重要的属性:BufferKBSize,Filters(D2010新特 BufferKBSize属性指定通讯缓冲区大小,默认设置为32KB.Filters属性可以包含一个传输过滤 Server属性指向TDSServer组件.TDSTCPServerTransport组件没有事件. 2.1.1.1.4. TDSHTTPSERVICE TDSHTTPService组件负责使用HTTP协议组织DataSnap服务端和客户端通讯. TDSHTTPService组件有十个属性(除了Name和 Active属性指定DSHTTPService开始侦听请求.可以在设计时设置,但是这会影响DataSnap服务 procedure TServerContainer1.DataModuleCreate(Sender: TObject); begin DSHTTPService1.Active := True; end; AuthenticationManager属性用于定义处理HTTP验证的管理组件,这里指向了 DSHostName和DSPort属性用于定义DataSnap服务端连接,但只有在没有指定Server属性时生效.
Filters属性可以包含一系列传输过滤器,在第四节详述. HttpPort属性定义DSHTTPService组件侦听的特定端口以响应连接.注意这个属性默认是80端口, RESTContext属性指定REST上下文URL,这样就可以以REST服务的方式调用DataSnap服务.默 最后,Server属性指向同一个容器中的TDSServer组件.如果没有指定Server属性,也可以使用 TDSHTTPService组件有五个事件:四个是REST相关的,一个是跟踪事件,REST相关事件将在第六 OnTrace事件可用于跟踪对DSHTTPService组件的调用,例如: procedure TServerContainer1.DSHTTPService1Trace(Sender: TObject; AContext: TDSHTTPContext; ARequest: TDSHTTPRequest; AResponse: TDSHTTPResponse); begin LogInfo('HTTP Trace ' + AContext.ToString); LogInfo(' ' + ARequest.Document); LogInfo(' ' + AResponse.ResponseText); end; 注意HTTP跟踪信息只有当客户端使用HTTP连接到服务端是才会触发(默认使用TCP/IP协议). 跟踪输出如下所示: 17:05:55.398 HTTP Trace TDSHTTPContextIndy 17:05:55.400 /datasnap/tunnel 17:05:55.403 OK 从中可见,AContext设置为TDSHTTPContextIndy,ARequest设置为/DataSnap/tunnel,AResponse 2.1.1.1.5. TDSHTTPSERVICEAUTHENTICATIONMANAGER 当选中HTTP通讯协议的验证复选框后,TDSHTTPServiceAuthenticationManager组件将自动出现 TDSHTTPServiceAuthenticationManager组件有一个事件:OnHTTPAuthenticate事件,可以验证 procedure TServerContainer1.DSHTTPServiceAuthenticationManager1HTTPAuthenticate( Sender: TObject; const Protocol, Context, User, Password: string; var valid: Boolean); begin if (User = 'Bob') and (Password = 'Swart') then valid := True else valid := False end; 当然,你可以使用数据库技术来扩展验证方式.客户端最好使用HTTPS方式将用户名和密码等 HTTPS可以确保连接安全和数据包加密, 数据包被窃取也不会泄露用户和密码信息.可与你所在
DataSnap服务应用程序另一个优势是可将HTTP验证信息(所用协议和上下文信息)记录下来.以 2.1.1.2. SERVERMETHODSUNITDEMO 注意我们已经检查了ServerContainerUnitDemo.pas单元,现在看一下DataSnap服务的另外重要 由于TServerMethods1继承于TDataModule,设计时可以看到一个数据模块的可视区域.我们可以 如果不想向项目组添加其他类型的项目—DataSnap控制台应用程序及DataSnap Windows服务应 2.2. DATASNAP 客户端 第一个DataSnap服务端范例启动并侦听请求,现在创建一个客户端.本节中,我们将讲解如何在 确信DataSnap服务已启动,我们创建一个DataSnap客户端应用程序项目.为了在设计时方便切换 DataSnap客户端目录中的组件都是一些老的DataSnap组件,可以使用,但不推荐.但可使用新 除了DataSnap客户端目录,我们还要看看dbEpress目录,可以找到一个新的组件叫做 TSqlServerMethod组件可用于调用DataSnap服务的远程方法,但首先需要连接到DataSnap服务. 可以使用TSQLConnect组件建立连接—--不在使用原来的TXXXConnection组件.为了灵活 注意:默认CommunicationProtocol属性为空,TSQLConnection将使用TCP/IP作为通信协议(端口 HostName,UserName和Password用于连接到DataSnap服务.在本地测试时,将HostName设置为 不要忘记将TSQLConnection的LoginPropt属性设置为False,否则将会在连接的时候弹出登录窗
一旦TSQLConnection的Driver属性设置好,就可以设置Connected属性为True去连接DataSnap服 2.2.1. DATASNAP客户端类 在确定连接正常后,右击TSQLConnection组件,选择Generate DataSnap Class选项,将生成一个 这个TServerMethods1Client类如下所示 type TServerMethods1Client = class private FDBXConnection: TDBXConnection; FInstanceOwner: Boolean; FEchoStringCommand: TDBXCommand; FServerTimeCommand: TDBXCommand; public constructor Create(ADBXConnection: TDBXConnection); overload; constructor Create(ADBXConnection: TDBXConnection; AInstanceOwner: Boolean); overload; destructor Destroy; override; function EchoString(Value: string): string; function ServerTime: TDateTime; end; 如你所见,TServerMethods1Client类有两个构造方法,一个析构方法和两个我们在服务端定义 为使用这些方法,将ServerMethodsClient单元引用到ClientForm,在客户端窗体上放置一个按 procedure TForm2.Button1Click(Sender: TObject); var Server: TServerMethods1Client; begin Server := TServerMethods1Client.Create(SQLConnection1.DBXConnection); try ShowMessage(DateTimeToStr(Server.ServerTime)) finally Server.Free end; end; 这个代码将创建一个TServerMethods1Client类实例,然后调用ServerTime服务类,最后释放这 点击按钮将弹出对话框显示服务端的时间.
2.2.1.1. HTTP COMMUNICATION PROTOCOL 注意我已经提到TSQLConnection组件以TCP/IP作为默认通讯协议.这样我们就看不到任何HTTP 修改后运行DataSnap客户端,会出现如下错误: 解决方法是在DataSnap客户端向ClientForm中添加DSHTTPLayer单元引用. 2.2.1.2. HTTP 验证 使用HTTP通讯协议的好处之一是可以使用其包含的HTTP验证.由DataSnap服务端的 如果实现了OnHTTPAuthenticate事件处理,将会核对HTTP验证.我们必须保证输入正确的信息才 HTTP检验从客户端传递到服务端的用户名和密码,及其他TDSHTTPServiceAuthentication特定 注意我们也需要指定HostName的值,除非是在同一台电脑上测试. 2.3. DATASNAP服务部署 范例的服务端和客户端在同一台电脑上运行良好,但是实际环境中,DataSnap服务将运行在服务 2.3.1. DATASNAP 客户端部署 假设客户端与服务端运行在不同的电脑上,我们必须保证客户端可以连接到服务端.为了保证这 3. DATASNAP和数据库 除了使用Delphi2010 DataSnap框架创建简单的服务方法,我们还可以在服务端添加数据库操作, 我们同样可以快速创建数据库操作的DataSnap范例,只使用SQLConnection组件和已创建的客户 首先,我们要确保服务端公布了一个数据集,打开ServerMethodsUnitDemo并在数据模块中添加
Users\Documents\RAD Studio\7.0\Demos\database\databases\BlackfishSQL on Windows XP, or C:\Users\Public\Documents\ RAD Studio\7.0\Demos\database\databases\BlackfishSQL. 下一步,添加一个TSQLDataSet组件,将其SQLConnection设置为TSQLConnection组件. 设置其CommandType为ctQuery,双击CommandText属性输入SQL语句. SELECT EMP_NO, FIRST_NAME, LAST_NAME, HIRE_DATE, JOB_COUNTRY FROM EMPLOYEE 确保在设计时设置了TSQLConnection组件的LoginPrompt和Connected属性为False,及 现在在ServerMethodsUnitDemo单元的TServerMethods1类中添加一个public方法返回一个 type
SQLConnection1: TSQLConnection; SQLDataSet1: TSQLDataSet; private { Private declarations } public { Public declarations } function EchoString(Value: string): string; function ServerTime: TDateTime; function GetEmployees: TDataSet; end; 如你所见,TServerMethods1类中定义了一个叫做GetEmployees的方法,实现如下: function TServerMethods1.GetEmployees: TDataSet; begin SQLDataSet1.Open; // make sure data can be retrieved Result := SQLDataSet1 end; 重新编译服务并运行.注意如你关闭了服务但无法重新编译,可能是DataSnap服务还在运行. 3.1. TSQLSERVERMETHOD 回到DataSnap客户端,TSQLConnection组件应该已经断开到DataSnap服务端的连接(如果仍然连 为了重写原来的ServerMethodsClient单元,推荐从项目中移除原来的ServerMethodsClient后 type TServerMethods1Client = class private FDBXConnection: TDBXConnection; FInstanceOwner: Boolean; FEchoStringCommand: TDBXCommand; FServerTimeCommand: TDBXCommand; FGetEmployeesCommand: TDBXCommand; public constructor Create(ADBXConnection: TDBXConnection); overload; constructor Create(ADBXConnection: TDBXConnection; AInstanceOwner: Boolean); overload; destructor Destroy; override; function EchoString(Value: string): string; function ServerTime: TDateTime; function GetEmployees: TDataSet; end; 为使用GetEmployees方法获取TDataSet,我们可以使用TsqlServerMethod组件.将
本例我们选择TServerMethods1.GetEmployees作为ServerMethodName属性的值.根据 我们使用TDataSetProvider,TClientDataSet和TDataSource来使数据显示在TDBGrid中. 在客户端添加一个TDataSetProvider控件,将其DataSet设置为SqlServerMethod控件.下一步添 最后,放一个TDataSource控件,设置其DataSet为TClientDataSet控件,然后就可以放TGBGrid和 为了在设计时验证连接,我们设置ClientDataSet的Active属性为True.将触发SqlServerMethod 这种方式连接一直保持,TCliendtDataSet和TSQLConnection的被激活,数据显示在TDBGrid中. 这种方式很容易以只读方式查看数据.注意这里说只读,因为TSQLServerMethod不允许 TSQLServerMethod是轻量级方式连接获取只读数据.这样,如果你需要获取DataSnap服务器上的 然而,有时我们需要更新数据,这时就必须使用不同的方法获取数据. 3.2. TDSPROVIDERCONNECTION 如果我们想提交更新,我们就需要TDSProviderConnection组件与服务端的TDataSetProvider关 首先,我们需要修改服务端数据模块,现有的只是返回一个TDataSet,我们必须添加一个实际的 现在,重新编译DataSnap服务,运行.然后修改客户端. 3.2.1. TDSPROVIDERCONNECTION 客户端 为检索到发布的TDataSetProvider组件需要修改一下DataSnap客户端.将TSQLServerMethod和 在前一个例子中,TClientDataSet只设置了一个ProviderName属性.然而,使用 在ProvideName属性的下拉框中显示dspEmployees选项(在服务端的ServerDataModule单元中发 现在我们可以设置TClientDataSet.Active为True,在设计时查看数据. 设置TClientDataSet.Active为True,同时将TSQLConnection.Connected置为True.注意在设计
procedure TForm2.Button2Click(Sender: TObject); begin ClientDataSet1.Open; end; 现在添加代码提交数据变更,保存回服务器. 3.2.2 数据库更新 有两种方法将数据修改保存会服务端:自动和手动.都是调用一下方法,但是会自动调用或手动 对于自动方式,我们可以使用TClientDataSet的数据修改时触发的OnAfterInsert,OnAfterPost procedure TForm2.ClientDataSet1AfterPost(DataSet: TDataSet); begin ClientDataSet1.ApplyUpdates(0); end; 如果发生了更新错误,将会触发TClientDataSet的OnReconcileError事件,更多信息见3.2.3 手动方式发生更新也是使用TClientDataSet的ApplyUpdates方法.但是这时方法不在 procedure TForm2.btnUpdateClick(Sender: TObject); begin ClientDataSet1.ApplyUpdates(0); end; 自动提交的好处当然用户不会忘记将变更保存回服务端.然而,缺点是无法提供Undo能力.一旦 3.2.3. RECONCILE ERRORS TClientDataSet.ApplyUpdates方法有一个参数:应用更新时允许发生的最大错误数量.如果有 幸运的是,Delphi提供了一个很强大的对话框来处理这个问题.当在DataSnap客户端需要做一些 使用Delphi提供的功能,File.New.Other,在Delphi文件子目录中选择Reconcile Error对话框 选中这个图标点击OK,保存为RecError.pas,加入到DataSnapClient项目.这个单元包括了定义 ReconcileErrorForm窗体实例将按需要动态创建.那么如何使用这个特殊的ReconcileErrorForm窗体呢? procedure TForm2.ClientDataSet1ReconcileError(DataSet: TClientDataSet; E: EReconcileError; UpdateKind: TUpdateKind;
这个事件处理程序与四个参数,第一个是抛出错误的TClientDataSet,第二个参数是引发错误冲 - raSkip:不更新这条记录,但在变更日志中保留未提交的变更,下次提交在试. -raAbort:取消记录冲突处理.. -raMerge:将更新记录与远程数据库记录合并,仅在客户端变更修改过的远程字段 -raCorrect:使用正确的值替换更新记录,这需要用户介入. -raCancel:对本记录的修改全部放弃.回到初始值状态. -raRefresh:对本记录修改全部放弃,但重新加载当前数据库的记录值. 关于ReconcileErrorForm不需要考虑全部执行选项.只需要做两件事件.一,在DataSnap客户端主窗体中 procedure TFrmClient.ClientDataSet1ReconcileError(DataSet: TClientDataSet; E: EReconcileError; UpdateKind: TUpdateKind; var Action: TReconcileAction); begin Action := HandleReconcileError(DataSet, UpdateKind, E) end; 3.2.4. 示范冲突错误 现在最大的问题是:实际工作中如何使用的冲突处理?为了测试,需要两个或更多DataSnap客户 -启动服务端应用程序 -启动第一个客户端应用程序,点击链接按钮,获取数据 -启动第二个客户端应用程序,点击链接按钮,获取数据 -使用第一个客户端应用程序,修改第一行数据的FirstName列 -使用第二个客户端应用程序,修改第一行数据的FirstName列 -在第一个客户端应用程序中点击更新按钮 -在第二个客户端应用程序中点击更新按钮,这时将会发生一个或多个错误.因为第一个应用程 -进入更新错误对话框,现在可以处理冲突(忽略(Abort),取消(Abort),合并(Merge),更正(Correct),取消 Skip移动到下一行记录,忽略更新请求.但其更新将保留在更新日志中.Cancel也忽略更新请求 Refresh清除本记录所有的变更记录,并将数据库中的值作为当前记录的值.Merge试图将数据库 Correct是一个强大的选项,在事件处理中给你一个指定更新记录值的机会.需要写代码或弹出 3.3. DATASNAP 数据库部署 部署一个使用数据库的DataSnap服务需要比部署一个简单DataSnap服务的步骤要多些.客户端, 服务端,必须部署数据库驱动.及所选数据库依赖的驱动和文件.使用DBX4,确保发布
Studio\dbExpress\7.0 directory on Windows XP or in the C:\Users\Public\Documents\RAD 3.4. 重用已有的远程数据模块 如果你有一个远程数据模块类,也可以将其组合到新的DataSnap项目中来.但是必须要牺牲一些特性,尤其 首先,如果有一个你要迁移的DataSnap服务应用程序,而不仅仅是一个远程数据模块,你需要使用命令行 {$IF CompilerVersion >= 20} initialization TComponentFactory.Create(ComServer, TRemoteDataModule2010, Class_RemoteDataModule2010, ciMultiInstance, tmApartment); {$IFEND} end. 从项目中移除UpdateRegistry函数或用编译开关修饰: {$IF CompilerVersion >= 20} class procedure UpdateRegistry(Register: Boolean; const ClassID, ProgID: string); override; {$IFEND} 最重要的变更—将项目转换为无COM依赖的DataSnap服务.-及移除类型库(.ridl文件)和类型库导入单元.这 4. DATASNAP 过滤器[FILTER]用法 本节将说明过滤器工作原理,及如何使用已存在的过滤器(如压缩)或创建新的DataSnap过滤器.DataSnap 必须在客户端和服务端指定过滤器.在服务端,必须指定TDSTCPServerTransport组件的过滤器属性列表. 当处理OnConnect事件时,可以检查用于连接的已注册的过滤器,例如使用自定义的日志函数输入日志信息, procedure TServerContainer1.DSServer1Connect( DSConnectEventObject: TDSConnectEventObject); var i: Integer; begin LogInfo('Connect ' + DSConnectEventObject.ChannelInfo.Info); for i:=0 to DSConnectEventObject.Transport.Filters.Count-1 do LogInfo(' Filter: ' + DSConnectEventObject.Transport.Filters.GetFilter(i).Id); end;
作为范例,我们使用已随D2010提供的DataSnap过滤器.可用于在客户端和服务端压缩数据流.这里说的 TDSTCPServer和TDSHTTPService组件都有一个TTransportFiltersCollection类型的Filters属性.点击Filters 注意除了设置服务端TDSTCPServerTransport组件Filters属性外,也需要在客户端指定一个相应的过滤器 如果没有在客户端添加DbxCompressionFilter单元引用,运行客户端后将会抛出异常信息: |
|
来自: aaie_ > 《datasnap》