บทนี้เรามาดูกันต่อเรื่องของการทำการปรับปรุงข้อมูล การทำงานในเรื่องของฐานข้อมูลนั้น ไม่หนีการทำงาน 4 ลักษณะดังนี้
เพิ่มข้อมูล (ใช้คำสั่ง INSERT)
แก้ไขข้อมูล (ใช้คำสั่ง UPDATE)
ลบข้อมูล (ใช้คำสั่ง DELETE)
ดูข้อมูล (ใช้คำสั่ง SELECT)
เมื่อบทที่แล้วนั้น เราได้เรียนรู้การดูข้อมูลไปแล้ว วันนี้เรามาเรียนรู้กันต่อถึงการทำงานอีก 3 ลักษณะที่เหลือ แต่ต้องมาทำความเข้าใจกันก่อนว่า ADODataReader และ SQLDataReader นั้นการทำงานคือการที่ดึงเอาผลลัพธ์ที่ SQL Server หรือ DataSource อื่นส่งมาให้ ส่งต่อให้กับโปรแกรมของเราเลย เราจึงไม่สามารถปรับปรุงข้อมูลที่รับได้โดยตรง ดังนั้น เราจึงต้องใช้วิธีส่งคำสั่ง SQL กลับไปถ้าเราต้องการที่จะปรับปรุงข้อมูล
บางคนอาจคิดว่าวิธีนี้ยุ่งยาก มันก็จริงครับ แต่การที่เราส่งคำสั่งที่ใช้ในการปรับปรุงข้อมูลได้แก่ INSERT, UPDATE, DELETE ไปให้กับ SQL Server โดยตรงนั้น ถือว่าเป็นวิธีที่มีประสิทธิภาพที่สุด ซึ่งต่างกับ DataSource บางประเภท ถ้า DataSource นั้น(เช่น dBase) ไม่สามารถรองรับคำสั่ง SQL ได้ เวลาสั่งงานเป็นตำสั่ง SQL ตัวของ OLEDB ต้องทำการแปลงก่อนหนึ่งชั้น ทำให้โปรแกรมอาจจะทำงานช้ากว่า ที่เราจะใช้เรื่องของ Client Cursor (เราจะได้เรียนในบทต่อไป) เอาเป็นหลักการนี้นะครับ ถ้า DataSource นั้นเข้าใจคำสั่ง SQL ได้โดยตรง เช่น Oracle, MSSQL เป็นต้น การส่งคำสั่งเป็น SQL เลยจะได้ประสิทธิภาพสูงสุด แต่ถ้า DataSource นั้นไม่รองรับคำสั่ง SQL การส่งคำสั่ง SQL ไปอาจจะทำให้การทำงานช้าลง
ในบทนี้ผมเน้นการใช้งานกับ Microsoft SQL Server เท่านั้น โดยที่ผมจะเขียนโปรแกรมดึงข้อมูลทุกรายการจากตาราง authors ที่อยู่ใน pubs ไปเก็บไว้ใน Table anames ที่อยู่ pubs เช่นเดียวกัน โดยที่ผมจะลอกไปเฉพาะ ชื่อและนามสกุลเท่านั้น
ต้องมาคุยกันก่อนว่า ผมไม่ได้สอนคำสั่ง SQL ผมถือว่าพื้นฐานการใช้งานคำสั่ง SQL นั้นทุกคนต้องใช้เป็นอยู่แล้ว ผมเน้นถึง ADO.NET เท่านั้นครับ
เราลองดู Code กัน
|
authors.cs |
using System;
using System.Text;
using System.Data.SQL;
class Hello
{
static SQLConnection CNRead, CNWrite;
static SQLDataReader ReadData(string strSQL)
{
SQLDataReader DR;
SQLCommand CMMRead;
CNRead = new SQLConnection("localhost", "sa", "", "pubs");
CMMRead = new SQLCommand("SELECT * FROM authors", CNRead);
CNRead.Open();
CMMRead.Execute(out DR);
return DR;
}
public static SQLCommand ConnectForWrite()
{
SQLCommand CMMWrite;
CNWrite = new SQLConnection("localhost", "sa", "", "pubs");
CNWrite.Open();
CMMWrite = new SQLCommand();
CMMWrite.ActiveConnection = CNWrite;
return CMMWrite;
}
static void CloseConnections()
{
CNRead.Close();
CNRead = null;
CNWrite.Close();
CNWrite = null;
}
public static void Main()
{
SQLException ee = null;
string strSQL;
SQLDataReader DR = ReadData("SELECT au_lname, au_fname FROM authors");
SQLCommand CMMWrite = ConnectForWrite();
strSQL = "IF EXISTS (SELECT * FROM sysobjects WHERE id = object_id('aname')) DROP TABLE aname";
CMMWrite.CommandText = strSQL;
CMMWrite.ExecuteNonQuery();
strSQL = "CREATE TABLE aname (fname varchar(40), lname varchar(40) )";
CMMWrite.CommandText = strSQL;
CMMWrite.ExecuteNonQuery();
CNWrite.BeginTransaction();
try {
while (DR.Read()) {
strSQL = string.Format("INSERT INTO aname (fname, lname) VALUES ('{0}', '{1}')",
DR["au_fname"].ToString(),
DR["au_lname"].ToString()
);
CMMWrite.CommandText = strSQL;
CMMWrite.ExecuteNonQuery();
}
}
catch (SQLException e) {
ee = e;
}
if (ee == null) {
CNWrite.CommitTransaction();
Console.WriteLine("Done");
} else {
CNWrite.RollbackTransaction();
Console.WriteLine("Command: {0}", strSQL);
Console.WriteLine("Error {0}: {1}", ee.Number, ee.Message);
}
DR.Close();
CMMWrite = null;
CloseConnections();
}
}
|
เวลา Compile คุณต้องใช้ csc /r:system.dll /r:system.data.dll authors.cs
สิ่งที่น่าสนใจในโปรแกรม
SQLConnection() โดยปกติแล้วมีเพียงหนึ่งตัวก็เพียงพอแล้ว สามารถใช้มันการส่งคำสั่ง SQL ได้ไม่จำกัดจำนวน แต่ในโปรแกรมตัวนี้จำเป็นต้องมี 2 ตัว เพราะตัวแรกสำหรับการอ่านนั้นถูกผูกเข้ากับ SQLDataReader() ซึ่งเชื่อมตลอดเวลา ทำให้เราต้องมี SQLConnection() ตัวที่สองสำหรับการเขียน
ลักษณะการทำงานของ SQLConnection() ของโปรแกรมนี้เหมือนกับใน ADO คือ Connect ตลอดเวลา
SQLCommand() ตัวที่ใช้ในการสร้าง SQLDataReader() มีสร้าง SQLDataReader() เสร็จแล้วไม่ได้ใช้งาน เราจึงลบออกจาก Memory ได้ ยึดเป็นหลักไว้ว่า SQLDataReader() ผูกกับ SQLConnection() ไม่ใช่ SQLCommand()
IF EXISTS (SELECT * FROM sysobjects WHERE id = object_id('aname')) DROP TABLE aname คำสั่งนี้เป็นคำสั่งแรกสำหรับการเขียน มันทำการตรวจสอบว่า Table ชื่อ aname มีแล้วหรือยัง ถ้ามีแล้วให้ลบทิ้ง
CREATE TABLE aname (fname varchar(40), lname varchar(40) ) คำสั่งนี้เอาไว้สร้าง Table ชื่อ aname โดยที่มี 2 columns ชื่อ fname และ lname เอาไว้เก็บชื่อหน้าและชื่อหลังของผู้แต่งหนังสือ
โปรแกรมฐานข้อมูลที่ดีต้องมีคุณสมบัติ Atomic กล่าวคือถ้าเรามีข้อมูล 100 รายการที่ต้องการบันทึกลงฐานข้อมูล ถ้าไฟดับระหว่างที่กำลังเขียนรายการที่ 50 เมื่อเปิดเครื่องใหม่ โปรแกรมจะต้อง ยกเลิกรายการทั้ง 50 รายการนั้นกลับไปสถานะเหมือนยังไม่เคยทำอะไรเลย เพื่อเขียนข้อมูลเข้าไปใหม่ แต่ถ้าโปรแกรมฐานข้อมูลไม่มีคุณสมบัติข้อนี้ 50 รายการยังคงอยู่และเป็นขยะ ซึ่งทำให้โปรแกรมเราคำนวณผิดพลาดได้ SQL Server ทำ Atomic โดยการใช้แนวคิดของ Transaction เราเริ่มต้น BeginTransaction ถ้า CommitTransaction ทุกอย่างก็เรียบร้อย แต่ถ้า ไฟดับก่อนโดยที่ยังไม่ CommitTransaction เครื่องจะทำการ RollbackTransaction กลับไปจุดก่อนคำสั่ง BeginTransaction เราสามารถสั่ง RollbackTransaction ได้เองในกรณีที่เราพบข้อมูลพลาดในการเขียนโปรแกรมโดยสั่ง RollbackTransaction ทั้งหมดนี้เป็น Method ของ SQLConnection()
ในข้อมูลนี้ Microsoft
ตั้งใจใส่ข้อมูลแกล้งเอาไว้เพื่อให้ผู้หัดเขียนโปรแกรมเจอกรณียกเว้น
จะได้เขียนโปรแกรมมีบั๊กนัอยลง
ใน Table authors ก็เหมือนกัน
จะมีชื่อคนหนึ่งที่ชื่อว่า
Michael O'Leary จะสังเกตได้ว่า
ชื่อหลังของเขามี
เครื่องหมาย '
ซึ่งเป็นตัวยกเว้นของคำสั่ง
SQL
เวลา Run Program
คุณจะผลลัพธ์ดังนี้ครับ
|
DOS Prompt |
| C:\CS>authors Command: INSERT INTO aname(fname, lname) VALUES ('Michael', 'O'Leary') Error 170: Line 1: Incorrect syntax near 'Leary' C:\CS>_ |
จะเห็นได้ว่าคำสั่งที่เราส่งไปนั้นผิด Syntax เราสามารถดักได้โดย try-catch block ทำให้เราสามารถจัดการเมื่อมี Error เกิดขึ้น ข้อผิดพลาดที่เกิดจากใน ADO.NET นั้นจะชื่อว่า SQLException ซึ่งภายในจะมีตัวเลข Error กำกับพร้อมกับคำอธิบาย เช่นใน ที่นี่เกิด Error หมายเลข 170 ซึ่งก็คือ Syntax Error นั่นเอง
นอกเหนือจากนั้นผมยังแสดงให้ดูว่าเมื่อเกิด Error ขึ้น มันจะเกิดการ RollBack ของข้อมูลทำให้รายการที่ถูก Insert ก่อนหน้านั้นหายหมด คุณลองไป SELECT ข้อมูลดูได้ จะเห็นแค่มี Table เปล่าๆ ไม่มีข้อมูล
แต่ Table aname ยังคงมีอยู่เพราะในโปรแกรมมัน เริ่ม BeginTransaction หลังจากการสร้าง Table เสร็จแล้ว
ทางแก้ปัญหาของ Error ข้างบน ทำได้โดย ถ้าตรงไหนมี ' อยู่ให้เพิ่มเข้าไปอีกตัว เช่น ถ้าต้องการใส่ O'Leary ให้เพิ่มเป็น O''Leary เป็นต้น ดังนั้นผมจะใช้ Regular Expression คำสั่ง Replace มาแปลง ให้ แก้ โปรแกรมดังนี้ครับ
จาก
strSQL = string.Format("INSERT INTO aname (fname, lname) VALUES ('{0}', '{1}')",
DR["au_fname"].ToString(),
DR["au_lname"].ToString()
);
|
เป็น
strSQL = string.Format("INSERT INTO aname (fname, lname) VALUES ('{0}', '{1}')",
Regex.Replace(DR["au_fname"].ToString(), @"'", @"''"),
Regex.Replace(DR["au_lname"].ToString(), @"'", @"''")
);
|
แต่การที่คุณสามารถใช้ Regular Expression ได้นั้น คุณต้องเพิ่ม
using System.Text.RegularExpressions; |
และเวลาคอมไพล์โปรแกรม ต้อง Compile ด้วย
csc /r:system.dll /r:system.data.dll /r:system.text.regularexpressions.dll authors.cs