Cài đặt Entity Framework, kỹ thuật lập trình cơ bản

    0

    Code-first là hướng tiếp cận xuất hiện cuối cùng trong Entity framework, cũng là hướng được lập trình viên đặc biệt hoan nghênh. Với hướng tiếp cận này, bạn không cần rời khỏi môi trường viết code C# quen thuộc của Visual Studio. Tất cả những gì bạn cần làm là cài đặt bộ thư viện Entity Framework rồi xây dựng các lớp model (hay business/domain class) theo đúng “kiểu C#” quen thuộc. Entity Framework code-first sẽ giúp bạn thực hiện tất cả các công đoạn khác, từ tạo cơ sở dữ liệu, tạo bảng, truy vấn dữ liệu, v.v.. Nếu có sự thay đổi về cấu trúc class, Entity Framework code-first cũng có thể giúp bạn chuyển đổi cấu trúc cơ sở dữ liệu tương ứng một cách nhanh chóng và tiện lợi, đặc biệt là không làm mất dữ liệu cũ.

    Bài học này sẽ hướng dẫn bạn những kỹ thuật quan trọng nhất để cài đặt và sử dụng Entity Framework trong một project mới theo hướng tiếp cận Code-first. Các kỹ thuật sẽ học bao gồm: (1) tạo class và ánh xạ sang bảng trong cơ sở dữ liệu, (2) thêm bản ghi mới vào bảng, (3) truy vấn dữ liệu với LINQ, (4) cập nhật và xóa dữ liệu, (5) xử lý sự thay đổi của cấu trúc dữ liệu.

    Quay trở lại Tự học lập trình ADO.NET và Entity Framework

    Cài đặt Entity Framework cho project

    Entity Framework được Microsoft cung cấp miễn phí bao gồm cả mã nguồn. Bạn có thể tải mã nguồn Entity framework từ GitHub.

    Tuy nhiên, cách đơn giản nhất để cài đặt Entity Framework cho project là sử dụng NuGet.

    Nếu bạn chưa biết: NuGet là website cho phép mọi người tạo và chia sẻ các thư viện code. Microsoft chịu trách nhiệm cung cấp thư viện Entity Framework trên NuGet.

    Chúng ta trước hết sẽ tạo một project Console App, sau đó cài đặt thư viện EF và thực hiện một số thao tác truy vấn – xử lý dữ liệu. Nó giúp bạn trải nghiệm trọn vẹn quy trình sử dụng cơ bản của Entity Framework.

    Tự thực hiện: Tạo một solution trống có tên S03_CodeFirst. Trong solution này tạo một project loại Console App đặt tên là P01_HelloCodeFirst.

    Cài đặt Entity Framework sử dụng NuGet Package Manager

    NuGet Package Manager là giao diện đồ họa tích hợp sẵn trong Visual Studio giúp bạn quản lý (cài đặt/xóa bỏ) các thư viện class tải về từ NuGet.

    Sau đây là các bước cài đặt Entity Framework từ NuGet Package Manager:

    Bước 1. Click phải chuột vào tên project và chọn “Manage NuGet Packages” để mở ra cửa sổ (tab) NuGet Package Manager.

    Bước 2. Chọn tab Browse, gõ tìm kiếm entity framework. Lựa chọn gói EntityFramework ở danh sách và ấn Install để bắt đầu cài đặt.

    NuGet package manager - entity framework

    Nếu cài đặt thành công, trong project sẽ xuất hiện một số thư viện và file mới:

    Project đã cài đặt Entity Framework
    Project đã cài đặt Entity Framework xuất hiện một số thư mục và file cấu hình mới

    Cài đặt Entity Framework sử dụng Package Manager Console

    Package Manager Console có cùng vai trò như NuGet Package Manager nhưng sử dụng giao diện dòng lệnh.

    Bước 1. Mở cửa sổ này từ View => Other Windows => Package Manager Console

    Bước 2. Từ dấu nhắc lệnh gõ install-package entityframework (không phân biệt hoa-thường) và ấn Enter.

    Quá trình cài đặt sẽ tự thực hiện.

    Lưu ý, dù sử dụng giao diện nào, máy tính phải được kết nối với Internet để tải các file thư viện từ NuGet.

    Tạo cơ sở dữ liệu từ class C# sử dụng Code-first

    Để sử dụng code-first, bạn cần tạo hai loại class.

    Thứ nhất là các class mô tả các thông tin cần quản lý. Mỗi class thuộc loại này sẽ ánh xạ sang một bảng của cơ sở dữ liệu. Nhóm class này có thể được gọi theo nhiều cách khác nhau như model/domain/business/entity class. Đặc điểm chung của nhóm class này là chỉ chứa dữ liệu trong các property.

    Thứ hai là một class con của lớp DbContext, ánh xạ sang chính cơ sở dữ liệu. Loại class này cũng thường được gọi tắt là context hoặc entity container.

    Xây dựng entity class

    Để giữ code đơn giản, chúng ta tạm viết tất cả code vào file Program.cs, trực tiếp trong namespace P01_HelloCodeFirst (mặc định của project).

    Tự thực hiện: Tạo thêm class Person như sau:

    public class Person
    {
        public int PersonId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

    Lưu ý rằng, trong lớp Person bạn cần sử dụng property để xuất nhập dữ liệu. Ngoài ra, Person chỉ sử dụng các kiểu dữ liệu cơ sở của C#. Loại class này được gọi là entity class, business object (BO). Trong các công nghệ phát triển ứng dụng, loại class này thường được gọi là domain class, model class hoặc đơn giản là model. Bản thân loại class này phải là các POCO – Plain Old C# Object.

    POCO là loại class chỉ sử dụng các kiểu dữ liệu cơ sở tiêu chuẩn của C#/.NET. Bạn không được sử dụng các kiểu dữ liệu “ngoại lai” từ bất kì thư viện, framework nào nằm ngoài phạm vi chứa các POCO (project). Một POCO có thể sử dụng các POCO khác miễn là nó cùng phạm vi. POCO không biết và không có bất kỳ phương thức nào để truy xuất đến nguồn dữ liệu. POCO chỉ có nhiệm vụ chứa và xử lý dữ liệu. Chỉ với các đặc điểm này, POCO mới có thể ánh xạ thành bảng của cơ sở dữ liệu. Các dạng class “thông thường” khác không ánh xạ sang bảng dữ liệu được.

    Như đã giới thiệu trong bài tổng quan về Entity Framework, có sự vênh nhau giữa kiểu của .NET và SQL Server. Tuy nhiên, Entity Framework giúp giải quyết tình huống này bằng cách tự lựa chọn kiểu SQL Server phù hợp nhất với kiểu của .NET. Do đó, bạn có thể thoải mái sử dụng các kiểu dữ liệu cơ bản của C# .NET khi khai báo các lớp model.

    Entity Framework code-first sử dụng “configuration over convention”. Hiểu đại khái là Entity Framework sẽ tự cấu hình dựa theo các quy ước. Ví dụ, nếu gặp property nào có kiểu là int và chứa cụm ký tự Id, EF sẽ ánh xạ property này thành trường khóa chính của bảng. Trong quá trình học bạn sẽ còn gặp nhiều quy ước như vậy nữa.

    Convention: mỗi entity class phải chứa một property thuộc kiểu int có tên là Id hoặc theo mẫu <tên_class>Id. Entity Framework sẽ tự động lấy property này làm trường khóa chính cho bảng dữ liệu tương ứng.

    Bạn sẽ học kỹ hơn về các quy ước trong code-first ở một bài học riêng.

    Xây dựng lớp context

    Tự thực hiện: Xây dựng class Context như sau:

    public class Context : DbContext
    {
        public Context():base("name=S03_connectionString")
        {
    
        }
    
        public DbSet<Person> People { get; set; }
    }

    Lớp Context được xây dựng như trên sẽ ánh xạ sang một cơ sở dữ liệu. Các thông tin để truy xuất CSDL này được lưu trong connectionstring S03_connectionString (trong file App.Config).

    Lưu ý rằng, lớp DbContext và DbSet<T> cùng nằm trong namespace System.Data.Entity, nghĩa là phải có dòng lệnh using System.Data.Entity; ở đầu file code.

    Cũng để ý rằng, constructor của Context gọi tới constructor của lớp cha DbContext với tham số là chuỗi “name=S03_connectionString”: public Context():base("name=S03_connectionString"){}. Giá trị S03_connectionString có thể lựa chọn tùy ý. Tuy nhiên, cần phải sử dụng đúng chuỗi này để đặt tên cho connection string trong file App.config.

    Tạo connection string trong App.config

    Như bạn đã biết, Entity Framework phải gọi tới ADO.NET data provider để làm việc với cơ sở dữ liệu. Thành phần này đòi hỏi phải cung cấp chuỗi tham số kết nối (connection string) mà bạn đã học.

    Tự thực hiện: Bổ sung node connectionString vào App.config như sau:

    <connectionStrings>
        <add name="S03_connectionString" connectionString="Data source=.; Initial Catalog=S03_CodeFirst; Integrated security=SSPI" providerName="System.Data.SqlClient" />
    </connectionStrings>

    Node này định nghĩa một chuỗi kết nối đặt tên là S03_connectionString, trùng với giá trị cung cấp khi gọi constructor của lớp Context. Bạn có thể dễ dàng thấy, chuỗi này cung cấp tham số để kết nối tới instance mặc định của Sql server cài trên máy cục bộ, cơ sở dữ liệu tên là S03_CodeFirst, sử dụng xác thực windows.

    Khối <connectionStrings> phải đặt sau khối <configSections>. Nếu không sẽ bị lỗi ở giai đoạn chạy chương trình.

    Lưu ý rằng, cơ sở dữ liệu S03_CodeFirst hiện CHƯA tồn tại! Code-first sẽ giúp chúng ta tạo cơ sở dữ liệu có tên tương ứng khi chạy chương trình lần đầu tiên.

    Sử dụng Code-first tạo cơ sở dữ liệu

    Để code-first giúp tạo cơ sở dữ liệu từ các class vừa xây dựng, trong phương thức Main viết code như sau:

    static void Main(string[] args)
    {
        using (var context = new Context())
        {
            context.Database.CreateIfNotExists();
        }
    }

    Phương thức CreateIfNotExists sẽ kiểm tra xem cơ sở dữ liệu chỉ định trong connection string đã tồn tại hay chưa. Nếu chưa, code-first sẽ tạo cơ sở dữ liệu cùng tên với giá trị tham số Initial Catalog.

    Dịch và chạy thử chương trình.

    Nếu bạn dùng Sql Server Management Studio hoặc Visual Studio kết nối tới server, bạn sẽ thấy có một cơ sở dữ liệu mới tên là S03_CodeFirst đã được tạo ra. Trong cơ sở dữ liệu này hiện có hai bảng, People và _MigrationHistory. Bảng People thì chắc bạn đã đoán ra – nó là ánh xạ từ class Person. Bảng _MigrationHistory chúng ta sẽ xem xét ở phần cuối bài.

    Cơ sở dữ liệu do code-first tạo ra từ các class C#
    Cơ sở dữ liệu do code-first tạo ra từ các class C#

    Nếu mở bảng People, bạn sẽ thấy ngay rằng các property của class này đã được ánh xạ sang thành các cột của bảng. Code-first đã tự động lựa chọn kiểu dữ liệu phù hợp của SQL giúp chúng ta. Thuộc tính PersonId tự biến thành khóa chính của bảng.

    Cấu trúc bảng do code-first tạo ra
    Cấu trúc bảng do code-first tạo ra

    Đến đây xin chúc mừng bạn đã lần đầu tiên thành công tiếp xúc với code-first.

    Nếu nhớ lại sự vất vả khi tạo cơ sở dữ liệu với SQL, bạn sẽ thấy ý nghĩa của Entity Framework Code-first. Không cần SQL, không cần giao diện thiết kế, không cần cấu hình phức tạp. Tất cả chỉ là code C# thông thường. Hoàn toàn đi theo trật tự viết code bạn quen thuộc.

    Để bạn dễ theo dõi, dưới đây là full code của file Program.cs và App.config mà chúng ta đã thực hiện ở các bước trên.

    using System.Data.Entity;
    
    namespace P01_HelloCodeFirst
    {
        internal class Program
        {
            private static void Main(string[] args)
            {
                using (var context = new Context())
                {
                    context.Database.CreateIfNotExists();
                }
            }
        }
    
        public class Person
        {
            public int PersonId { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
        }
    
        public class Context : DbContext
        {
            public Context() : base("name=S03_connectionString")
            {
    
            }
    
            public DbSet<Person> People { get; set; }
        }
    }
    <?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>
      <connectionStrings>
        <add name="S03_connectionString" connectionString="Data source=.; Initial Catalog=S03_CodeFirst; Integrated security=SSPI" providerName="System.Data.SqlClient" />
      </connectionStrings>
      <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
      </startup>
      <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>

    Tạo bản ghi mới bằng code-first

    Bạn đã tạo ra cơ sở dữ liệu mới nhưng nó đang trống trơn. Giờ chúng ta sẽ cùng xem cách tạo bản ghi mới và lưu vào cơ sở dữ liệu.

    Như bạn đã thấy, mỗi bản ghi của bảng People có ba trường: PersonId, FirstName và LastName. Ba trường này tương đương với 3 thuộc tính của lớp Person. Nói cách khác, mỗi bản ghi của bảng People giờ tương đương với một object của lớp Person (nhưng phải nằm trong Context.People). Chiều ngược lại cũng đúng: mỗi object của lớp Person (nằm trong Context.People) sẽ tương đương với một bản ghi của bảng People. Tất cả các bản ghi của bảng People sẽ tương đương với thuộc tính People (kiểu DbSet<Person>) của object Context. Bản thân object của Context sẽ tương đương với cả cơ sở dữ liệu.

    Nếu hình dung ra vấn đề này, việc tạo bản ghi mới hoàn toàn đơn giản: tạo thêm một object Person và gán nó vào People. Hãy cùng thực hiện.

    Tạo bản ghi và lưu vào cơ sở dữ liệu

    Điều chỉnh code của phương thức Main như sau:

    private static void Main(string[] args)
    {
        using (var context = new Context())
        {
            context.Database.CreateIfNotExists();
    
            var person = new Person
            {
                FirstName = "Donald",
                LastName = "Trump"
            };
            context.People.Add(person);
            context.SaveChanges();
        }
    }

    Dịch và chạy chương trình. Sau đó mở bảng People để xem kết quả. Đây là bảng People mở trong Visual Studio.

    thêm bản ghi bằng code-first
    Một bản ghi đã được tạo ra trong bảng People

    Như bạn đã thấy, việc thêm một bản ghi mới vào bảng People giờ vô cùng đơn giản: (1) tạo một object của lớp Person; (2) thêm object này vào danh sách People của Context; (3) gọi phương thức SaveChanges() của Context. Không có một dòng SQL nào!

    Lưu ý là biến context và toàn bộ code làm việc với dữ liệu được đặt trong một khối using. Mục tiêu của nó cũng tương tự như khi chúng ta sử dụng khối using với SqlConnection. Sau khi kết thúc khối code, biến context sẽ tự hủy để giải phóng tài nguyên.

    Cách xử lý dữ liệu của code-first

    Bạn cũng đã thấy, mặc dù chúng ta không gán giá trị cho property PersonId, nhưng khi lưu vào cơ sở dữ liệu, trường khóa chính PersonId tự động lấy giá trị. Giá trị này sẽ trả ngược lại cho property PersonId của object.

    Như bạn có thể đã hình dung được, tất cả các object Person được lưu trong property People kiểu DbSet<Person> của Context. Mỗi object này được gán cho một trong các trạng thái: Deleted, Added, Modified, Unchanged. Khi bạn thực hiện một thao tác với object của danh sách People, object sẽ chuyển sang một trong các trạng thái tương ứng.

    Ngoại trừ Unchanged, bất kỳ object nào chuyển sang một trong các trạng thái còn lại có thể sẽ cần phải lưu lại vào cơ sở dữ liệu. Sự thay đổi này được thực hiện bằng cách thực thi một truy vấn SQL tương ứng.

    Phương thức SaveChanges() kế thừa từ DbContext chịu trách nhiệm lưu tất cả các thay đổi về dữ liệu trở lại cơ sở dữ liệu. Phương thức này sẽ kiểm tra trạng thái của tất cả các object và tạo ra truy vấn tương ứng với trạng thái để lưu sự thay đổi này vào cơ sở dữ liệu giúp bạn.

    Phương thức SaveChanges() hoạt động theo kiểu “giao dịch” (transactional). Nghĩa là bạn có thể thực hiện (commit) nhiều truy vấn có liên quan trong cùng một lượt. Một giao dịch thành công khi tất cả các truy vấn cùng thành công. Nếu một trong các truy vấn không thành công, giao dịch sẽ hỏng và trả về giá trị trước đó (rollback). Giao dịch đảm bảo tính toàn vẹn và ổn định của dữ liệu.

    Khi hiểu được nguyên lý trên, bạn cần rút ra vấn đề sau: Hãy thực hiện tất cả những sự thay đổi dữ liệu cần thiết, nhưng chỉ gọi đến SaveChanges() một lần duy nhất là đủ. Không nên gọi SaveChanges() liên tục.

    Thực hiện các thao tác khác với dữ liệu bằng code-first

    Truy vấn dữ liệu

    Viết thêm phương thức Retrieve và sửa lại code của phương thức Main như sau:

    private static void Main(string[] args)
    {
        using (var context = new Context())
        {
            context.Database.CreateIfNotExists();
    
            Retrieve(context);
        }
    
        Console.ReadKey();
    }
    
    private static void Retrieve(Context context)
    {
        var people = context.People;
        foreach (var person in people)
        {
            Console.WriteLine($"[{person.PersonId}] {person.FirstName} {person.LastName}");
        }
    }

    Phương thức Retrieve() thực hiện truy vấn dữ liệu và in ra console. Trong đó,

    var people = context.People;

    là truy vấn đơn giản nhất trên dữ liệu của bảng People. Nhìn như một lệnh truy xuất dữ liệu từ danh sách bình thường. Tuy nhiên trong Entity Framework lệnh này hoạt động rất khác.

    Bài giảng này giả định rằng bạn đã biết cách sử dụng thư viện LINQ (to objects). Do đó trong bài giảng sẽ sử dụng mà không giải thích chi tiết về các truy vấn này.

    Thứ nhất, truy vấn này sẽ được dịch thành truy vấn SELECT * của SQL. Sau đó thông qua cơ chế thực thi lệnh của ADO.NET như chúng ta đã biết để lấy dữ liệu về chương trình.

    Thứ hai, Entity Framework sử dụng một cơ chế gọi là thực thi trễ (delayed execution) cho các truy vấn tới cơ sở dữ liệu bằng LINQ. Thực thi trễ hiểu đại khái là việc truy xuất dữ liệu từ cơ sở dữ liệu sẽ không thực hiện ngay ở lúc phát lệnh. Thay vào đó, đến lúc thực sự cần đến dữ liệu thì lệnh truy xuất mới thực sự được thi hành.

    Vậy thế nào là “khi cần dữ liệu”? Trong đoạn code trên, lúc phát lệnh in ra console, đó là lúc cần đến dữ liệu. Và chỉ lúc đó, truy vấn mới được thực thi. Một số phương thức cũng thúc đẩy việc thực thi truy vấn như ToArray(), ToList ().

    Cơ chế thực thi trễ hỗ trợ tăng hiệu suất cho ứng dụng: hoãn thực thi các truy vấn (vốn cần nhiều thời gian xử lý) cho đến khi thực sự cần đến dữ liệu.

    Cập nhật bản ghi

    Viết thêm phương thức Update và chỉnh lại code của phương thức Main() như sau:

    private static void Main(string[] args)
    {
        using (var context = new Context())
        {
            context.Database.CreateIfNotExists();               
    
            Console.WriteLine("Before:");
            Retrieve(context);
            Update(context);
            Console.WriteLine("After:");
            Retrieve(context);
    
        }
    
        Console.ReadKey();
    }
    
    private static void Update(Context context)
    {
        var person = context.People.FirstOrDefault();
        if(person != null)
        {
            person.FirstName = "Barrack";
            person.LastName = "Obama";
            context.SaveChanges();
        }
    }

    Ở phương thức Update() chúng ta thực hiện một truy vấn khác của LINQ, FirstOrDefault(). Truy vấn này lấy ra bản ghi đầu tiên của cơ sở dữ liệu. Nếu không có bản ghi nào, phương thức trả về Null.

    Trên object kết quả, bạn có thể thay đổi giá trị như bình thường. Để lưu lại thay đổi này, bạn lại gọi tới phương thức SaveChanges(). Cách thức hoạt động của update không khác gì việc add dữ liệu lúc trước.

    Xóa bản ghi

    Viết thêm phương thức Delete và chỉnh lại code của phương thức Main() như sau:

    private static void Main(string[] args)
    {
        using (var context = new Context())
        {
            context.Database.CreateIfNotExists();
    
            Delete(context);
        }
    
        Console.ReadKey();
    }
    
    private static void Delete(Context context)
    {
        var person = context.People.FirstOrDefault();
        if(person != null)
        {
            context.People.Remove(person);
            context.SaveChanges();
        }
    }

    Cách xóa dữ liệu với code-first cũng rất đơn giản: (1) xác định object cần xóa; (2) gọi phương thức Remove; (3) lưu thay đổi.

    Có thể nói đơn giản thế này: Thêm, sửa, xóa dữ liệu với Entity Framework code-first giống hệt như các phương thức tương ứng của danh sách (List<T>) với LINQ. Khác biệt duy nhất là cần gọi SaveChanges() để Entity Framework cập nhật những thay đổi vào cơ sở dữ liệu.

    Qua các ví dụ về thêm mới, xóa, cập nhật dữ liệu có thể thấy, Entity Framework theo dõi trạng thái của các object và chỉ sinh ra các truy vấn phù hợp để thực hiện các thay đổi đó. Cơ chế theo dõi và transaction giúp Entity Framework hoạt động hiệu quả cũng như đơn giản hóa việc xử lý dữ liệu của người lập trình.

    Lưu ý (nhắc lại lần nữa): hạn chế tối đa sử dụng SaveChanges(). Chỉ gọi phương thức này khi thực sự cần thiết. Đặc biệt tránh gọi SaveChanges() liên tục trong vòng lặp. Đây là điều nhiều người mới học hay mắc phải.

    Kết luận

    Bài học đã giúp bạn tiếp xúc với những kỹ thuật cơ bản nhất để lập trình với Entity Framework theo hướng tiếp cận Code-first. Với những kỹ thuật này bạn đã có thể dễ dàng sử dụng cơ sở dữ liệu trong project.

    Tuy nhiên, các kỹ thuật này chưa đủ để bạn có thể tạo ra cơ sở dữ liệu với quan hệ phức tạp giữa các bảng.

    Ngoài ra, mỗi vấn đề trình bày ở bài học này vẫn chỉ ở mức độ giới thiệu chứ chưa đi sâu vào chi tiết.

    Trong các bài học tiếp theo chúng ta sẽ đi vào từng vấn đề cụ thể và chuyên sâu của lập trình Entity Framework.

    Nếu có thắc mắc hoặc cần trao đổi thêm, mời bạn viết trong phần Bình luận ở cuối trang. Nếu cần trao đổi riêng, hãy gửi email hoặc nhắn tin qua form liên hệ. Nếu bài viết hữu ích với bạn, hãy giúp chúng tôi chia sẻ tới mọi người. Cảm ơn bạn!

    Bình luận

    avatar
      Đăng ký theo dõi  
    Thông báo về