DELPHI DATASNAP 入门操作(3)简单的主从表的简单更新【含简单事务处理】

DELPHI DATASNAP 2010 入门操作(1)为什么要用datasnap 2010

http://www.cnblogs.com/zhqian/archive/2010/07/06/1771779.html

 

DELPHI DATASNAP 2010 入门操作(2)不写一行代码,绿色三层我也行

http://www.cnblogs.com/zhqian/archive/2010/07/06/1771798.html  

 

很久前写了点入门的资料,写得很不好,但是发现还是有很多人看了转了, 我写的东西都是很简单的,估计在行业里面混了几年的朋友,肯定会骂,这么没水平的,也好意思放出来?不过对于我来说,这不重要,因为,这是一个学习的过程。

这不,东家又让我们失望了,还记得当初橙子把同时支持linux 及MAC的内部每日更新版本的放出来,给停用了很久的EDN ID,不过还好,听说已经恢复了。
   XE出了,因为没有什么功能的更新,群里面的朋友都在戏称delphi XE update 1 为delphi 2010 update 7

在这儿,不去评论XE的好坏,但是传说中的改了4000多个BUG,还是在稳定性方面有所加强的,所以,上一个demo 用的是delphi 2010 做的,今天的DEMO,我改成XE了,向导不太一样了,但是最终生成的代码,没有什么太大的区别,所以,我不再重复向导部份,直接就在上一个DEMO的基础(实际我是全部新做了,上一个DEMO我也不知丢哪儿去了)上,继续我们今天的话题。

    说到主从表,在实际工作中还没有用过,不过上次面试时,凭感觉乱拉下控件,居然也做成功能,当然,有很多细节需要处理的,今天,我也关注不了多少细节。

在主从表更新的过程中,有一个最重要的问题就是主建问题,很多人很喜欢用自增ID做主建,因为这个减少我们的代码编写量,但是当在主从表中用了自增ID时,如何在数据提交前就取得对应的ID,可是一个很大的挑战,网上也看到一些解决方案,不过我没有看的太懂,所以,我今天也放弁了自增ID,用GUID 来取而代之,相信用GUID做出来的程序,能够满足你的业务快速发展的需要!

 

GUID的取值

GUID 按理论上是,其本上不会重复的,我想,不需要我多说吧,不了解的,请谷歌,直接上代码吧

建立GUID的原型代码在 SysUtils 中:
如下:
  1. {$IFDEF MSWINDOWS}
  2. {$EXTERNALSYM CoCreateGuid}
  3. function CoCreateGuid(out guid: TGUID): HResult; stdcall; external 'ole32.dll' name 'CoCreateGuid';
  4.                                                                                                                                              
  5. function CreateGUID(out Guid: TGUID): HResult;
  6. begin
  7.   Result := CoCreateGuid(Guid);
  8. end;
  9. {$ENDIF}
  10. {$IFDEF POSIX}
  11. { CreateGUID }
  12. { libuuid.so implements the tricky code to create GUIDs using the
  13.   MAC address of the network adapter plus other flavor bits.
  14.   libuuid.so is currently distributed with the ext2 file system
  15.   package, but does not depend upon the ext2 file system libraries.
  16.   Ideally, libuuid.so should be distributed separately.
  17.   If you do not have libuuid.so.1 on your Linux distribution, you
  18.   can extract the library from the e2fsprogs RPM.
  19.   Note:  Do not use the generic uuid_generate function in libuuid.so.
  20.   In the current implementation (e2fsprogs-1.19), uuid_generate
  21.   gives preference to generating guids entirely from random number
  22.   streams over generating guids based on the NIC MAC address.
  23.   No matter how "random" a random number generator is, it will
  24.   never produce guids that can be guaranteed unique across all
  25.   systems on the planet.  MAC-address based guids are guaranteed
  26.   unique because the MAC address of the NIC is guaranteed unique
  27.   by the manufacturer.
  28.   For this reason, we call uuid_generate_time instead of the
  29.   generic uuid_generate.  uuid_generate_time constructs the guid
  30.   using the MAC address, and falls back to randomness if no NIC
  31.   can be found.  }
  32. var
  33.   libuuidHandle: NativeUInt;
  34.   uuid_generate_time: procedure (out Guid: TGUID) cdecl;
  35. function CreateGUID(out Guid: TGUID): HResult;
  36. const
  37.   E_NOTIMPL = HRESULT($80004001);
  38. begin
  39.   Result := E_NOTIMPL;
  40.   if libuuidHandle = 0 then
  41.   begin
  42.     // uuid_generate_time lives in libuuid.so.1 on Linux and libc on Mac OSX
  43.     libuuidHandle := dlopen({$IFDEF MACOS}libc{$ENDIF}{$IFDEF LINUX}'libuuid.so.1'{$ENDIF}, RTLD_LAZY);
  44.     if libuuidHandle = 0 then Exit;
  45.     uuid_generate_time := dlsym(libuuidHandle, 'uuid_generate_time');
  46.     if @uuid_generate_time = nil then Exit;
  47.   end;
  48.   if Assigned(uuid_generate_time) then
  49.   begin
  50.     uuid_generate_time(Guid);
  51.     Result := 0;
  52.   end;
  53. end;
  54. {$ENDIF POSIX}
  55. function StringToGUID(const S: string): TGUID;
  56.   procedure InvalidGUID;
  57.   begin
  58.     ConvertErrorFmt(@SInvalidGUID, [s]);
  59.   end;
复制代码
现在,我们就通过调用上面的函数,轻松的实现返回一个GUID,单元如下:
  1. unit UnitGUID;
  2. interface
  3. uses sysutils;
  4. function getGUID():string;
  5. implementation
  6. // 返回一个字符串类型的GUID串
  7. function getGUID():string;
  8. var
  9. Guid:TGUID;
  10. begin
  11. CreateGuid(Guid);
  12. RESULT:=GUIDToString(Guid);
  13. end;
  14. end.

DEMO 数据库的选择

Xe 的DBX 居然连 东家自己的 Blackfish™ SQL  都没有默认驱动了,而DBX又不支持本地库,这不是难为难我麻,要搞个看demo的朋友都了解的库还真不容易,oracle是肯定不会选的,估计没几个人装,而Firebird数据库,估计不是所有人都听过的,MSSQL得装到2008版本,否则还要装个客户端驱动,想了想,算了吧,目前我的需求ADO就满足,为什么就非得用DBx呢,于是,我就选用了ADO+ACCESS来做我的数据库。
数据库结构就不再多说了,这次所做的是一个班级表及一个学生表,关系就是一个学生必须属于一个班级。

 

主从表服务器端设置

在我们的上一个demo 中,在服务器端自动生成了一个类:ServerMethods1,现在我们在界面上放上所需要用到的控件
并设置相关属性
ADOConnection1:
connectionstring设置为: Provider=Microsoft.Jet.OLEDB.4.0;Password="";Data Source=db.mdb;Persist Security Info=True
TADOQUERY1:
name 设置为:ADOQuerymain
locktype设置为:ltReadOnly(由于ADO是双向数据集,但是现在我们的数据回写用DSP负责,所以这儿没有必要再设置为可读写,设置为只读可以减少下载数据的总时间)
Connection设置为:ADOConnection1


TADOQUERY2:
name 设置为:ADOQDetails
locktype设置为:ltReadOnly(由于ADO是双向数据集,但是现在我们的数据回写用DSP负责,所以这儿没有必要再设置为可读写,设置为只读可以减少下载数据的总时间)
Connection设置为:ADOConnection1 TdataSetProvider1:name设置为:DSPmaindataSET 设置为:ADOQuerymain.options.poAllowCommandText设置为:true TdataSetProvider2:name设置为:DSPDetailsdataSET 设置为:ADOQDetails.options.poAllowCommandText设置为:true options.poAllowCommandText 设置为true 的目的是让客户机能直接向服务器提交代码,这提供了方便,但是也开启了安全隐患之门。

 

设置完成后,再加上一行代码确保连接能连到数据库

我们在ServerMethods1的onCreate事件中写下如下代码:

  1. procedure TServerMethods1.DSServerModuleCreate(Sender: TObject);
  2. begin
  3. ADOConnection1.Connected :=True ;
  4. end;
复制代码

设计时界面如图:
1.jpg

主从表客户机设置

先设置DBX连接到服务器:
一、运行服务器端
二、DBX连接设置
SQLConnection1 设置
设置Derver为datasnap(由于我们所有设置都是默认,所以代码不需要设置IP、端口等值)
LoginPrompt为False
现在我们可以把Connected 设置为TRUE看我们的客户机是否能正常连到服务器

DSProviderConnection1设置:
DSProviderConnection设置为: DSProviderConnection1
ServerClassname设置为: TServerMethods1(这个是我们在运行服务器向导时生成的类名)

三、CDS及CDS主从表设置

clientdataset1设置:
name设置为 CDSmain
RemoteServer设置为:DSProviderConnection1
ProviderName设置为:DSPmain

clientdataset2设置:
name设置为 CDSDetails
RemoteServer设置为:DSProviderConnection1
ProviderName设置为:DSPDetails
MasterSourec设置为:CDSmain
MasterFields设置为: 班级编号
IndexFieldNames设置为:班级编号

TDataSource1 设置
name设置为:DataSMain
Dataset设置为:CDSmain

TDataSource2 设置
name设置为: DataSDetails
Dataset设置为: CDSDetails

DBNavigator1设置
name设置为:DBNavigatorMAIN
DataSource设置为:DataSMain

DBNavigator2设置
name设置为:DBNavigatorDetails
DataSource设置为:DataSDetails

DBGrid1设置:
DataSource设置为:DataSMain
双击控件添加所有班级表字段,其中:
班级编号的readonlay设置为true


DBGrid2设置:
DataSource设置为:DataSDetails
双击控件添加所有班级表字段,其中:
学生编号,班级编号,班级名称的readonlay设置为true

放一个工具栏,然后上面放上几个按扭,名称分别设置为:刷新主表,刷新从表,提交主表,提交从表
四、客户端代码编写:

设置完成后,我们开始我们的代码的编写

1、启动时打开数据库:
  1. procedure TForm2.FormCreate(Sender: TObject);
  2. begin
  3. SQLConnection1.Connected :=True ;
  4. with  CDSmain do
  5. begin
  6.   Close ;
  7.   CommandText :='select * from 班级 ';
  8.   Open ;
  9. end;
  10. with CDSDetails do
  11. begin
  12.     Close ;
  13.   CommandText :='select * from 学生表 ';
  14.   Open ;
  15. end;
  16. end;
复制代码
2、刷新主表代码:
  1. procedure TForm2.ToolButton1Click(Sender: TObject);
  2. begin
  3. CDSmain.Close ;
  4. CDSmain.Open ;
  5. end;
复制代码
3、刷新从表代码:
procedure TForm2.ToolButton3Click(Sender: TObject);
begin
CDSDetails.Close ;
CDSDetails.Open ;
end;

4、在主表添加数据时,自动生成GUID做班级编号

点菜单: file -uses unit...
选择UnitGUID后按ok
在CDSmain的onNewRecord 中添加代码,用于插入时生成GUID:
  1. procedure TForm2.CDSmainNewRecord(DataSet: TDataSet);
  2. begin
  3. with DataSet do
  4. begin
  5. FieldByName('班级编号').AsString :=getGUID ;
  6. end;
  7. end;
复制代码
5、主表提交时,班级名称不能为空
在CDSmain的onBeforePost 事件中添加代码
  1. procedure TForm2.CDSmainBeforePost(DataSet: TDataSet);
  2. begin
  3. with DataSet do
  4. begin
  5. if FieldByName('班级名称').AsString ='' then
  6. begin
  7. Application.MessageBox('班级名称不能为空!', '提示信息', MB_OK + MB_ICONINFORMATION +
  8. MB_TOPMOST);
  9. Abort ;
  10. end;
  11. end;
  12. end;
复制代码
6、在主表中无数据时,不让在从表中插入数据
在CDSDetails的onBeforeInsert 事件中添加代码:
procedure TForm2.CDSDetailsBeforeInsert(DataSet: TDataSet);
begin
if (not CDSmain.Active)  or  CDSmain.IsEmpty  then
begin
  Application.MessageBox('请先录入班级信息再录入学生信息!', '提示信息', MB_OK +
    MB_ICONINFORMATION + MB_TOPMOST);
  Abort ;
end;
end;

7、在从表添加数据时,自动生成GUID做班级编号
在CDSDetails的onNewRecord 中添加代码,用于插入时生成GUID及对主表中的值:
  1. procedure TForm2.CDSDetailsNewRecord(DataSet: TDataSet);
  2. begin
  3. with DataSet do
  4. begin
  5. FieldByName('学生编号').AsString :=getGUID ;
  6. FieldByName('班级编号').AsString :=CDSmain.FieldByName('班级编号').AsString   ;
  7. FieldByName('班级名称').AsString :=CDSmain.FieldByName('班级名称').AsString   ;
  8. end;
  9. end;
复制代码
8:保存时学生姓名不能为空
在CDSDetails的onBeforePost事件中添加:
  1. procedure TForm2.CDSDetailsBeforePost(DataSet: TDataSet);
  2. begin
  3. with DataSet do
  4. begin
  5. if FieldByName('姓名').AsString ='' then
  6. begin
  7. Application.MessageBox('学生姓名不能为空!', '提示信息', MB_OK + MB_ICONINFORMATION +
  8. MB_TOPMOST);
  9. Abort ;
  10. end;
  11. end;
  12. end;
复制代码
9在提交主表中:
  1. procedure TForm2.ToolButton5Click(Sender: TObject);
  2. begin
  3. try
  4. CDSmain.ApplyUpdates(0);
  5. Application.MessageBox('已成功地更新到服务器!', '提示信息', MB_OK + MB_ICONINFORMATION +
  6.   MB_TOPMOST);
  7. except
  8. on E:Exception do
  9. begin
  10.   Application.MessageBox(PChar('更新失败,错误代码为:'+E.Message ), '提示信息', MB_OK + MB_ICONINFORMATION +
  11.   MB_TOPMOST);
  12. end;
  13. end;
  14. end;
复制代码
10 在提交从表中
  1. procedure TForm2.ToolButton7Click(Sender: TObject);
  2. begin
  3. try
  4. CDSDetails.ApplyUpdates(0);
  5. Application.MessageBox('已成功地更新到服务器!', '提示信息', MB_OK + MB_ICONINFORMATION +
  6. MB_TOPMOST);
  7. except
  8. on E:Exception do
  9. begin
  10. Application.MessageBox(PChar('更新失败,错误代码为:'+E.Message ), '提示信息', MB_OK + MB_ICONINFORMATION +
  11. MB_TOPMOST);
  12. end;
  13. end;
  14. end;

事务处理

如果我们在提交主表失败后再提交从表,哪么从表中的关系就丢失了,就成了垃圾数据了(其实我们的DEMO在删除主表记录时也会产生垃圾数据,这时也要同时删除从表记录,就留给大家来完成了)

如果我们用上事务,就能保证数据正确提交
在服务器的TServerMethods1 类中加上事务处理代码,如下:
  1. unit ServerMethodsUnit1;
  2. interface
  3. uses
  4.   SysUtils, Classes, DSServer, DB, ADODB, Provider;
  5. type
  6.   TServerMethods1 = class(TDSServerModule)
  7.     ADOConnection1: TADOConnection;
  8.     ADOQuerymain: TADOQuery;
  9.     DSPmain: TDataSetProvider;
  10.     ADOQDetails: TADOQuery;
  11.     DSPDetails: TDataSetProvider;
  12.     procedure DSServerModuleCreate(Sender: TObject);
  13.   private
  14.     { Private declarations }
  15.   public
  16.     { Public declarations }
  17.     function EchoString(Value: string): string;
  18.     function ReverseString(Value: string): string;
  19.     function  BeginTrans:Integer ;
  20.     procedure CommitTrans ;
  21.     procedure RollbackTrans ;
  22.   end;
  23. implementation
  24. {$R *.dfm}
  25. uses StrUtils;
  26. function TServerMethods1.EchoString(Value: string): string;
  27. begin
  28.   Result := Value;
  29. end;
  30. function TServerMethods1.ReverseString(Value: string): string;
  31. begin
  32.   Result := StrUtils.ReverseString(Value);
  33. end;
  34. function TServerMethods1.BeginTrans:Integer;
  35. begin
  36.   Result :=ADOConnection1.BeginTrans;
  37. end;
  38. procedure  TServerMethods1.CommitTrans;
  39. begin
  40.   ADOConnection1.CommitTrans;
  41. end;
  42. procedure TServerMethods1.DSServerModuleCreate(Sender: TObject);
  43. begin
  44. ADOConnection1.Connected :=True ;
  45. end;
  46. procedure  TServerMethods1.RollbackTrans ;
  47. begin
  48.   ADOConnection1.RollbackTrans;
  49. end;
  50. end.
复制代码
在客户机的SQLConnection1控件上右击,再选择:Generate Datasnap client classes
会生成我们的相应的调用单元(已存在的会更新)
2.jpg


定义一个调用类变量
  1. var
  2. s:TServerMethods1Client;
复制代码
在我们调用事务前初始化类
  1. s:= TServerMethods1Client.Create(SQLConnection1.DBXConnection );
复制代码
现在我是在窗口的创建事件中写的代码
  1. procedure TForm2.FormCreate(Sender: TObject);
  2. begin
  3. SQLConnection1.Connected :=True ;
  4. with CDSmain do
  5. begin
  6. Close ;
  7. CommandText :='select * from 班级 ';
  8. Open ;
  9. end;
  10. with CDSDetails do
  11. begin
  12. Close ;
  13. CommandText :='select * from 学生表 ';
  14. Open ;
  15. end;
  16. s:= TServerMethods1Client.Create(SQLConnection1.DBXConnection );
  17. end;
复制代码
在用事务同时更新的单击中添加代码:
  1. procedure TForm2.ToolButton9Click(Sender: TObject);
  2. begin
  3. try
  4. s.BeginTrans ;//启动事务
  5. if CDSmain.ChangeCount >0 then
  6. CDSmain.ApplyUpdates(0);
  7. if CDSDetails.ChangeCount >0 then
  8. CDSDetails.ApplyUpdates(0);
  9. Application.MessageBox('已成功地更新到服务器!', '提示信息', MB_OK + MB_ICONINFORMATION +
  10. MB_TOPMOST);
  11. s.CommitTrans ;//提交事务
  12. except
  13. on E:Exception do
  14. begin
  15. s.RollbackTrans ;//回滚事务
  16. Application.MessageBox(PChar('更新失败,错误代码为:'+E.Message ), '提示信息', MB_OK + MB_ICONINFORMATION +
  17. MB_TOPMOST);
  18. end;
  19. end;
  20. end;
复制代码
3.jpg
下载 (66.4 KB)
10 分钟前
终于完成了代码的编写,测试下吧,别用在生产系统中,特别是事务部份,当你同一时间运行两个事务时,会有另一个报错,就算你修改的数据不同也是这样,原因这儿就不再细说,等有空继续后面的文章时,我们再细入的分析吧!

把代码传上来,大家一起学习提高吧!
DAtasnapXEDEMO.rar (175.12 KB)

db.zip (12.4 KB)
client.zip (1.25 MB)
server.zip (1.47 MB)

 

附件下载请移步我的论坛:http://datasnap.5d6d.com/thread-117-1-1.html

posted @ 2011-01-23 03:16  周黔  阅读(4611)  评论(3编辑  收藏  举报