Archive

Archive for the ‘ADO.NET’ Category

การใช้งาน ADO.NET กับ วิธีปฏิบัติที่เป็นเลิศ [ Best practices ]

ผมครุ่นคิดอยู่นานว่า การใช้งาน Component ต่าง ๆ ใน .NET Framework นั้น จะทำอย่างไร จึงจะสามารถใช้ได้อย่างมีประสิทธิภาพเนื่องจาก document หรือ คู่มือต่าง ๆ ที่เราพอหาได้นั้นจะเป็นคู่มือแนะนำการใช้งาน กว่าเราจะเข้าใจการทำงานอย่างถ่องแท้และสามารถเลือกการปฏิบัติในการใช้งานด้วยตัวเองได้ดี ก็ต้องลองใช้งานไปแล้วหลายโครงการ ซึ่งในที่สุดเราก็ต้องหวนกลับมาปรับปรุง code เหล่านั้นใหม่เมื่อพบว่าลักษณะการใช้งานเหล่านั้นขาดประสิทธิภาพสิ้นดี

โชคดีครับที่ พอหาเรื่องเหล่านี้ได้ใน internet มีผู้ใช้งานมากมายรวมถึงผู้ผลิตเองเขียนแนะนำการใช้งาน หรือแนวทางในการใช้งานเพื่อให้ได้ประสิทธิภาพสูงสุดหรือที่เรียกว่า Best practice ในการใช้งาน Library หรือ components นั้น ๆ ซึ่งในหัวข้อนี้ จะขอพูดถึงการใช้งาน ADO.NET

ในบทความนี้จะกล่าวถึงหลักปฏิบัติ หรือแนวทางในการปฏิบัติเพื่อใช้งาน ADO.NET ให้ได้อย่างมีประสิทธิภาพ คือ เพิ่มความเร็วและประหยัดเนื้อที่การใช้งานและลดภาระให้กับ network ซึ่งจะประกอบด้วยหัวข้อต่าง ๆ เหล่านี้

– .NET Framwork Data providers
– การใช้งาน DataReaders, DataSets, DataAdapters, and DataView
– การใช้ Commands
– การใช้ Connections
– การทำงานร่วมกับ XML

          ความเข้าใจใน ADO.NET technology อย่างถ่องแท้จะทำให้เราสามารถพัฒนาระบบได้เต็มประสิทธิภาพ และได้ศักยภาพสูงสุดของการทำงาน รวมถึง ความสามารถในการขยายระบบ และ function การทำงานที่ เป็นไปตามทิศทางของระบบที่ ควรเป็น ในบทความพูดถึงการปฏิบัติที่ดีในการใช้ objects ใน ADO.NET และ ให้คำแนะนำที่จะช่วยให้เราสามารถที่จะปรับปรุง การออกแบบ Application ที่ใช้ ADO.NET ให้ดียิ่งขึ้นและมีประสิทิภาพสูงสุด

.NET Framework Data Providers

Data provider เปรียบเสมือน สะพานเชื่อมระหว่าง application และ data source  ซึ่ง data provider ทำหน้าที่ในการส่ง ผลของ query จาก data source หลังจากที่ประมวลผล คำสั่งที่ data source แล้ว และ ส่งการเปลี่ยนแปลงใน DataSet กลับไปยัง Data source

เพื่อให้ได้ประสิทธิภาพสูงสุด ในการพัฒนา Application ควรเลือกใช้ .NET Framework data provider ให้เหมาะสมกับ Data source  data provider มีให้เลือกใช้อยู่หลายตัวครับ ตารางด้านล้างแสดงข้อมูล ของ data provider กับ data source ที่เหมาะสมกัน

Provider

Details

SQL Server .NET Data Provider Found in the System.Data.SqlClientnamespace.Recommended for middle-tier applications using Microsoft SQL Server version 7.0 or later.Recommended for single-tier applications using the Microsoft Data Engine (MSDE) or Microsoft SQL Server 7.0 or later. 
OLE DB .NET Data Provider Found in the System.Data.OleDbnamespace.Recommended for middle-tier applications using Microsoft SQL Server 6.5 or earlier, or any OLE DB provider that supports the OLE DB interfaces listed in OLE DB Interfaces Used by the OLE DB .NET Data Providerin the .NET Framework SDK. (OLE DB 2.5 interfaces are not required.)For Microsoft SQL Server 7.0 or later, the .NET Framework Data Provider for SQL Server is recommended.Recommended for single-tier applications using a Microsoft® Access database. Use of an Access database for a middle-tier application is not recommended.

Support for the OLE DB Provider for ODBC (MSDASQL) is disabled. For access to Open Database Connectivity (ODBC) data sources, an ODBC .NET Data Provider download is available and will be included in the .NET Framework SDK version 1.1.

ODBC .NET Data Provider The ODBC .NET Data Provider for is available for download.Found in the Microsoft.Data.Odbcnamespace.Provides access to data sources that are connected to using an ODBC driver.Note   The ODBC .NET Data Provider will be included in upcoming versions of the .NET Framework starting with version 1.1. The namespace for the included ODBC .NET Data Provider is System.Data.Odbc.
.NET Data Provider for Oracle The Microsoft .NET Data Provider for Oracle is available for download.Found in the System.Data.OracleClientnamespace.Provides access to Oracle data sources (version 8.1.7 and later).Note   The .NET Data Provider for Oracle will be included in upcoming versions of the .NET Framework starting with version 1.1.
Custom .NET Data Provider ADO.NET provides a minimal set of interfaces to enable you to implement your own .NET Framework data provider. For more information about creating a custom data provider, see Implementing a .NET Data Provider in the .NET Framework SDK.
SQLXML Managed Classes The release of XML for Microsoft SQL Server 2000 (SQLXML 3.0) contains SQLXML Managed Classes that enable you to access the XML functionality of Microsoft SQL Server 2000 and later, from the .NET Framework. For example, these classes enable you to execute XML templates, perform XML Path Language (XPath) queries over data at the server, or perform updates to data using Updategrams or Diffgrams.Building on the functionality from SQLXML 1.0 and 2.0, SQLXML 3.0 introduces Web Services to SQL Server 2000. With SQLXML 3.0, Stored Procedures and XML Templates can be exposed as a Web Service through SOAP.SQLXML 3.0 is available for download

อ้างอิงจาก http://msdn.microsoft.com/en-us/library/ms971481.aspx

การเชื่อมต่อกับ SQL Server ตั้งแต่ Version 7.0 ขึ้นไป

เพื่อให้ได้ประสิทธิภาพสูงสุด เมื่อต้องการเชื่อมต่อกับฐานข้อมูล Microsoft SQL server ควรเลือกใช้ SQL Server .NET Data provider.  SQL Server .NET Data provider นั้นออกแบบให้ เข้าถึง SQL Server โดยตรง โดยที่ไม่ต้องเกี่ยวข้องกับ technology อื่น ๆ  จากภาพด้าน แสดง  techonolgy ต่าง ๆ ที่ช่วยให้ เข้าถึง SQL Server

Figure 1. Connectivity methods for accessing SQL Server 7.0 or later

อ้างอิงจาก http://msdn.microsoft.com/en-us/library/ms971481.aspx

การเชื่อมต่อ ODBC Data Sources

ODBC .NET Data provider อยู่ใน namespace ที่ชื่อว่า Microsoft.Data.Odbc มีสถาปัตยกรรม แบบเดียวกับ  SQL Server .NET และ OLE DB Data provider. และใช้ connection strings มาตรฐานของ ODBC.

–>  ODBC.NET Data provider จะถูกรวมไว้ใน .NET Framework ตั้งแต่ version 1.1 เป็นต้นไป ซึ่งจะอยู่ใน namespace ชื่อ System.Data.Odbc

การใช้งาน DataReaders  DataSets DataAdapters และ DataViews

ADO.NET มี Object ที่ใช้ในการเข้าถึงข้อมูล และเก็บไว้ใน Memory คือ DataSet และ DataReader

–      DataSet เก็บข้อมูลในรูปแบบ relational ไว้ใน หน่วยความจำ  ประกอบด้วย ตาราง ข้อมูล ลำดับ และ ความสัมพันธ์ระหว่างตาราง

–      Data reader ให้ Streamของข้อมูล ในลักษณะไปข้างหน้าอย่างเดียว และอ่านได้อย่างเดียว

การ ใช้ DataSet มักใช้ควบคู่กับ DataAdapter เพื่อเชื่อมต่อไปยัง data source และมักต้องใช้ DataView เพื่อใช้ในการ sorting และ filtering ข้อมูลใน DataSet นอกจากนี้ DataSet ยังสามารถที่จะสร้าง strongly typed เพื่อให้สามารถเข้าถึงข้อมูลในตารางในลักษณะ property ของ object.

ในหัวข้อถัดไปจะกล่าวถึงการใช้งาน DataSet และ DataReader ทั้งเรื่องการใช้งานการ optimize การเข้าถึงข้อมูล รวมถึงเคร็ดลับการ optimize การใช้งาน DataAdapter และ DataView

คงต้องไว้คราวหน้าแล้วหล่ะครับ คราบขอไว้แค่นี้นะครับ ขอให้สนุกกับการเขียนโปรแกรม

s_teerapong2000@yahoo.com

Teerapong sontayaman

Categories: ADO.NET, DOT NET Tags:

ประยุกค์ใช้ Template patterns และ Generics เพื่อสร้าง Data Access Layer

January 17, 2010 2 comments

ในบทความนี้แสดง การทำงานร่วมกัน ของ  template pattern และ  Generics ของ .Net framework  เพื่อสร้าง Data Access Layer …..

เพื่อความเข้าใจ ขออธิบาย template pattern ก่อนนะครับ   สำหรับ template pattern นั้น จุดประสงค์ของการใช้งานนั้นคือ เพื่อที่จะแบ่ง code ที่ต้องการให้ เปลี่ยน กับ code ที่ไม่ต้องการให้เปลี่ยน    ตัว template pattern นั้นใช้สำหรับ code ที่คงที่ และถูกเรียกใช้งานบ่อยใน class  ดังนั้นหลากเราพบว่าต้องเขียน code เดิม ๆ บ่อยครั้ง เราสามารถลดงานตรงนั้นได้ด้วย template pattern.

เพื่อให้เห็นภาพ เราลองมาดูตัวอย่างกันนะครับ  ลักษณะของงานที่จะต้องใช้ template pattern ดังที่พูดไปก็คือ มี class หลาย class ที่มี การทำงาน คล้ายกัน หรือ มีลักษณะของ พฤติกรรมคล้าย ๆ กัน  ครับตัวอย่างของเราก็คือ  GetTheFancyNumber()

// DoSometing class
Public class DoSomething
{
   private int m_number;
   public int GetTheFancyNumber()
   {
     int  theNumber = m_number;
     // Do lats of crazy staff to theNumber here.
     Return theNumber;
   }

}

// DoSomethingElse class
Public class DoSomethingElse
 {
    private int m_number;
    public int GetTheFancyNumber()
    {
      int theNumber = m_number;
       // Do lots of crazy staff to theNumber Here.
       return theNumber;
    }
 }

ตัวอย่างพยายามแสดงให้เห็นว่า ทั้งสอง class นั้น มี การทำงาน เหมือนกัน เราสามารถที่นำ code นั้นมาใส่ไว้ใน base class และ ส่วนที่ต่างกันยกให้เป็นหน้าที่ของ class ลูก หรือ class ที่สืบทอดไปจัดการต่อ จากในตัวอย่างของเรานั้น เราจะนำ “GetTheFancyNumber()” method ใส่ไว้ใน base class และทำการ encapsulate สำหรับการกำหนดค่าให้กับ ตัวแปร theNumber ให้ class ที่สืบทอดไป ดำเนินการเอง ครับ ดังนั้น code ที่เราจะได้สำหรับการสร้าง base class จะได้ดังนี้

Public abstract class DoSomethingBase
{
   protected abstract int GetTheNumber();
   public int GetTheFancyNumber()
   {
       int theNumber = GetTheNumber();
       // do something to theNumber here.
       return theNumber;
    }
}

เราสร้าง child class เราใช้ logic ที่ encapsulated ด้วย template ( GetFancyNumber()) และทำการ implement ในส่วนที่ class ลูก อาจจะใช้วิธีการหาตัวเลขแตกต่างกัน ก็คือ GetTheNumber() ครับ ดังตัวอย่าง

Public class DoSomethingElse : DoSomethingBase
{
    private int m_number;
    protected override int GetTheNumber()
    {
        return m_number;
    }
}
Public class DoSomething : DoSomethingBas
{
    private int m_number;
    protected override int GetTheNumber()
    {
         return m_number;
    }
}

ครับ ในส่วนของ template pattern นี้เราคงพอได้ concept แล้วนะครับ สำหรับ template pattern นี้หากเรานำมาใช้งานร่วมกับ generics แล้วจะได้ประสิทธิภาพมาก ซึ่งจะได้ดูในตัวอย่างต่อไป ครับ

ในส่วนต่อไปเมื่อเข้าใจ การประยุกค์ใช้ template design pattern แล้ว เราจะนำ template นี้มาทำงานร่วมกับ generics ครับ
ในเรื่องของ การทำงานกับ database นั้น มีอยู่งานหนึ่งที่เราต้องทำ ทุกครั้งคือ การเข้าถึง database แล้วดึงข้อมูลออกจาก database และ สร้าง object ของข้อมูลนั้นเพื่อนำมาใช้งานต่อไป ส่วนของงานดังกล่าวนี้ ก็คือ DAL (Data access layer ) ครับ ซึ่งเราจะลองมา implement ด้วย template pattern. กันครับ
สมมุติว่าเรามี table ที่ชื่อว่า Person ดังนี้ครับ
Create table [tblPerson](
[PersonId] [int] IDENTITY (1,1) NOT NULL,
[FirstName] [Nvarchar] (50),
[LastName] [Nvarchar] (50),
[Email] [Nvarchar] (50),
CONSTRAINT [PK_tblPerson] PRIMARY KEY CLUSTERED
([PersonId]) ON [PRIMARY]
) ON [PRIMARY]
และเราสร้าง class สำหรับการเก็บข้อมูล Person ในแต่ละ record ดังนี้ครับ

 public class Person
{
     string m_email;
     string m_firstname;
     string m_lastname;
     int m_id;

     Persion()
     {
     }
     public string Email
      {
           get{ return m_email;}
           set{ m_email = value;}
      }
     public string FirstName
     {
           get{ return m_firstname; }
           set{ m_firstname = value; }
     }
    public string LastName
    {
          get{return m_lastname;}
          set{m_lastname = value;}
    }
    public int Id
    {
         get{return m_id;}
         set{m_id = value; }
    }
}

ครับ ในการ access database มีอยู่ 2 สิ่งที่เราต้องทำ เสมอคือ
1 access database – run คำสั่ง (command) – get result
2 เรา นำข้อมูลที่ได้ (result) มาสร้างเป็น object
ทั้งสอง ขั้นตอนนี้ เหมาะสำหรับการ implement ด้วย template pattern นะครับ เราลองมาดูที่ ข้อ 2 ก่อนนะครับ เราเรียกมันว่า mapping
เราจะทำการสร้าง Base class ที่มีหน้าที่ในการ map ข้อมูลที่ได้จากการ ประมวลผล command มาสร้างเป็น object ของข้อมูลนั้น และนำมาใส่ไว้ใน collection นะครับ เราจะสร้าง base class ที่เป็น generic นะครับมีหน้าตอดังนี้

Abstract class MapperBase<T>
{
     protected abstract T Map(IDataRecord record);
     public Collection<T> MapAll(IDataReader reader)
     {
        Collection <T> collection = new Collection<T>();
        while(reader.Read())
        {
             try{
                collection.add(Map(reader));
             }catch{
                   throw;
             }
        }
        return collection;
     }
}

ต่อไปเราลองมาดูการ สืบทอด จาก MapperBase เพื่อสร้าง Person objects ดูนะครับ

    public class PersonMapper :MapperBase<Person>
    {
        protected override Person Map(IDataRecord record)
        {
            //throw new Exception("The method or operation is not implemented.");
            try
            {
                Person p = new Person();
                p.Id = (DBNull.Value == record["PersonId"]) ? 0 : (int)record["PersonId"];
                p.FirstName = (DBNull.Value == record["firstname"]) ? string.Empty : (string)record["firstname"];
                p.LastName = (DBNull.Value == record["lastname"]) ? string.Empty : (string)record["lastname"];
                p.Email = (DBNull.Value == record["email"]) ? string.Empty : (string)record["email"];

                return p;
            }
            catch (Exception)
            {

                throw;
            }
        }
    }

เป็นงัยครับ ง่ายไหม ครับการสร้าง mapper ให้กับ class ที่เราดึงข้อมูลมาจาก ตารางข้อมูล

ต่อไปเป็นการ ใช้ template pattern + generice เพื่อสร้าง data access
เรามาดูกันว่า งานอะไรบ้างที่ต้องมีการเปลี่ยนแปลงหรือ มีรายละเอียดของงานต่างกัน สำหรับการเข้าถึงแต่ละ table ครับ
1 การได้มาซึ่ง connection
2 การสร้าง sql command และ sql command type
3 และ การได้มาซึ่ง mapper (จากหัวข้อที่แล้ว)
ซึ่งเราจะกำหนด ให้เป็น abstract method ดังนี้

IDbConnection GetConnection();
string CommandText { get; }
CommandType CommandType { get; }
Collection GetParameters(IDbCommand command);
MapperBase GetMapper();

ตามที่ได้ทราบแล้วนะครับ methods ดังกล่าวนั้นจะต้องถูก overrided ด้วย class ใดก็ตามที่ สืบทอด base class ที่จะกล่าวต่อไป นี้
ส่วนงานที่เราสมมุติว่าต้องมีกระบวนการเหมือน เราก็ใส่ไว้ใน method ที่ชื่อว่า Execute() ซึ่งจะเป็นผู้สร้าง collection ของ object ที่เราต้องการ

    public abstract class ObjectReaderBase<T>
    {
        protected abstract IDbConnection GetConnection();
        public abstract string CommandText { get;}
        public abstract CommandType Commandtype { get;}
        protected abstract Collection<IDataParameter> Getparameters(IDbCommand command);
        protected abstract MapperBase<T> GetMapper();

        public Collection<T> Execute()
        {
            Collection<T> collection = new Collection<T>();
            using (IDbConnection connection = GetConnection())
            {
                IDbCommand command = connection.CreateCommand();
                command.CommandText = CommandText();
                command.CommandType = Commandtype();
                foreach (IDataParameter param in this.Getparameters(command))
                    command.Parameters.Add(param);

                try
                {
                    connection.Open();
                    using (IDataReader reader = command.ExecuteReader())
                    {
                        try
                        {
                            MapperBase<T> mapper = GetMapper();
                            collection = mapper.MapAll(reader);
                            return collection;
                        }
                        catch (Exception)
                        {

                            throw;
                        }
                        finally
                        {
                            reader.Close();
                        }
                    }
                }
                catch (Exception)
                {

                    throw;
                }
                finally {
                    connection.Close();
                }
            }
        }

    }

และต่อไปนี้เป็นตัวอย่างของ การใช้งาน ObjectReaderBase โดยเราจะทำการ สร้าง child class ชื่อ PersonReader ดังนี้

class PersonReader: ObjectReaderBase<Person>
{
    private static string m_connectionString =
         @"Data Source=DATA_SOURCE_NAME;Initial Catalog=Test;Integrated Security=True";

    protected override System.Data.IDbConnection GetConnection()
    {
        // update to get your connection here

        IDbConnection connection = new SqlConnection(m_connectionString);
        return connection;
    }
    protected override string CommandText
    {
        get { return "SELECT PersonID, FirstName, LastName, Email FROM tblPerson"; }
    }

    protected override CommandType CommandType
    {
        get { return System.Data.CommandType.Text; }
    }

    protected override Collection<IDataParameter> GetParameters(IDbCommand command)
    {
        Collection<IDataParameter> collection = new Collection<IDataParameter>();
        return collection;

        //// USE THIS IF YOU ACTUALLY HAVE PARAMETERS
        //IDataParameter param1 = command.CreateParameter();
        //param1.ParameterName = "paramName 1"; // put parameter name here
        //param1.Value = 5; // put value here;

        //collection.Add(param1);

        //return collection;
    }

    protected override MapperBase<Person> GetMapper()
    {
        MapperBase<Person> mapper = new PersonMapper();
        return mapper;
    }
}

ครับคงได้แนวความคิด สำหรับการใช้งาน template pattern บวกกับ generics นะครับ คงพอมี ideas สำหรับการนำไปปรับปรุงใช้งาน หรือนำไปเพิ่มเติมให้ได้ตามความต้องการใช้งาน นะครับ
ครับ เพื่อการนำไปใช้ ท่านก็คงต้องเพิ่มเติม วิธีการเข้าถึง database ในรูปแบบต่าง ๆ เช่น ExecuteNonquery() หรือ ExecuteScalar() ครับ สุดท้ายมาดูการ ใช้งานสักนิดนะครับ

static void Main(string[] args)
{
    PersonReader reader = new PersonReader();
    Collection<Person> people = reader.Execute();

    foreach (Person p in people)
        Console.WriteLine(string.Format("{0}, {1}: {2}",
            p.LastName, p.FirstName, p.Email));

    Console.ReadLine();

}
<pre>

ครับสิ่งหนึ่งที่เราได้ ประโยชน์จากการใช้งานรูปแบบนี้นั้น ก็คือเราสามารถลดงานที่ต้องเขียน code ลงไปมากเนื่องจาก เราได้ลดส่วนที่ต้องทำทุกครั้งเหมือน ๆ กัน แยกออกไป จากส่วนที่ ต้องทำงานแตกต่างกันในแต่ละครั้ง ทำให้การ บำรุงรักษา นั้นง่ายและสะดวก และลดจำนวน code ที่ต้องเขียนลงมากครับ
ธีระพงษ์ สนธยามาลย์ Soft Speed solution ‘s Senior Programmer , s_teerapong2000@yahoo.com
ที่มาของบท ความ An elegant C# Data Access Layer using the Template Pattern and Generics , By  Matthew Cochran May 22, 2006
และบทความที่ ใกล้เคียงครับ http://www.codeproject.com/KB/database/BusinessObjectHelper.aspx

การป้องกันการโจมตีด้วย SQL Injection , SQL Injection Attacks

December 29, 2008 2 comments

ในเรื่องที่จะพูดถึงนี้ น่าจะพูดได้ว่าเป็นเกร็ดเล็กเกร็ดน้อยที่ ผู้พัฒนาควรจะรู้และเป็นประโยชน์มากเมื่อนำไปประยุกต์ใช้งาน เรื่องที่จะพูดถึงก็คือ การป้องกันการโจมตีด้วย

SQL Injection มันเป็นอย่างไร พูดอย่างง่าย ๆ ก็คือ กระบวนการหรือการ ส่งหรือใส่ SQL code ไปยัง application โดยที่ผู้พัฒนาไม่ได้ตั้งใจหรือไม่ได้เจตนาที่ทำให้เกิด SQL code แบบนั้น ( เกิดจากบุคคลที่ 3 ด้วยความปรารถนาที่เป็นลบนั่นเอง ) คงพอจะเข้าใจนะครับ  ซึ่งเหตุการณ์แบบนี้จะเกิดขึ้นได้ก็ต่อเมื่อ application นั้นถูกออกแบบมาอย่างไม่ดีเท่าที่ควร หรือที่เรียกว่า poor design นะครับ  — ส่วนใหญ่มันมักถูกจะออกแบบมาแบบนั้นซะด้วยซิ ว่าไหมครับ  — ซึ่งมันก็จะเกิดกับ application ที่ใช้เทคนิค การสร้าง SQL String ให้กับ Command Object โดยการรับข้อมูลมาจาก ผู้ใช้งาน คงจะนึกภาพไม่ออก มาดูตัวอย่างเพื่อให้มองเห็นภาพอย่างชัดเจนดีกว่านะครับ

String sql =
“SELECT Orders.CustomerID, Orders.OrderID, COUNT(UnitPrice) AS Items, “ +
“SUM(UnitPrice * Quantity) AS Total FROM Order “ +
“INNER JOIN OrderDetails “ +
“ON Orders.OrderID = OrderDetail.OrderID “ +
“WHERE Orders.CustomerID = ‘” + txtID.Text +” ’ ” +
“GROUP BY Orders.OrderID, Orders.CustomerID”;

SqlCommand cmd = new SqlCommand(sql,connection);

จากตัวอย่าง ผู้บุกรุกอาจจะทดสอบ SQL statement ด้วยวิธีการต่าง ๆ โดยจุดประสงค์แรกก็คือเพื่อให้ได้มาซึ่ง error Message ซึ้งถ้า Error นั้นไม่ถูกปล่อยปะละเลยหรือไม่ถูกดำเนินอย่างเหมาะสมแล้วละก็ System Error message อาจถูกแสดงออกไป และเป็นประโยชน์ต่อผู้บุกรุกที่จะนำไปใช้ต่อไปได้อีก
ตัวอย่าง ลองคิดกันดูนะครับว่าจะเกิดอะไรขึ้นถ้า user ป้อนข้อมูลนี้ลงใน TextBox จากตัวอย่างข้างต้น

ALFKI’ OR ‘1’=’1

จะส่งผลให้ SQL Statement เป็นดังนี้

SELECT Orders.CustomerID, Orders.OrderID, COUNT(UnitPrice) AS Items,
SUM(UnitPrice * Quantity) AS Total FROM Orders
INNER JOIN [Order Details]
ON Orders.OrderID = [Order Details].OrderID
WHERE Orders.CustomerID = ‘ALFKI’ OR ‘1’=’1′
GROUP BY Orders.OrderID, Orders.CustomerID

พิจารณา Expression หลัง WHERE จะเห็นว่า ข้อมูลทั้งหมดในตาราง Orders จะถูกแสดงออกมา ใช่ไหมครับ ผมคงไม่ต้องอธิิบายรายละเอียดนะครับ เนื่องจากว่า มันเป็น Logic ของ OR ใช่ไหมครับ ถ้าค่าด้านหนึ่งด้านใด เป็น จริง ก็จะส่งผลให้ทั้ง Statement เป็น จริงแมนบ่ ครับ  น่านแหละครับ 1=1 เป็น จริง ไม่ว่า CustomerID จะเป็นอะไรก็ตาม ทั้งหมดเป็นจริง

sqlinject11ครับ คิดต่ออีกนิดถ้าหากว่าข้อมูลดังกล่าวนั้นเป็นข้อมูลที่ Sensitive หละครับ เช่น Social Security numbers  วันเกิด   หรือ  ข้อมูล Credit card   ละก็จะถือว่านี้เป็นปัญหาใหญ่หลวง ซึ่งอาจก่อให้เกิดความเสียหายมหาศาล ใช่ใหม่ครับ

ตัวอย่างวิธีการที่ซับซ้อนมากกว่านั้นขึ้นไปอีกเช่น
ผู้บุกรุกสามารถ comment ส่วนท้ายของ SQL Statement โดยใช้ — สำหรับ SQL Server ( MySql ใช้ # และ Oracle ใช้ ; ) หรือ ใช้ การสั่งงานแบบ Batch ได้เพื่อเพิ่ม SQL Code เข้าไปทำงานอย่างใดอย่างหนึ่ง ในกรณีนี้ สำหรับ SQL Server ก็แค่เพียงเพิ่ม semicolon และตามด้วย Command ที่ีต้องการ ซึ่งทำให้ผู้บุกรุกสามารถ ลบข้อมูลใน Table อื่นได้ หรือ สามารถเรียกใช้ SQL Server xp_cmdshell system stored procedure เพื่อประมวลผลโปรแกรม ที่ command line ได้ เป็นไงหล่ะครับ ไปได้ถึงขนาดนั้น
ครับลองดูตัวอย่างนี้นะครับ ถ้าผู้ใช้งานกรอกข้อมูลนี้ลงใน txtID TextBox ลองพิจารณาดูนะครับ

ALFKI’ ; DELETE * FROM Customers —

หมายความว่า หลังจากที่ได้แสดงข้อมูล Orders ของ CustomerID เท่ากับ ALFKI แล้ว ต่อด้วยการลบข้อมูลทั้งหมดในตาราง Customers และยกเลิก คำสั่งต่อท้ายที่เหลือทั้งหมด ครับ

ว่ากันด้วยการป้องกัน —
ต่อมา เราลองมาดูกันว่าเราจะทำอย่างไรถึงจะสามารถจัดการหรือป้องกันการโจมตีแบบนี้ ได้บ้าง
แน่นอนครับ สำหรับโปรแกรมเมอร์แล้วเราสามารถการปฏิบัติตาม แนวทางที่เราเชื่อว่าเป็นแนวทางการปฏิบัติที่ดีเช่น การจำกัดความยาวของข้อมูลที่อนุญาตให้ป้อนลงใน TextBox โดยการกำหนด TextBox.MaxLength ไม่ให้ยาวเกินความจำเป็น ซึ่งเป็นการลดโอกาสที่ ผู้รุกจำสามารถป้อน script ยาว ๆ ลงไปได้ ยิ่งไปกว่านั้น เราต้องทำการจำกัดข้อมูลของ Error message ถ้าเราดัก exception ของ database เราก็ควรที่จะแสดงเฉพาะข้อความที่เป็นคำอธิบาย เช่น “Data source error” แทนที่จะแสดง ข้อมูลใน Exception.Message ซึ่งอาจจะเป็นจะเป็นการชี้ โพรงให้กระรอกได้ ( ช่องโหว่ของระบบได้ )

และยิ่งไปกว่านั้น คุณก็ต้อง จัดการกับ character พิเศษต่าง เช่น แปลง single quotation marks (‘) เป็น two quotation marks (“) เพื่อให้ไม่ทำให้เกิดความสับสนกับ ตัวปิดหัวท้าย

string ID = txtID.Text().Replace(“‘”,”””);

ครับ ไม่ได้หมายถึงมันจะจบสิ้นนะครับ มันจะนำมาซึ่ง ความปวดหัวเพิ่มเข้าไปอีกถ้า ค่าของ txtID.Text นั้นมี apostrophes ขึ้นมา ซึ่งมันอาจจะเกิดขึ้นได้ อีกต่อ ๆ ไปซึ่งสรุปได้ว่า แนวทางปฏิบัติดังกล่าวไม่สามารถแก้ไขปัญหา SQL Injection ได้อย่างสมบูรณ์แบบ ผมสรุปแบบนี้ไม่ใช่ว่ามันจะจบลงแบบนี้หรอกนะครับ เพียงแต่จะบอกว่ามีวิธีการที่ดีกว่านั้นสำหรับการป้องกัน SQL injection attack ซึ่งผมจะไ้ด้กล่าวต่อไป

การป้องกัน SQL injection attack โดยการ ใช้ Parameterized Commands

Parameterized Command ใช้ placeholders ใน SQL text ซึ่ง Parameterized command นี้เป็นตัวกำหนด dynamic values ที่จะส่งผ่านมาทาง Parameters collection ของ Command object ดูดังตัวอย่าง นะครับ
จาก SQL Statement
SELECT * FROM Customers WHERE CustomerID = ‘ALFKI’
เปลี่ยนเป็น
SELECT * FROM Customers WHERE CustomerID = @CustID

ลองมาดูตัวอย่างเต็ม ๆ กันนะครับ ( สมมติว่าเราสร้าง connection object ไว้แล้ว )

String sql =
“ SELECT Orders.CustomerID, Orders.OrderID, COUNT(UnitPrice) AS Items, “ +
“ SUM(UnitPrice * Quantity) AS Total FROM Orders “ +
“ INNER JOIN OrderDetails “ +
“ ON Order.OrderID = OrderDetails.OrderID “ +
“ WHERE Orders.CustomerID = @CustID “ +
“ GROUP BY Orders.OrderID, Orders.CustomerID” ;
SqlCommand cmd = new SqlCommand(sql,con);
cmd.Parameters.Add(“@CustID”, txtID.Text);

ถ้าเราลองทำ SQL injection attack กับ code ที่ถูกปรับปรุงใหม่แล้ว เราจะบบว่าโปรแกรมจะไม่ส่งข้อมูลใดออกมาแสดงผลเลยเนื่องจาก นั่นก็หมายความว่าไม่มี order items ใดที่มีข้อมูล customerID มีค่าเท่ากับ ALFKI’ OR ‘1’=’1 ซึ่งเป็นสิ่งที่คุณต้องการใช่ไหมครับ

การป้องกัน SQL injection attack โดยการ ใช้ Stored Procedures
Stored Procedures ผมคงจะไม่อธิบายว่ามันคืออะไรทำงานอย่างไรหรอกนะครับ คิดว่าท่านทั้งหลาย รู้จักดีอยู่แล้ว แต่จะขอกล่าวถึงประโยชน์ของมันสักนิดหนึ่ง คือ

  • ง่ายต่อการบำรุงรักษา หมายถึงว่าตัว stored procedures นั้นมันแยกอยู่ต่างหากกับโปรแกรมที่เรียกใช้มัน เพราะฉะนั้นหากจะต้องมีการเปลี่ยนแปลงอะไรที่ stored procedure ก็สามารถทำได้โดยไม่ต้องมีการ Recompile ทั้งโปรแกรมนั่นเอง
  • ทำใ้ห้เราสามารถใช้งาน database ในวิถีทางที่ปลอดภัยได้ เราสามารถที่จะกำหนดให้ stored procedures สามารถเข้าถึงเฉพาะ tables ที่กำหนดได้
  • – ทำให้ประสิทธิภาพการทำงานดีขึ้น เนื่องจาก stored procedures สามารถรวม หลาย ๆ คำสั่งทำงานในคราวเดียวกันได้ ซึ่งทำให้เราสามารถทำงาน ให้เสร็จหลาย ๆงานพร้อมกันได้ต่อหนึ่งรอบการเข้าถึง database

มาดูตัวอย่างกันดีกว่านะครับว่าจะใช้งานอย่างไร ซึ่งจริงแล้วไม่ก็คือ การใช้ Place holder กับ Parameterized Command Object นั่นแหละ ครับ เพียงแต่เราเรียกใช้ stored procedure แทนที่จะใช้การส่งผ่าน sql text สมมติว่าเราสร้าง Stored procedure ไว้ดังนี้นะครับ

CREATE PROCEDURE InsertEmployee
@TitleOfCourtesy varchar(25),
@LastName varchar(20),
@FiratName varchar(10),
@EmployeeID int OUTPUT
AS
INSERT INTO Employees ( TitleOfCourtesy, LastName, FirstName, HireDate )
VALUES (@TitleOfCourtesy, @LastName, @FirstName, GETDATE());

SET @EmployeeID = @@IDENTITY
GO

Stored procedures มี parameters 3 ตัวคือ TitleOfCourtesy, LastName, และ FirstName ซึ่ง return ID ของ record ออกมาทาง output parameters ชื่อว่า @EmployeeID

ต่อไปเราสร้าง SqlCommand เพื่อเรียกไปยัง stored procedure command นี้มี input Parameters 3 ตัวเช่นเดียวกัน และ ส่งค่า ID ของ record ใหม่ออกมาให้ต่อไปเป็นตัวอย่างของการสร้าง

SqlCommand cmd = new SqlCommand(“InsertEmployee”, con);
cmd.CommandType = CommandType.StoredProcedure;

ต่อไปเราก็เพิ่ม parameters ของ stored procedures เข้าไปที่ Collection ของ Command.Parameters ซึ่งเราต้องกำหนด data type และ ขนาดของ parameter นั้นด้วย ดังนี้

cmd.parameters.Add(new SqlParameter(“@TitleOfCourtesy”, SqlDbType.NVarChar,25));
cmd.Parameters[“@TitleOfCourtesy”].Value = title;
cmd.Parameters.Add(new SqlParameter(“@LastName”, SqlDbType.NVarChar, 20));
cmd.Parameters[“@LastName”].Value = lastName;
cmd.Parameters.Add(new SqlParameter(“@FirstName”, SqlDbType.NVarChar, 10));
cmd.Parameters[“@FirstName”].Value = firstName;

cmd.Parameters.Add(new SqlParameter(“@EmployeeID”, SqlDbType.Int, 4));
cmd.Parameters[“@EmployeeID”].Direction = ParameterDirection.Output;

สังเกตว่า Parameter ตัวสุดท้ายนั้นเป็น output parameter นะครับ

สุดท้ายเราก็ทำการ run

con.Open();
try
{
int numAff = cmd.ExecuteNonQuery();
HtmlContent.Text += String.Format(“Inserted {0} record(s)
“, numAff);
// Get the newly generated ID.
empID = (int)cmd.Parameters[“@EmployeeID”].Value;
HtmlContent.Text += “New ID: ” + empID.ToString();
}
finally
{
con.Close();
}

ครับทั้งหมด ก็มาจบตรงที่ สองวิธีการสุดท้ายที่เราสามารถที่จะป้องกันการ โจมตี ด้วย SQL Injection ได้อย่างมีประสิทธิภาพ ก็คือการใช้ Parameterized Commands และการใช้ Stored procedures นั่นเอง นี่เป็น ส่วนหนึ่งของการแก้ปัญหานี้นะครับ
หากต้องการข้อมูลเพิ่มเติมละก็ ผมใช้ข้อมูลจากหนังสือเล่มนี้นะครับ Pro ASP.NET 2.0 in C# 2005

การทำ Master-Detail โดยใช้ BindingSource,Master-Detail using BindingSource

December 23, 2008 2 comments

ในการสร้าง Database Application นั่นในบางครั้งเราอาจจะต้องการ การแสดงผลในลักษณะที่เป็น Master-Detail คือมีตารางหลักและตารางที่แสดงผล Detail อีกหนึ่งตาราง เมื่อคลิกที่ record ใด record หนึ่งในตารางหลัก ก็จะนำไปสู่การแสดงผลที่ตารง Detail ที่สัมพันธ์กับข้อมูลในตารางหลัก ซึ่งการทำในลักษณะนี้เราเรียกกันว่า Master-Detail นะครับ ส่วนวิธีการนั้นก็มีวิธีการทำได้หลากหลายนะครับตั้งแต่ แบบลูกทุ่ง ไปจนถึง Best Practices ในบทความนี่เราจะหล่าวถึงวิธีที่การทำโดยใช้ component ที่ชื่อว่า BindingSource ถ้าใครเป็นแฟนของ ADO.NET ก็คงพอจะรู้ว่า มันเป็น component ที่เริ่มมีใช้ใน version 2.0 นะครับ ถ้าผมไม่เลอะเลือน  ซึ่งวิธีการนี้จะทำให้ programmer ลดงานต่าง ๆ ลงมากและ ลดข้อผิดพลาดลงได้มากนะครับ ไม่ต้องเสียเวลาไปพะวงกับการ ตรวจสอบมากจนเกินไป

สำหรับการทำงานผมจะกล่าวถึง concept ในการทำก่อน นะครับ ตัวอย่างของแนวคิดนี้ก็เช่น เรามีตาราง Orders และ OrderDetails โดยทั้งสองตารางนี้เราอ้างอิงกันโดยใช้ OrderID นะครับ ดังรูป

pic2

แล้วใน windows Form นั้นเราต้องการแสดงผลดังนี้

dotnet_databinding_3

นั่นก็คือเราใช้ DataGridView กับทั้งสอง table แล้ว Binding ด้วย BindingSource ให้กับทั้งสอง table ซึ่งเดี๋ยวเราจะมาดูกันว่าเราจะ Binding อย่างไร และเราก็จะต้อง สร้าง Relation ของทั้งสองตารางนี้ ใน DataSet ที่เรา Fill ทั้งสองตารางนี้ลงไปด้วย คือเราจะทำการ Fill ทั้งสอง Table ลงใน Dataset เดียวกันแล้วก็สร้าง Relation ระหว่างทั้งสอง Tables นี้แล้วจึง ทำการ Binding ให้กับ DataGridView นะครับ นั่นคือ Concept ทั้งหมดในการทำ ต่อไปเรามาดูขั้นตอนวิธีการทำเลยดีกว่านะครับ

สมมุติว่าเราได้สร้าง windows Form แล้วและเราได้ นำเอา DataGridView มาวางใน Form แล้วตั้งชื่อให้ว่าเป็น dgvOrders และ dgvOrderDetails นะครับ  อีกนิดหนึ่งนะครับ ฐานข้อมูลถ้าเราใช้ Microsoft Access เราต้อง  using System.Data.OleDb หรือ  ถ้าเราใช้ Microsoft SQLServer เราต้อง using System.Data.SqlClient นะครับ

  • เราก็จะต้องประการตัวแปร object ต่าง ที่เราจะต้องใช้งานเสียก่อนนะครับ   เช่น  OleDbConnection, OleDbCommand, OleDbDataAdapter, BindingSource
  • .
    .
    using System.Data.OleDb;

    namespace WindowsApplication1
    {
    public partial class Form1 : Form
    {
    OleDbConnection myConnection;
    OleDbCommand myCommand;
    OleDbDataAdapter myDataAdapter;
    BindingSource orders_BindingSource;
    BindingSource ordersDetail_BindingSource;
    DataSet myDataset;
    public Form1()
    {
    InitializeComponent();
    }
    private void Form1_Load(object sender, EventArgs e)
    {

    }
    .
    .
    .
    }

  • ต่อไป เราจะทำการ Binding ใน  Form1_load นะครับ ในที่นี้ผมจะไม่กล่าวถึง Connection String นะครับ ทุกท่านคงจะรู้แล้วนะครับ  เราจะกำหนด Sql String ไว้สองชุดเพื่อการเข้าถึงข้อมูลในแต่ละ Table จะใช้ Sql String อันเดียวก็ได้นะครับ ผมใช้สองชุดก็เพื่อให้การอธิบายมันชัดเจน แล้วเราจะใช้  myCommand กำหนด SelectCommand ให้กับ myDataAdapter และ เราก็จะ Fill ลงใน DataSet ซึ่งเรา จะทำการ Fill ลงไปสองครั้ง ดูตามตัวอยางก็แล้วกันนะครับ private void Form1_Load(object sender, EventArgs e)
            {
                string ConnectionString = GetConnectionString();
                myConnection = new OleDbConnection(ConnectionString);
                myConnection.Open();

                string SqlString1 = “Select * from Orders”;
                string SqlString2 = “Select * from OrderDetails”;

                myCommand = myConnection.CreateCommand();
                myCommand.CommandType = CommandType.Text;
                myCommand.CommandText = SqlString1;

                myDataAdapter = new OleDbDataAdapter();
                myDataAdapter.SelectCommand = myCommand;

                myDataSet = new DataSet();

                myDataAdapter.Fill(myDataSet, “Orders”);

                myCommand.CommandText = SqlString2;

                myDataAdapter.Fill(myDataSet, “OrderDetails”);

            }

  • ครับต่อไปเราจะสร้าง Relation ระหว่าง 2 Tables นี้นะครับ เราจะตั้งชื่อ Relation นี้ว่า เป็น Oreder_OrderDetail นะครับ  และเราก็จะใช้ OrderID เป็น ตัวข้อมูลอ้างอิงของทั้งสองตารางดังนี้นะครับดูตามตัวอย่างเลย นะครับ private void Form1_Load(object sender, EventArgs e)
    {
    .
    .
    .
                myDataAdapter.Fill(myDataSet, “Orders”);

                myCommand.CommandText = SqlString2

                myDataAdapter.Fill(myDataSet, “OrderDetails”);
                // —- สร้าง relation —–
                DataRelation myDataRelation = new DataRelation(“Order_OrderDetails”,
                                              myDataSet.Tables[“Orders”].Columns[“OrderID”],
                                              myDataSet.Tables[“OrderDetails”].Columns[“OrderID”]);
                //————————
    orders_BindingSource = new BindingSource();
    ordersDetail_BindingSource = new BindingSource();

    // –กำหนด DataSource ให้กับ Bindingsource
    orders_BindingSource.DataSource = myDataSet;
    orders_BindingSource.DataMember = “Orders”;

    ordersDetail_BindingSource.DataSource = orders_BindingSource;
    ordersDetail_BindingSource.DataMember = “Order_OrderDetails”;

    // –Binding ให้กับ Datagridview ทั้งสอง
    dgvOrders.DataSource = orders_BindingSource;
    dgvOrderDetails.DataSource = ordersDetail_BindingSource;
            }

  • ต่อไปทำ การสร้าง BindingSource และกำหนด DataSource ให้กับ BindingSource ทั้งสองนะครับ และก็ต่อไปเลยก็คือ Binding ให้กับ DataGridView ทั้งสองด้วย ดูต่อจาก code ด้านบนนะครับ…
  • ครับ ถ้าดูจากตัวอย่าง code แล้วคงจะพอเข้าใจได้ไม่ยากนะครับ ทั้งหมดนี้ก็เป็นการ ทำการ Binding ในลักษณะ Master-Details ซึ่งผมเคยลองหาใน internet แล้วก็ค่อนข้างที่หายากนิดหนึ่งไม่ค่อยมีใครอธิบายละเอียดละออเป็นภาษาไทยนะครับ ผมก็พยายามอธิบายให้ละเอียดเพื่อเพื่อน โปรแกรมเมอร์คนไทยด้วยกัน  ในเรื่องของการ Binding แบบนี้ นะครับ เป็นคุณลักษณะที่ ใช้กับ ADO.NET 2.0 ขึ้นไปนะครับ รู้สึกว่าถ้าต่ำกว่านี้เขาจะไม่มี  BindingSource ครับ  จริงแล้วการ Binding นั้นยังมีอีกมากนะครับ ในตัวอย่างที่ผมกล่าวมาแล้วนี้เป็นลักษณะ  one to many นะครับ ยังมีแบบ many to many อีก ซึ่งคงต้องเอาไว้มีเวลาแล้วจะมาลงไว้ให้นะครับ  หรือถ้าใครมีแหล่งข้อมูลก็ช่วย post comment ให้ด้วยนะครับเพื่อ เป็นวิทยาทาน ให้กับเพื่อนโปรแกรมเมอร์ด้วยกัน นะครับ  s_teerapong2000@yahoo.com
  • สำหรับหนังสือที่เรียกได้ว่าเป็นสุดยอดของเรื่องนี้ก็ต้องเล่มนี้นะครับ  “Data Binding with Windows Forms 2.0 – Programming Smart Client Data Applications with .NET” นะครับ