Search This Blog

Sunday, July 10, 2016

Design Patterns trong các dự án thực tế - Template Method Pattern

Lời nói đầu

Trước khi tìm hiểu về Template Method Pattern, hãy đọc lại bài viết Design patterns là gì nếu bạn vẫn chưa biết design patterns là gì. Ngoài ra để giúp bạn dễ dàng nắm bắt nội dung bài viết, hãy chắc là bạn đã biết về Lập trình hướng đối tượng (OOP) và các tính chất, khái niệm của nó như bao đóng (encapsulation), trừu tượng (abstract), kế thừa (inheritance), đa hình (polymorphism), đối tượng (object), lớp (class), giao diện (interface)...
***
Template Method Pattern là gì?

Template Method Pattern là một trong các pattern thuộc nhóm behavioural pattern, nó định nghĩa ra một khung sườn hay một template, là các thao tác cần phải xử lí của một chức năng nào đó. Các thao tác này có thể được các class con kế thừa và xử lí lại mà không làm thay đổi, ảnh hưởng đến cấu trúc bên trong chức năng đó.

Áp dụng Template Method Pattern

Template Method Pattern được sử dụng khá nhiều trong mô hình Abstract class (cha) - Concrete Class (con) khi chúng ta muốn các Concrete class tự thực thi xử lí theo cách của nó, nhưng đồng thời vẫn đảm bảo tuận theo những ràng buộc nhất định từ Abstract class, ví dụ như ràng buộc về thứ tự các bước, hay ràng buộc về đầu vào, đầu ra...

Trong pattern này, Abstract class định nghĩa ra một template method để thực hiện một chức năng nào đó. Template method này sẽ gọi đến các method khác bên trong Abstract class để tạo dựng nên bộ khung. Nhưng có thể các method đó sẽ không được thực thi bên trong Abstract class, mà sẽ được override và thực thi lại bên trong các Concrete class.

Hãy xem hình vẽ ví dụ bên dưới

Chúng ta có một Abstract class Sport bao gồm một Template methodPlay(). Bên trong Template method này sẽ bao gồm 3 method là Initialize() dùng để thiết lập các giá trị ban đầu, Start() để bắt đầu và End() để kết thúc. Chúng ta cũng có 2 Concrete class FootballBasketBall kế thừa từ Abstract class Sport và override 3 method Initialize(), Start() và End() để thực thi lại. Lúc này Template method Play() vẫn giữ được cấu trúc ban đầu nhưng đã được thực thi khác nhau tùy thuộc vào Concrete class.

Trong dự án của chúng ta, các bạn có thể đã gặp trường hợp cùng một chức năng, cùng một luồng xử lí nhưng từng class lại có những method đặt tên khác nhau, cách xử lí khác nhau, việc truy xuất dữ liệu khác nhau..v.v. Điều này gây trở ngại trong việc kiểm tra cũng như review hệ thống. Việc tuân theo những quy tắc, luồng (flow) nhất định khá là quan trọng, giúp cho việc kiểm tra, review trở nên thuận lợi hơn, đảm bảo luồng xử lí vận hành trơn tru, đồng nhất và không xảy ra ngoại lệ sanh lỗi.

Lấy ví dụ chúng ta có các Form nhập liệu. Mỗi khi mở Form lên thì việc chúng ta cần phải làm là:

- Khởi tạo giao diện theo design (InitializeComponent)

- Khởi tạo giao diện theo yêu cầu

- Phân quyền chức năng

- Hiển thị dữ liệu

.v.v

Các thao tác này hầu như các Form bắt buộc phải trải qua khi mở Form lên. Nếu thiếu một trong các thao tác này thì có thể dẫn đến một vài bug nào đó không mong muốn, chính vì thế ta nên làm sao để tất cả các Form đều phải trải qua tuần tự các bước như trên. Một điểm nữa là rõ ràng không có Form nào giống Form nào 100% các thao tác, có thể giao diện khác nhau, dữ liệu khác nhau..., vì vậy chúng ta cũng cần phải thiết lập các thao tác làm sao cho các Form có thể tùy biến lại.

Khi đã xác định được yêu cầu, thì việc tiếp theo là chúng ta phải tạo ra được một Abstract class, mà theo ví dụ ở đây là một BaseForm.


BaseForm của chúng ta sẽ có một Template method LoadForm, bên trong sẽ bao gồm các method khác dùng để thiết lập thông số, controls, dữ liệu...Chúng ta cũng có 2 ConcreteForm kế thừa thực thi lại các method.

Dưới đây là minh họa cho ví dụ của chúng ta

ĐẦU TIÊN tạo một BaseForm như hình vẽ. BaseForm của chúng ta gồm một GroupBox dùng để chứa các control. Hai button Save và Close dùng để lưu dữ liệu và đóng form.

BaseForm của chúng ta xử lí cho chế độ Add, Edit View nên chúng ta cũng tạo một enum Mode
   public enum Mode  
   {  
     View,  
     Add,  
     Edit,  
   }  

View Code (F7) để cập nhật code-behind của BaseForm
   public partial class BaseForm : Form  
   {  
     public Mode Mode { get; set; }  

     public BaseForm()  
     {  
       InitializeComponent();  
       this.Load += BaseForm_Load;  
     }  

     /// <summary>  
     /// Form_Load event  
     /// </summary>  
     void BaseForm_Load(object sender, EventArgs e)  
     {  
       //invoke template method  
       this.LoadForm();  
     }  

     /// <summary>  
     /// Template method  
     /// </summary>  
     public void LoadForm()  
     {  
       this.InitControl();  
       this.SetControl();  
       this.SetAuthority();  
       this.SetData();  
     }  

     /// <summary>  
     /// Initialize control  
     /// </summary>  
     public virtual void InitControl()   
     {  
       this.AcceptButton = this.btnSave;  
       this.CancelButton = this.btnClose;  
       this.btnSave.Click += btnSave_Click;  
       this.btnClose.Click += btnClose_Click;  
     }  

     /// <summary>  
     /// Set control's status  
     /// </summary>  
     public virtual void SetControl() { } 
 
     /// <summary>  
     /// Set control's data
     /// </summary>  
     public virtual void SetData() { }  

     /// <summary>  
     /// Check authority and set button's status  
     /// </summary>  
     public virtual void SetAuthority()  
     {  
       this.btnSave.Enabled = HasAuthority("Search");  
     }  

     /// <summary>  
     /// Check authoriry  
     /// </summary>  
     private bool HasAuthority(string authority)  
     {  
       return true;  
     }
   }  

Đây là Template method LoadForm của chúng ta
     /// <summary>  
     /// Template method  
     /// </summary>  
     public void FormLoad()  
     {  
       this.InitControl();  
       this.SetControl();  
       this.SetAuthority();  
       this.SetData();  
     }  

và sẽ được gọi trong sự kiện Form_Load.
     /// <summary>  
     /// Form_Load event  
     /// </summary>  
     void BaseForm_Load(object sender, EventArgs e)  
     {  
       //invoke template method  
       this.FormLoad();  
     }  

Các method bên trong cần được khai báo hoặc là abstract hoặc là virtual. Mục đích là để các Concrete class có thể override và xử lí lại nếu cần thiết. Trong ví dụ này vì BaseForm không thiết lập abstract nên Kiên để tất cả method đều là virtual, nhưng thông thường với một số method mà chỉ có thể xử lí ở Concrete class thì nên được khai báo là abstract, còn nếu method có thể vừa có xử lí ở base class, vừa có xử lí ở concrete class thì nên khai báo là virtual.

     /// <summary>  
     /// Initialize control  
     /// </summary>  
     public virtual void InitControl()   
     {  
       this.AcceptButton = this.btnSave;  
       this.CancelButton = this.btnClose;  
       this.btnSave.Click += btnSave_Click;  
       this.btnClose.Click += btnClose_Click;  
     }  

     /// <summary>  
     /// Set control's status  
     /// </summary>  
     public virtual void SetControl() { } 
 
     /// <summary>  
     /// Set control's data
     /// </summary>  
     public virtual void SetData() { }  

     /// <summary>  
     /// Check authority and set button's status  
     /// </summary>  
     public virtual void SetAuthority()  
     {  
       this.btnSave.Enabled = HasAuthority("Add");  
     }  

Chúng ta có 2 method xử lí hoàn toàn ở Concrete Form:
- SetControl(): dùng để ẩn/hiện các input control tùy theo Mode (Add, Edit, View)
- SetData(): dùng để thiết lập dữ liệu ban đầu cho các input control.
Ngoài ra có 2 method vừa xử lí ở BaseForm, vừa xử lí ở Concrete Form:
- InitControl(): dùng để thiết lập, khởi tạo các giá trị ban đầu cho Form.
- SetAuthority(): dùng để kiểm tra quyền để ẩn/hiện các button.

TIẾP THEO chúng ta tạo ConcreteForm1 kế thừa từ BaseForm này và chỉ design thêm 2 bộ control Employee Name như hình vẽ. Hãy nhớ thiết lập modifier cho các control trên BaseFormProtected.



View Code (F7) và bổ sung đoạn code bên dưới
 namespace TemplateMethodPattern  
 {  
   public partial class ConcreteForm1 : BaseForm  
   {  
     public ConcreteForm1()  
     {  
       InitializeComponent();  
       base.Mode = TemplateMethodPattern.Mode.Add;  
     }  

     /// <summary>  
     /// Implement BaseForm's InitControl method  
     /// </summary>  
     public override void InitControl()  
     {  
       this.txtEmpVN.TabIndex = 1;  
       this.txtEmpEN.TabIndex = 2;  
       this.txtEmpVN.Focus();  
     }  

     /// <summary>  
     /// Implement BaseForm's SetControl method  
     /// </summary>  
     public override void SetControl()  
     {  
       if (base.Mode == TemplateMethodPattern.Mode.View)  
       {  
         this.txtEmpEN.Enabled = false;  
         this.txtEmpVN.Enabled = false;  
       }  
       else  
       {  
         this.txtEmpEN.Enabled = true;  
         this.txtEmpVN.Enabled = true;  
       }  
     }  

     /// <summary>  
     /// Implement BaseForm's SetData method  
     /// </summary>  
     public override void SetData()  
     {  
       if (base.Mode == TemplateMethodPattern.Mode.Add)  
       {  
         this.txtEmpVN.Text = string.Empty;  
         this.txtEmpEN.Text = string.Empty;  
       }  
       else  
       {  
         this.txtEmpVN.Text = "kien.chu";  
         this.txtEmpEN.Text = "ken";  
       }  
     }  
   }  
 }  

Lúc này trong ConcreteForm1 chúng ta chỉ cần override các method đã khai báo là virtual trên BaseForm về và xử lí theo cách chúng ta muốnkhông cần quan tâm đến các bước thực hiện tuần tự thế nào, nơi nào sử dụng các method này.v.v..Khi chúng ta Run chương trình từ ConcreteForm1, nó vẫn sẽ xác định được event nào cần gọi, các bước sẽ được xử lí ra sao...

Trên đây là giới thiệu về Template Method Pattern và một vài ví dụ đơn giản về nó. Nếu các bạn có gì trao đổi hay thắc mắc thì có thể để lại comment bên dưới bài viết này.
Share to be shared!

Bài trước - Facade Pattern


Design Patterns trong các dự án thực tế - Facade Pattern

Lời nói đầu

Trước khi tìm hiểu về Facade Pattern, hãy đọc lại bài viết Design patterns là gì nếu bạn vẫn chưa biết design patterns là gì. Ngoài ra để giúp bạn dễ dàng nắm bắt nội dung bài viết, hãy chắc là bạn đã biết về Lập trình hướng đối tượng (OOP) và các tính chất, khái niệm của nó như bao đóng (encapsulation), trừu tượng (abstract), kế thừa (inheritance), đa hình (polymorphism), đối tượng (object), lớp (class), giao diện (interface)...
***
Facade Pattern là gì?

Facade Pattern là một trong các pattern quan trọng nhất và hay nhất trong thiết kế phần mềm, thuộc nhóm structural pattern. Nó đóng vai trò che dấu đi tất cả những sự phức tạp, sự lằng nhằng của một chức năng nào đó trong hệ thống và cung cấp một giao diện, một class với một cách thức sử dụng đơn giản và hiệu quả hơn rất nhiều.

Lấy ví dụ: mỗi buổi sáng bạn đi làm và bạn muốn phải tắt hết các thiết bị điện vì dạo này giá điện cao quá. Bạn sẽ làm gì? Bạn phải kiểm tra và tắt quạt, tắt tivi, tắt đèn..., rồi lỡ may quên tắt máy lạnh thì cuối tháng lãnh đủ. 
Những việc cần làm mỗi lần rời nhà được minh họa dưới đây
 public LeaveHome  
 {  
    Televison.TurnOff();  
    AirConditioner.TurnOff();  
    Light.TurnOff();  
 }  

Bạn có muốn giải quyết nhanh và đơn giản hơn không? Hãy cúp cầu dao điện. Tất cả chỉ trong một nốt nhạc và mọi vấn đề được giải quyết, bạn cũng không cần phải quan tâm đến cách nó hoạt động thế nào (phải che dấu vì nguy hiểm mà). Đó là cách mà Facade Pattern hoạt động. 
  public LeaveHome   
  {   
    CircuitBreaker.TurnOff();    
  }   

  /// <summary>  
  /// CircuitBreaker Facade  
  /// </summary>  
  public CircuitBreaker   
  {   
    /// <summary>  
    /// Facade method  
    /// </summary>  
    public static void TurnOff()   
    {   
      Televison.TurnOff();   
      AirConditioner.TurnOff();   
      Light.TurnOff();     
    }   
  }   

Áp dụng Facade Pattern

Trong thiết kế phần mềm cũng vậy, đặc biệt là đối với những bạn giữ vai trò key member trong dự án hoặc giả sử là bạn muốn trở thành như vậy thì việc đưa ra những cách xử lí đơn giản, tiết kiệm chi phí (thời gian, công sức) cho hệ thống nhưng vẫn đảm bảo luồng xử lí vẫn hoạt động tốt là rất quan trọng.

Kiên lấy ví dụ trong thực tế trong dự án khi bạn phát triển một màn hình, trong đó có một combobox hiển thị danh sách nhân viên công ty, bạn sẽ phải làm gì? 

- Viết câu truy vấn dữ liệu.

Kiểm tra xem có cần thêm bớt gì dữ liệu hay không (thêm điều kiện, sắp xếp hay thêm dòng trống.v.v..)

- Format lại dữ liệu theo ngôn ngữ cần hiển thị (nếu có)

- Rồi đổ lên control

..bla..bla, 

Kiên đoán là ai cũng có thể làm việc này và có thể bạn chỉ mất chừng 10 - 15 phút để hoàn thành. Nhưng hãy cùng nhau suy nghĩ rộng ra thêm một chút các vấn đề sau để thấy vì sao Facade Pattern lại quan trọng:

1. Trùng lặp code. Hãy tưởng tượng cũng có 20 chức năng khác cũng muốn hiển thị danh sách nhân viên trong combobox, vậy là bạn có 20 đoạn code lằng nhằng lặp đi lặp lại chỉ để xử lí cùng một vấn đề. Đây là một code smell mà bạn cần tránh.

2. Tốn chi phí. 20 chức năng x 15 phút = 300 phút. Đương nhiên khi bạn copy & paste thì sẽ nhanh hơn. Nhưng chắc chắn sẽ không nhanh môt Facade mất 10' hoàn thành và 5' để tạo ra cách thức truy cập. Chưa kể bạn phải mất thời gian để giải thích về chức năng đó cho developer phải lấy dữ liệu từ table nào, đi qua layer nào, xử lí ra sao...Hay nếu sau này có thay đổi thì cũng làm cho bạn mất khá nhiều thời gian.

3. Thiếu sót chức năng. Bạn muốn combobox của bạn có thêm dòng trống để select all, hay bạn muốn dữ liệu hiển thị đúng theo ngôn ngữ, hoặc là yêu cầu muốn sắp xếp theo column nào đó. Nếu team của bạn hiểu rõ yêu cầu, giao tiếp tốt với nhau thì ok, còn không bạn đã có bug rồi đấy.

4. Khó review. Mỗi người một style, một phong cách code khác nhau. Thiếu comment hay comment không hiểu được. Vi phạm coding standard của dự án.v.v..Bạn chọn vẫn kiên nhẫn review hết hay bỏ qua không review?

5. Gặp vấn đề khi có thay đổi. Ok bạn đã hoàn thành 20 chức năng với 100% yêu cầu và sự hài lòng của khách hàng. Đột nhiên vào một ngày đẹp trời, khách hàng nói rằng :"Tôi không cần hiển thị theo ngôn ngữ nữa, chỉ hiển thị tên tiếng Anh thôi". Bạn có nhớ hết tất cả các màn hình sử dụng combobox này ko? Bạn có nhớ hết class nào viết chức năng này không?

Viết có một chức năng bé tí tẹo mà suy nghĩ xa xôi, phức tạp nhỉ. Vâng đúng rồi đấy, Design pattern được đưa ra dựa theo kinh nghiệm, là kĩ năng giải quyết vấn đề mà ở trường không có dạy cho chúng ta.

Vậy ta giải quyết thế nào trong trường hợp này? Thật ra bản thân Facade Pattern khá là đơn giản, hãy xem lại bài toán cúp cầu dao, bạn chỉ cần gom tất cả những việc cần làm vào một nơi, đồng thời cung cấp một lời gọi, có thể là một method hay một property để xử lí chúng. Dưới đây là các Class Diagram mà Kiên minh họa đơn giản cho trước và sau khi áp dụng Facade Pattern cho chức năng hiển thị danh sách employee lên combobox này:

Giả sử trước khi áp dụng Facade Pattern, chúng ta có 3 Screen sử dụng combobox để hiển thị danh sách nhân viên. Để làm được điều đó, chúng ta viết 3 method SelectData() ở mỗi Screen để gọi đến class EmployeeService thực thi câu truy vấn. Trong EmployeeService có 3 method xử lí cùng một yêu cầu nhưng được viết riêng cho 3 Screen đó. Rồi có Screen lại viết thiếu chức năng như Screen1 thiếu chức năng thêm một dòng trống để select all, Screen2 lại thiếu chức năng hiển thị dữ liệu theo ngôn ngữ. Có khá là nhiều vấn đề nhỉ.


Áp dụng Facade Pattern, chúng ta làm theo nguyên tắc tạo ra một Facade để gom tất cả xử lí vào đó. Facade của chúng ta là một custom control EmployeeCombobox kế thừa trực tiếp từ System.Windows.Forms.Combobox đồng thời cung cấp một facade method FillData()Các method khác của EmployeeCombobox như ChangeLanguage() và SelectData() đã được che dấu đi, các Screen không cần quan tâm đến nữa. Lúc này EmployeeService cũng chỉ còn duy nhất một method dùng chung là SelectData(). Hãy nhìn các màn hình, lúc này nó không còn những xử lí rườm rà nữa, mà chỉ cần gọi đến facade method FillData() của EmployeeCombobox để hiển thị danh sách nhân viên.

Dưới đây là phần Kiên minh họa cho ví dụ này.

Đầu tiên: ta tạo một custom control EmployeeCombobox là một Component class


Sau đó cho kế thừa từ System.Windows.Forms.Combobox và bổ sung code như bên dưới
 namespace FacadePattern  
 {  
   /// <summary>  
   /// Custom control  
   /// </summary>  
   public partial class EmployeeCombobox : System.Windows.Forms.ComboBox  
   {  
     /// <summary>  
     /// Allow add null row or not  
     /// </summary>  
     public bool HasNull { get; set; }  

     /// <summary>  
     /// Facade method  
     /// </summary>  
     public void FillData()  
     {  
       //Select data  
       this.SelectData();  

       //Add null row  
       this.AddNewRow();  

       //Change language  
       this.ChangeLanguage();  
     }  

     /// <summary>  
     /// Select data from database  
     /// and set to DataSource  
     /// </summary>  
     private void SelectData()  
     {  
       EmployeeService employeeService = new EmployeeService();  
       base.DataSource = employeeService.SelectData();  
     }  

     /// <summary>  
     /// Display value based on language  
     /// </summary>  
     private void ChangeLanguage()  
     {  
       base.ValueMember = "ID";  
       switch (UserSession.Language)  
       {  
         case "en":  
           base.DisplayMember = "NameEN";  
           break;  
         case "vi":  
           base.DisplayMember = "NameVN";  
           break;  
         default:  
           base.DisplayMember = "NameEN";  
           break;  
       }  
     }  

     /// <summary>  
     /// Add null row  
     /// </summary>  
     private void AddNewRow()  
     {  
       if (this.HasNull)  
       {  
         DataRow row = ((DataTable)this.DataSource).NewRow();  
         ((DataTable)this.DataSource).Rows.InsertAt(row, 0);  
       }  
     }  
   }  
 }  

Ta có thể tạo các thuộc tính để bên ngoài truy cập và cấu hình các thông số cần thiết như là cho phép có dòng trống, sort theo column nào, sort giảm hay tăng, hay là các điều kiện tìm kiếm...
     /// <summary>  
     /// Allow to add null row or not  
     /// </summary>  
     public bool HasNull { get; set; }  


Đây là Facade method FillData() mà chúng ta tạo ra để cung cấp giao diện thay thế cho các xử lí bên trong.
     /// <summary>  
     /// Facade method  
     /// </summary>  
     public void FillData()  
     {  
       //Select data  
       this.SelectData();  

       //Add null row  
       this.AddNewRow();  

       //Change language  
       this.ChangeLanguage();
     }

Các xử lí thông thường mà chúng ta cần phải làm đã được che dấu đi (private method) 
     /// <summary>  
     /// Select data from database  
     /// and set to DataSource  
     /// </summary>  
     private void SelectData()  
     {  
       EmployeeService employeeService = new EmployeeService();  
       base.DataSource = employeeService.SelectData();  
     }  

     /// <summary>  
     /// Display value based on language  
     /// </summary>  
     private void ChangeLanguage()  
     {  
       base.ValueMember = "ID";  
       switch (UserSession.Language)  
       {  
         case "en":  
           base.DisplayMember = "NameEN";  
           break;  
         case "vi":  
           base.DisplayMember = "NameVN";  
           break;  
         default:  
           base.DisplayMember = "NameEN";  
           break;  
       }  
     }  

     /// <summary>  
     /// Add null row  
     /// </summary>  
     private void AddNewRow()  
     {  
       if (this.HasNull)  
       {  
         DataRow row = ((DataTable)base.DataSource).NewRow();  
         ((DataTable)base.DataSource).Rows.InsertAt(row, 0);  
       }  
     }  
   }  

Tiếp theo Kiên tạo EmployeeService đơn giản cho ví dụ này, nó cung cấp method SelectData() và trả về danh sách Employee sẽ được gọi từ custom control EmployeeCombobox. Bạn có thể tạo DAO hay Service, viết SqlQuery, Store Procedure hay LINQ đều được.
   public class EmployeeService  
   {  
     public System.Data.DataTable SelectData()  
     {  
       DataTable dtResult = new DataTable();  
       using (FacadePatternEntities context = new FacadePatternEntities())  
       {  
         dtResult = context.Employees  
           .Select(emp => new  
           {  
             emp.ID,  
             emp.NameEN,  
             emp.NameVN,  
           })  
           .ToDataTable();  
       }  
       return dtResult;  
     }  
   }  

Cuối cùng ta tạo Screen1 là một WindowForm để kiểm tra chức năng. Chúng ta cũng thấy custom control EmployeeCombobox sẽ được tạo ra trong Toolbox sau khi chúng ta build chương trình. Hãy kéo nó vào và thiết lập các thông số cần thiết như hình bên dưới.

View Code (F7) và bổ sung các đoạn code sau đây

 namespace FacadePattern  
 {  
   public partial class Screen1 : Form  
   {  
     public Screen1()  
     {  
       InitializeComponent();  
       UserSession.Language = "vi";  
       this.Load += Screen1_Load;  
     }  

     void Screen1_Load(object sender, EventArgs e)  
     {  
       //Init properties  
       this.cboEmployee.HasNull = true;

       //Invoke facade method  
       this.cboEmployee.FillData();  
     }  
   }  
 }  


Chúng ta cấu hình các thông số cần thiết đã tạo ra Facade
//Init properties  
this.cboEmployee.HasNull = true;

Sau này khi cần đổ dữ liệu cho combobox cho bất kì màn hình nào, chúng ta chỉ việc thông qua Facade method FillData() để xử lí mà không cần quan tâm cách nó xử lí như thế nào nữa.
//Invoke facade method  
this.cboEmployee.FillData();

Trên đây là giới thiệu và ví dụ về Facade Pattern - một trong những Pattern được sử dụng nhiều nhất trong thiết kế phần mềm. Nếu các bạn muốn trao đổi hay có thắc mắc với Kiên, có thể để lại comment bên dưới bài viết này.

Share to be shared! 

Bái trước - Adapter Pattern


Wednesday, July 6, 2016

Sử dụng ADO.NET Entity Framework tương tác với Database - Mô hình Database First

ADO.NET Entity Framework là một mô hình hay nền tảng ORM (Object Relational Mapper) ánh xạ trực tiếp với database để tạo ra những mô hình dữ liệu quan hệ...và cung cấp những cơ chế giúp ta có thể tương tác, khai thác dữ liệu hiệu quả, dễ dàng hơn.

Có ba cách để chúng ta sử dụng Entity Framework tương tác với database: Database First, Code First và Model First. Trong bài viết này Kiên sẽ giới thiệu đến các bạn cách sử dụng mô hình Database First trong Entity Framework để tạo mô hình dữ liệu quan hệ với database.

Vậy Entity Framework - Database First là gì?

Nếu bạn đã có một database, Entity Framework có thể tự động tạo ra một mô hình dữ liệu bao gồm các lớp và các thuộc tính tương ứng với cơ sở dữ liệu hiện có đối tượng như table và column thông qua cơ chế kéo thả khá dễ dàng. Các thông tin về cấu trúc database được lưu trữ trong một tập tin .edmx. Ngoài ra Entity Framework cung cấp một giao diện đồ họa khá trực quan mà bạn có thể sử dụng để hiển thị và chỉnh sửa file .edmx.

Chúng ta hãy thử bắt đầu với ví dụ đơn giản nhất.

BƯỚC ĐẦU TIÊN: Sử dụng Entity Framework để tạo mapping từ database


- Ta tạo một database FatoryPattern có một table [employee] đơn giản như hình 1
(hình 1)

- Mở Visual Studio 2013 (là phiên bản mà Kiên đang sử dụng, các bạn có thể sử dụng bất kì phiên bản nào tùy ý). Tạo mới một project Window Forms Application có tên FactoryPattern_Example như hình 2.
(hình 2)

- Click phải vào project -> Add -> New Item như hình 3.
(hình 3)

- Trong cửa sổ Add New Item, ta chọn ADO.NET Entity Data Model. Nhập tên trong ô Name FactoryModel, sau đó nhấn nút Add như hình 4.
(hình 4)

- Trong cửa sổ Entity Data Model Wizard - Choose Model Contents, chọn EF Designer from database, nhấn nút Next như hình 5.
(hình 5)

- Trong trường hợp chưa có connection được tạo sẵn, ta click vào nút New Connection. Trong cửa sổ Connection Properties hiện ra sau đó, ta nhập các thông tin đăng nhập vào MSSQL, chọn database, sau đó nhấn OK để hoàn thành thao tác như hình 6.
(hình 6)

- Quay lại cửa sổ Entity Data Model Wizard - Choose Your Data Connection, chúng ta sẽ thấy đã có một connection được tạo ra. Trong ô Save connection settings in App.Config as, chúng ta nhập FactoryPatternEntities, sau đó nhấn Next như hình 7.
(hình 7)

- Trong cửa sổ Entity Data Model Wizard - Choose Your Database Objects and Settings, chúng ta check chọn tất cả Table. Trong ô Model Namespace ta nhập FactoryPatternModel, sau đó nhấn Finish như hình 8 để hoàn tất việc tạo mapping đến database bằng Entity Framework Database First.
(hình 8)

- Hình 9 cho ta thấy diagram được sinh ra. Build Solution (F6) để kiểm tra có lỗi hay không.
(hình 9)

- Hình 10 là nội dụng của file App.config cho ta thấy một số thông tin cấu hình để kết nối đến database. Hãy chú ý đến key <add name="FactoryPatternEntities"> nằm trong <connectionStrings>...</connectionString>. Nó rất quan trọng trong việc tạo connection của chúng ta mà một lát nữa chúng ta sẽ quay lại với nó.
(hình 10)
 <?xml version="1.0" encoding="utf-8"?>  
 <configuration>  
  <configSections>  
   <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->  
   <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />  
  </configSections>  
  <startup>  
   <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />  
  </startup>  
  <connectionStrings>  
   <add name="FactoryPatternEntities"   
      connectionString="metadata=res://*/FactoryModel.csdl|res://*/FactoryModel.ssdl|res://*/FactoryModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;  
              data source=VNWKTTV093\SQLEXPRESS;initial catalog=FactoryPattern;persist security info=True;user id=admin;password=admin;MultipleActiveResultSets=True;App=EntityFramework&quot;"   
      providerName="System.Data.EntityClient" />  
  </connectionStrings>  
  <entityFramework>  
   <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />  
   <providers>  
    <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />  
   </providers>  
  </entityFramework>  
 </configuration>  


- Trên Form1.cs, chúng ta thiết kế một giao diện đơn giản để tìm kiếm employee như hình 11 bao gồm 1 textbox (txtName) làm điều kiện tìm kiếm, 1 button (btnSearch) để xử lí yêu cầu và 1 DataGridView (dgvEmployee) để hiển thị dữ liệu.
(hình 11)

- Trong Code-behind của Form1.cs, ta tạo sự kiện click của button Search. Bên trong sự kiện btnSearch_Click, đơn giản là ta sử dụng DbContext FactoryPatternEntities mà ta đã tạo ra thông qua Entity Framework để thực hiện một truy vấn tìm kiếm dựa theo Name. Sau đó ta gán dữ liệu tìm thấy vào lưới mà ta đã design ở hình 11.

Kiên đoán là bước này khá dễ dàng với các bạn. Hãy tham khảo đoạn code trong hình 12.
(hình 12)

- Chúng ta vào database FactoryPattern, ta tạo vài dòng dữ liệu trong table employee như hình 13.
(hình 13)

- Vào lại source code trong Visual Studio, Start Debugging (F5) để chạy chương trình. Sau đó nhấn Search để kiểm tra kết quả trả về như hình 14. Hãy chắc là DataFieldName của các column trong DataGridView tương thích với dữ liệu tìm được.
(hình 14)

- Như các bạn thấy, rất dễ dàng để chúng ta tạo một kết nối đến MSSQL và xử lí truy vấn dữ liệu thông qua cơ chế của Entity Framework.

- Hãy nhìn lại cách thức mà DbContext FactoryPatternEntities tạo ra kết nối đến database như hình 15. Khi DbContext này được tạo ra (hàm khởi tạo FactoryPatternEntities không tham số), nó sẽ đọc thông tin từ file App.config và tìm đến key có name là FactoryPatternEntities như Kiên đã nói đến trong hình 10, lúc này nó sẽ đọc thông tin từ connectionString và khởi tạo kết nối.
(hình 15)

- Chúng ta mở file App.config và chú ý đến key FactoryPatternEntities connectionString. Thông tin trong connectionString bao gồm Metadata, Provider, Data source, Initial catalog, Persist security info, User id, Password, MultipleActiveResultSets và App. được ngăn cách nhau bởi dấu ";"
  <connectionStrings>  
   <add name="FactoryPatternEntities"   
      connectionString="metadata=res://*/FactoryModel.csdl|res://*/FactoryModel.ssdl|res://*/FactoryModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;  
              data source=VNWKTTV093\SQLEXPRESS;initial catalog=FactoryPattern;persist security info=True;user id=admin;password=admin;MultipleActiveResultSets=True;App=EntityFramework&quot;"   
      providerName="System.Data.EntityClient" />  
  </connectionStrings>  


BƯỚC TIẾP THEO: Tạo ConnectionString.

- Đôi khi chúng ta sẽ không muốn để tất cả những thông tin kết nối đến database như tên database, user name, password... trong file App.config. Thay vào đó chúng ta sẽ tạo ra một ConnectionString thay thế cho việc lấy thông tin kết nối trực tiếp từ file App.config.

- Để tạo được connection string, đơn giản là ta tạo một class mới, đặt tên là Connection.cs, bên trong ta viết một method có tên GetConnectionString như hình 16 và đoạn Code bên dưới. Các giá trị bên trong method này được lấy từ file App.config.
(hình 16)
Code:
 public override string GetConnectionString()  
     {  
       SqlConnectionStringBuilder sqlBuilder = new SqlConnectionStringBuilder();  
       sqlBuilder.DataSource = @"VNWKTTV093\SQLEXPRESS";  
       sqlBuilder.InitialCatalog = "FactoryPattern";  
       sqlBuilder.MultipleActiveResultSets = true;  
       sqlBuilder.IntegratedSecurity = true;  
       sqlBuilder.ApplicationName = "EntityFramework";  
       EntityConnectionStringBuilder efBuilder = new EntityConnectionStringBuilder();  
       efBuilder.Metadata = "res://*/FactoryModel.csdl|res://*/FactoryModel.ssdl|res://*/FactoryModel.msl";  
       efBuilder.Provider = "System.Data.SqlClient";  
       efBuilder.ProviderConnectionString = sqlBuilder.ConnectionString;  
       return efBuilder.ConnectionString;  
     }  


- Vì DbContext FactoryPatternEntities được tạo ra không có hàm khởi tạo có tham số là một ConnectionString nên chúng ta sẽ sử dụng tính năng Partial Class của .NET để bổ sung hàm khởi tạo có tham số vào DbContext FactoryPatternEntities của chúng ta.
Bạn có thể tham khảo Partial Class ở đây: Partial Class trong C#

- Chúng ta thêm mới một class và đặt trùng tên với DbContext FactoryPatternEntities, bao gồm cả từ khóa partial. Bây giờ chúng ta có thể bổ sung hàm khởi tạo có tham số đầu vào là một ConnectionString cho FactoryPatternEntities như hình 16.
(hình 16)

- Chúng ta cũng cập nhật lại sự kiện btnSearch_Click trong form Form1.cs. Ở đây ta khởi tạo class Connection, sau đó gọi method GetConnectionString được viết ở trên để lấy được ConnectionString, sau đó khởi tạo DbContext FactoryPatternEntities thông qua ConnectionString này. Hãy xem lại đoạn code được cập nhật trong hình 17.
(hình 17)

- Start Debugging (F5) và kiểm tra kết quả để đảm bảo chúng ta vẫn tạo được kết nối đến database.  như hình 18.
(hình 18)
Trên đây là ví dụ khá đơn giản để chúng ta làm quen với Entity Framework - Database First. Trong các bài tiếp theo Kiên sẽ giới thiệu đến các bạn chi tiết hơn cũng như là hai mô hình còn lại.

Nếu có thắc mắc hay trao đổi gì, các bạn có thể để lại comment bên dưới bài viết này.

Share to be shared!