วันจันทร์ที่ 21 เมษายน พ.ศ. 2557

Amazing ClientDataSet

เป็นประโยชน์มากครับ 

สวัสดีครับ วันนี้ขอเปลี่ยนแนวจาก Agile จ๋ามาเป็น coding กันบ้าง เกิดเปลี่ยนบรรยากาศครับ พอดีช่วงนี้มีโอกาสจับ Delphi มาปัดฝุ่น ลง Delphi 2010 แล้วเขียน App เล็กๆ ให้เพื่อนๆ กัน ก็เลยนึกขึ้นมาได้ว่า ClientDataSet Component ของ Delphi นี่มันเจ๋งมากแม้ว่าเราจะไม่ได้เขียน Multi-Tier ก็ตาม ก็เลยครึ้มอกครึ้มใจ เอามา share ให้เพื่อนๆ ลองเล่นกันดีกว่า ผมมั่นใจว่ามันต้องมีประโยชน์เพื่อนๆ ที่ใช้ Delphi แน่นอน
ผมจะแบ่งเขียนไปเป็นตอนๆ นะครับ (อีกแล้ว ^^) เพราะมันทําอะไรได้เยอะมาก เขียนทีเดียวไม่จบครับ เพราะขนาดใน Text Book ของ Delphi  ยังแยกออกมาต่างหากบทนึงเลย แต่่จะเขียนได้กี่ตอนก็อีกเรื่องนึงครับ 555
Level ของบทความนี้ คือระดับ Intermediate นะครับ พอทําเป็น ก็จะเริ่มเข้าขั้นเทพไปอีก 1 ก้าว ^^
TClientDataSet (ขอเรียกย่อๆ ว่า CDS นะครับ)
Component ตัวนี้จะว่าไปแล้ว มันก็เป็น Dataset ครอบจักรวาลครับ เพราะเราใช้มันได้ตรงๆ เหมือนพวก TQuery หรือ TADODataSet เลย แต่มันเป็น Dataset สําหรับ Client เท่านั้น ที่พูดแบบนี้ เพราะว่าตัวมันเอง connect ตรงๆ กับพวก TxxxConnection ไม่ได้ ต้องอาศัย TDatasetProvider (เรียกย่อๆ ว่า DSP นะครับ) มาเชื่อมตรงกลางอีกทีหนึ่ง แล้ว DSP ค่อยไปเชื่อมกับ dataset อะไรก็แล้วแต่ เพื่อ insert / update ลงฐาน
ถ้าเอามาแปะใน form ของเดิมจะแปะ component ประมาณนี้
TDBConnection <- TDataset <- TDatasetProvider <- TDataSource <- TDBGrid
แต่ถ้าเราใช้ ClientDataSet จะอยู่ในรูปแบบนี้ครับ (ใน block [ ] คือ component 2 ตัวที่เพิ่มเข้ามา)
TDBConnection <- TDataSet  [ <- TDatasetProvider <- TClientDataSet ] <- TDataSource <- TDBGrid
อ้าว ในเมื่อมันทํางานเดี่ยวๆ ไม่ได้ แล้วยังต้องแปะซะยืดยาว มันจะดียังไงละเนี่ยะ ?
คําตอบก็คือ
  1. มันยืดหยุ่นครับ และ pattern ของการเขียนโปรแกรมจะเป็นหนึ่งเดียว ไม่ว่าเราจะเปลี่ยน component ในการติดต่อ base เป็นค่ายไหนก็ตาม เพราะ layer ที่เราเขียน code มันเป็น layer ที่ CDS เมื่อเปลี่ยน component เช่น เปลี่ยนจาก BDE ไปเป็น ADO หรือ DBExpress การเขียน code กับ ClientDataSet ก็เหมือนเดิม และยังเหมือนกับเวลาเขียน Multi-Tier หรือแม้แต่การเขียน Web Services โดยใช้ CDS ด้วยนะ
  2. มัน cache ข้อมูลเก็บไว้ใน memory ได้ คล้ายๆ กับ MemTable ครับ พอ cache ได้ คราวนี้เราก็เอาไป apply ได้อีกเยอะ
  3. มัน generate SQL statement ได้แบบ dynamic แค่กําหนด property ไม่ต้องเขียน code หรือจะกําหนด SQL statement แบบพิศดารก็ทําได้
  4. มันสามารถ update table ที่ select join มาได้ … ฮ่า ข้อนี้โดน แต่ไม่ได้พูดถึงในตอนนี้นะครับ ^^
แต่วันนี้มันเพิ่งเริ่ม เรามาเล่น feature ง่ายๆ ก่อน คือการ save ข้อมูล offline ที่ฝั่ง client ครับ แต่ผมว่าแค่นี้ก็เมพแล้วครับ ^^
ใช้ CDS Save ข้อมูล offline
เคยไหมครับเวลาเขียน app แบบ client / server เนี่ยะ บางที network มันไม่ค่อยนิ่ง ติดๆ ดับๆ แล้วลูกค้าอยากให้เรา save ข้อมูลแบบง่ายๆ ที่ client ก่อน พอ online ได้ค่อย update ที่ server … แหม อยากได้ง่ายๆ แต่ทํายากอิ๊บอ๋าย … >_<
ถ้าใช้ CDS เรื่องนี้ขนมกรุบเลยครับ ทํายังไง เรามาปะ component กันก่อนดีกว่า ^^
Example
ตัวอย่างนี้ผมใช้ SQL 2008 R2 Express Edition กับ Delphi 2010 นะครับ แต่ถ้าเพื่อนๆ ใช้ Delphi 7 หรือที่ต่ำกว่าก็ได้ครับ เขียนเหมือนกันไม่ได้ต่างกันเลย เพราะ CDS มันมีอยู่ใน version 6-7 ส่วน version ที่เก่ากว่านั้นผมไมแน่ใจนะ ส่วน database ถ้าไม่ได้ใช้ SQL Server จะใช้อะไรก็ได้ตามสะดวก มันได้หมดครับ
รายการของ Component (standard ล้วนๆ) มีดังนี้ครับ
  • ADOConection (ถ้าใช้ DB ตัวอื่นก็เปลี่ยน connection)
  • ADOQuery (ถ้าใช้ DB ตัวอื่นก็เปลี่ยน query)
  • DataSetProvider
  • ClientDataSet
  • DataSource
  • DBGrid (อยากได้ grid สวยๆ ก็เปลี่ยนตามสะดวก)
ส่วนใน Database ผมใช้ table เดียวคือ Customer มี 3 fields คือ ID : intName : nvarchar(50), และ LastName : nvarchar(50) ให้ ID เป็น Primaty Key ครับ
เริ่มปะ component ตัวอย่างนี้มี form เดียวนะครับ
  1. New Project ขึ้นมาใหม่
  2. เอา component ตามรายการข้างบนมาปะลงบน form
  3. กําหนด property ต่างๆ ของ component ซะ
  4. ADOConnection :- กําหนดให้ connection ไปที่ DB และกําหนด “LoginPromp” เป็น “false”
  5. ADOQuery :- กําหนด property “Connection” ให้ชี้ไปที่ “ADOConnection1” แล้วก็ไม่ต้องทําอะไรแล้ว แค่นี้ครับ อ่านไม่ผิด แค่นี้จริงๆ
  6. DataSetProvider :- กําหนด property “DataSet” ไปที่ “ADOQuery1”
  7. DataSetProvider :- (สําคัญ) กําหนด property “Options | poAllowCommandText” เป็น True
  8. ClientDataSet :- กําหนด property “Provider” เป็น “DataSetProvider1”
  9. ClientDataSet :- เขียน query ใน property “CommandText” เพื่อ read ค่ามาจาก DB “select * from Customer”
  10. DataSource :- กําหนด property “DataSet” เป็น “ClientDataSet1”
  11. DBGrid :- กําหนด property “DataSource” เป็น “DataSource1”
  12. ClientDataSet :- Set “Active” เป็น “True” เสร็จแล้ว ลอง run ได้เลย
เมื่อทดลอง run แล้วลองกรอกค่าลง DBGrid ดู จะพบว่าใน DBGrid มีข้อมูล แต่ถ้าไปดูในฐานมันจะไม่มีข้อมูลอะไรลงเลย (โดยปกติ ถ้าเป็นพวก dataset ทั่วไปยกเว้น InterBase พอ post มันจะลงเลย) นั่นเป็นเพราะข้อมูลมันจะถูก cache ไว้ใน memory ของ CDS โดยที่ยังไม่ submit กลับฐานไงครับ แต่ไม่เป็นไร เรื่องนี้เดียวค่อยมาว่ากัน อธิบาย property ในขั้นตอนก่อนหน้านี้ดีกว่า
อธิบาย …
หลักการ update ข้อมูลของ CDS ก็คือ ตัว CDS และ DSP จะทําการแปลงข้อมูลที่ตัวเองเก็บไว้ ให้เป็น SQL command แล้วอาศัย DSP ในการ apply update ลงฐานผ่านไปทาง dataset ที่มันเชื่อมอยู่ครับ เช่น – ถ้าเรา select มา ได้มา 3 record
  • เรา edit 1 record
  • เราลบไป 1 record
  • เรา insert 2 record
ผลลัพธ์ที่ได้ CDS จะส่งข้อมูลให้ DSP เป็น ROW แล้ว DSP จะทําการ gen “insert into” ไป 2 statementgen “update set where” ไป 1 statement และ “delete from where” อีก 1 statement แล้วฝาก DSP execute command พวกนี้ให้อีกทีครับ
การกําหนด property “Options | poAllowCommandText” เป็น True ของ DataSetProvider ในข้อที่ 7. จะเป็นตัวที่บอกว่า เราจะยอมให้มี SQL Command ที่ ClientDataSet หรือไม่ (ถ้าไม่กําหนดเวลา clientDataSet.Active := true มันจะ error) เพราะบางครั้งเราเขียน multi-tier เป็น app server เราอาจจะเขียนที่ ADOQuery ส่วน ClientDataSet ไม่เขียนอะไรก็ได้ วิธีนี้จะเป็นการกําหนด logic ของ SQL ที่ app server เท่านั้น ที่ client แค่ดูดไป และ update กลับมาก็พอ คล้ายๆ จะทําเป็น Thin Client ไงครับ logic อยู่ที่แม่อย่างเดียว
คราวนี้เราจะ save ลงฐานละ ให้เอา Button มาแปะบน form แล้วเขียน code ใน onClick แบบนี้ครับ แค่นี้ ข้อมูลก็จะถูก save ลงฐานแล้วครับ
procedure TForm1.Button1Click(Sender: TObject);
begin
ClientDataSet1.ApplyUpdates(0);
end;
parameter “0” หมายความว่า เราจะยอมให้เกิด error เมื่อ write ลงฐานกี่ record ส่วนใหญ่ก็กําหนดเป็น 0 หรือ -1 เพราะเราไม่ยอมให้มี error อยู่แล้ว แต่ในบางกรณี เราใช้
MySQL ซึ่งใช้ table แบบ backhole หรือ MyISAM มันจะยอมให้มี error ได้ งานแบบนี้ก็เช่น งานที่ติดต่อกับเครื่องจักรหรืออุปกรณ์อิเล็คทรอนิคซึ่งมีการ submit ข้อมูลเร็วมากๆ แล้วยอมให้มี error ได้ มันจะมีประโยชน์ตอนนี้แหละครับ
ถึงเวลา Save Offline
ถ้าสังเกตุการทํางานของ CDS จะเห็นว่า มัน cache ข้อมูลใน memory ได้ ถ้าปิดโปรแกรมไป ข้อมูลเหล่านี้ก็จะหายไปด้วย เพราะ object โดน free ไป ใน CDS เราสามารถเอา cache เหล่านี้ save ไว้ใน harddisk ได้ และมันจะ load ขึ้นมาให้ตอนที่เปิดโปรแกรมใหม่ เจ๋งมั้ยเล่า … ^^
วิธีการ ง่ายมากกกกก ให้กําหนด property “FileName” ครับ ผมกําหนดไว้ที่ “C:\Temp\Customer.xml” ครับ กําหนดแค่นี้ จากนั้นให้คุณ run โปรแกรม เพิ่มข้อมูลลงใน DBGrid แต่อย่า save ครับ ให้ปิดโปรแกรมเลย (สมมติผมใส่ไป 2 record) จากนั้นไปดูที่ file Customer.xml ที่เรากําหนดไว้ จะได้ข้อมูลแบบนี้ครับ
<?xml version=”1.0″ standalone=”yes”?>
<DATAPACKET Version=”2.0″>
<METADATA>
<FIELDS>
<FIELD attrname=”ID” fieldtype=”i4″/>
<FIELD attrname=”Name” fieldtype=”string.uni” WIDTH=”100″/>
<FIELD attrname=”LastName” fieldtype=”string.uni” WIDTH=”100″/>
</FIELDS>
<PARAMS CHANGE_LOG=”1 0 4 2 0 4″/>
</METADATA>
<ROWDATA>
<ROW RowState=”4″ ID=”1″ Name=”Name 1″ LastName=”Last Name 1″/>
<ROW RowState=”4″ ID=”2″ Name=”Name 2″ LastName=”Last Name 2″/>
</ROWDATA>
</DATAPACKET>
ได้ XML file ที่สวยงาม ไม่ต้องทําอะไรเลย ถ้าไป query ในฐานก็จะไม่มีข้อมูล เพราะเราไม่ได้ apply ลงไป
คราวนี้ run โปรแกรมใหม่ จะเห็นว่า ข้อมูลมันยังอยู่ใน grid อยู่ ให้กดปุ่ม Save ที่เขียนไว้ก่อนหน้านี้ (CDS จะ ApplyUpdate) คราวนี้ ข้อมูลลงฐานเรียบร้อยแล้ว … ง่ายไหม
สังเกตุว่า ในไฟล์ xml แต่ละ ROW จะมี RowState อยู่ด้วย นั่นคอื CDS จะเก็บ state ของแต่ละ row เอาไว้ เมื่อทําการ apply update ตัว CDS ก็จะเอาข้อมูลเหล่านี้ apply กลับไปที่ฐานให้นั่นเองครับ คุณลอง read ข้อมูลมา ลอง insert, update, delete แล้วปิดโปรแกรม จากนั้นไปเปิดดูไฟล์ xml ดู จะเห็นว่ามันมี row state อีกหลายแบบเลย
สิ่งที่ต้องระวังก็คือ ถ้า update ลง table เดียวกัน record เดียวกันตรงๆ เป็นไปได้ที่อาจจะเกิดการ update จาก 2 เครื่องที่ขัดแย้งกัน เช่น key ชนกัน หรือไป update ใส่ record ที่ delete ไปแล้ว เพราะฉะนั้นเพื่อหลีกเลี่ยงกรณีเหล่านี้ ให้ update ลง table ที่เป็นพวก transaction แล้วไปเขียน trigger ที่ table นั้นๆ เพื่อทํางานต่อ หรือเอาไปใช้กับงานที่ insert ข้อมูลโดยที่ไม่ทับกันจะดีกว่าครับ แต่ทั้งนี้ก็ขึ้นอยู่กับการกําหนด Isolation Level ของ DB ด้วยนะ ว่าเป็นแบบไหน อาจจะเป็น Dirty Read หรือ Read Submitted การ write ข้อมูลก็จะต่างกันไป ส่วนนี้ขอให้ไปศึกษาต่างหากนะครับ ผมคงไม่ลง detail มันเยอะอะครับ
สุดท้าย file type ของ .xml เราอาจจะใช้เป็น .cds ก็ได้ ข้อแตกต่างคือ .cds จะเก็บเป็น binary format ข้อดีคือ read / write เร็วกว่า เพราะ xml มันเป็น text ยิ่ง file ใหญ่ ยิ่ง read / write ช้า แต่ข้อเสียคือถ้ามันเกิดปัญหา เราจะเปิดดูหรือแก้ .cds ลําบาก ไม่ใช้แก้ไม่ได้นะ แก้ได้
การเปิดไฟล์ .cds หรือ .xml มาเปิดด้วย ClientDataSet นั้น เราเปิดขึ้นมาได้เลยครับ  โดยกําหนด FileName ใน CDS แล้ว Active := True ข้อมูลมาเลย ไม่ต้องกําหนด provider ให้วุ่นวาย read / write ได้ด้วย เจ๋งโคด เหมือนใช้พวก DB file เก่าๆ อย่าง fox หรือ dBase เลย แต่ข้อดีของ xml คือ มันเปิดด้วย text editor แก้มือได้เลยครับ เพราะฉะนั้นถ้าข้อมูลไม่เยอะ ก็ xml ดีกว่า
จบแล้ว หวังว่าคงจะได้ประโยชน์ไม่มากก็น้อย มีคําถาม ไปคุยกันต่อใน forum ตาม link นี้นะครับ

อ้างอิงจาก : http://www.thaideveloperexpert.org/headline/404

ไม่มีความคิดเห็น:

แสดงความคิดเห็น