Archive

Archive for January 17, 2010

ประยุกค์ใช้ 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