LINQ to SQL – Các khái niệm cơ bản: Object-Relational Mapping, Entity Class, Association và DataContext

Là một API được tạo ra để làm việc với SQL Server, LINQ to SQL đem lại hướng tiếp cận mới cho việc truy xuất và thao tác dữ liệu từdatabase. Sử dụng phương pháp ánh xạ giữa các đối tượng database và đối tượng trong lập trình (ORM), bạn có thể truy vấn dữ liệu giống như LINQ to Object.

Các khái niệm cơ bản

Entity Class

Bằng cách ánh xạ một table trong database thành một lớp, ta có thể thao tác dữ liệu trên đó bằng mã lệnh lập trình và tất nhiên bao gồm các lambda expression lẫn LINQ. Các lớp được ánh xạ này được gọi là các lớp entity. Như vậy, một lớp được sẽ được ánh xạ đến một table, theo đó một property sẽ được dùng để ánh xạ cho column của table.Việc ánh xạ này được gọi là ORM (object-relational mapping) chính là đặc điểm cơ bản của LINQ to SQL.

Việc ánh xạ này trong .NET được áp dụng bằng cách đặt các attribute cho class, property, method, … kĩ thuật này được gọi là Attribute-based Mapping.Bảng sau đây cho thấy các ánh xạ tương ứng của các đối tượng database vào các đối tượng lập trình:

Database Object LINQ Object
Database DataContext
Table Class and Collection
View Class and Collection
Column Property
Relationship Nested Collection
Stored Procedure Method

Để tạo một thể hiện của table với entity class, ta sử dụng lớp Table<TEntity>. Đối tượng này đại diện cho một table với TEntity là kiểu dữ liệu hay tên của entity class mà bạn tạo ra. Ví dụ với lớp Employee, bạn có thể tạo một đối tượng Table<Employee>

Khi tạo các entity class, bạn nên đặt tên theo dạng số ít.Ví dụ bạn có một table là Books thì tên lớp entity tương ứng nên là Book. Điều này rất dễ hiểu vì một entity class chỉ đại diện cho một dòng dữ liệu trong table. Đây cũng là một tiêu chuẩn đặt tên, như bạn cũng có thể thấy trong .NET, các tên lớp đều ở dạng số ít.

Association

Giống như giữa các bảng trong database, các entity class cũng có thể có một mối quan hệ với nhau dựa theo primary key và foreign key. Ví dụ lớp Category chứa primary key CategoryID liên kết với foreign key CategoryID trong lớp Product. Tức là một Category có thể bao gồm nhiều Product (trong database thì Categories là bảng cha của Products).

Để thể hiện mối quan hệ cha con giữa hai entity class như trên, người ta sử dụng thuật ngữ Association. Trong trường hợp này, lớp Category sẽ chứa một collection các đối tượng Product. Collection này có kiểu là EntitySet<TEntity>, với TEntity là kiểu của entity class chứa foreign key.

public EntitySet<Product> Products;

Tương tự như vậy bên lớp Product, cũng cần phải có một tham chiếu đến lớp Category có quan hệ với nó. Điều này được thực hiện bằng cách sử dụng một đối tượng EntityRef<TEntity>, lưu ý rằng đây không phải là một collection.

public EntityRef<Category> Category;

Một câu hỏi có thể được đặt ra là tại sao không dùng trực tiếp kiểu là Category và IList<Product> mà phải là EntityRef<Category> và EntitySet<Product >. Nguyên nhân điều này là do cơ chế deferred  khi thực hiện truy vấn của các toán tử trong LINQ. Các đối tượng Category và Product sẽ không tồn tại trong hai lớp này cho đến khi chúng được cần tới. Để hiểu thêm về cơ chế này bạn có thể tham khảo bài viết: LINQ – Deferred operator và cơ chế thực hiện truy vấn

DataContext

DataContext được dùng để thiết lập kết nối với database, ngoài ra đối tượng này còn quản lý một các định danh của đối tượng, theo dõi các thay đổi và thực hiện “phiên dịch” các thao tác mà bạn thực hiện trên đối tượng entity thành các câu SQL tương ứng để thực thi trên database.

Có thể coi DataContext là một đối tượng đại diện cho toàn bộ database tương tự như DataSet, nhưng được kết hợp chức năng của các đối tượng connection, command và data adapter trong ADO.NET.

Để tạo một thể hiện của DataContext, bạn cần truyền một connection string cho nó. Connection string này có thể là chuỗi đường dẫn đến tập tin database hoặc tên của SQL Server.

DataContext db = new DataContext(connectionString);

Từ đối tượng DataContext này, bạn có thể lấy các table cần thiết bằng phương thức generic GetTable<TEntity>() với kiểu trả về là một đối tượng Table<TEntity>. Bởi vì lớp Table<TEntity> này được hiện thực các interface IEnumerable<T> và IQueryable<T>, bạn có thể sử dụng các toán tử LINQ để thao tác để truy vấn dữ liệu.

DataContext db = newDataContext(connectionString);

Table<Employee> employees = db.GetTable<Employee>();

Tuy nhiên hướng tiếp cận thường được áp dụng là bạn sẽ tạo một sub class tương ứng cho database cần làm việc. Ví dụ ta làm việc với database Northwind, việc tạo ra một class NorthwindDataContext cùng với các property và phương thức cần thiết sẽ giúp việc sử dụng dễ dàng và hiệu quả hơn trong quá trình phát triển ứng dụng.

Nếu điều này làm bạn cảm thấy rắc rối, hãy yên tâm vì Visual Studio có thể tự động tạo ra tất cả cho bạn. Việc bạn cần làm chỉ là lựa chọn database và sử dụng sao cho hiệu quả cho từng trường hợp cụ thể.

Tạo Entity Class

Phần này sẽ hướng dẫn bạn các bước thủ công cần làm để tạo một lớp ánh xạ tương ứng với một bảng trong database đồng thời thực hiện truy vấn dữ liệu thông qua LINQ.

Đây chỉ là một ví dụ đơn giản giúp bạn hiểu rõ hơn mô hình này, trong các ứng dụng thực tế bạn nên dùng công cụ O/R Designer (Object Relational Designer) trong Visual Studio để thiết kế trực quan.

Ngoài hai attribute là TableAttribute và ColumnAttribute được giới thiệu dưới đây, còn có những loại attribute khác DatabaseAttribute, AssociationAttribute, FunctionAttribute,… nằm ngoài phạm vi của bài viết này.

TableAttribute

Trong ví dụ này ta sẽ tạo một entity class của bảng Employees trong Northwind database. Sử dụng hai attribute [Table] và [Column], ta tạo lớp Employee như sau:

[Table(Name = "Employees")]
public class Employee
{
	[Column(IsPrimaryKey = true)]
	public int EmployeeID { get; set; }
	[Column]
	public string Country { get; set; }
}

Tên của class và các property phải trùng với tên table và cột tương ứng. Nếu bạn muốn đặt tên class và property khác, cần phải sử dụng thuộc tính Name của attribute [Table] và [Column]. Như trong đoạn mã trên, tôi dùng thuộc tính Name để xác định tên của table là Employees.

Tham khảo: TableAttribute

ColumnAttribute

Giả sử bạn muốn đặt tên lại cho EmployeeID thành ID, khi đó ta phải dùng thêm thuộc tính Name cho attribute [Column] này:

[Table(Name = "Employees")]
public class Employee
{
	[Column(IsPrimaryKey = true,Name="EmployeeID")]
	public int EmployeeID { get; set; }
	[Column]
	public string Country { get; set; }
}

Thuộc tính IsPrimaryKey của [Column] rất rõ ràng, xác định một column có phải là khóa chính hay không.

Tham khảo: ColumnAttribute

Ví dụ hoàn chỉnh

Trong ví dụ này tôi sẽ tạo một entity class cho bảng Employees trong database Northwind. Bạn sẽ thấy cách tôi tạo các property cho entity class đều theo cú pháp tắt (auto implemented property). Đối với các ứng dụng phức tạp với yêu cầu xử lý nhiều hơn thì bạn nên tạo một private field và hiện thực property trên đó.

Các bước bạn sẽ thực hiện trong phần này:

1. Tạo một Console Application

2. Add reference thư viện System.Data.Linq và hai chỉ thị sử dụng namespace:

using System.Data.Linq;

using System.Data.Linq.Mapping;

3. Tạo các Entity class tương ứng với mỗi bảng trong database sẽ sử dụng.

4. Tạo một DataContext kết nối đến database.

5. Thực hiện truy vấn trên dữ liệu từ DataContext.

Chương trình C# sau sẽ cho tạo một DataContext kết nối tới tập tin Northwind.mdf, lấy về table Employees và in ra các employee có cột Country là “USA”:

using System;
using System.Linq;
using System.Data.Linq;
using System.Data.Linq.Mapping;

namespace LinqToSqlTest
{
	class Program
    {
		static void Main()
        {

		// Tạo một DataContext từ file Northwnd.mdf
		DataContext db = new DataContext("C:\\SampleDatabases\\Northwnd.mdf");
		// Lấy table Employees
		Table<Employee> employees = db.GetTable<Employee>();
		// Lấy các employee từ USA
		var query = from e in employees
		where e.Country == "USA"
		select e;

		Console.WriteLine("ID \t Country");
		foreach (var emp in query)
		Console.WriteLine("{0} \t {1}",
        emp.EmployeeID, emp.Country);

		Console.Read();
        }
    }

	[Table(Name = "Employees")]
	public class Employee
	{
		[Column(IsPrimaryKey = true)]
		public int EmployeeID { get; set; }
		[Column]
		public string Country { get; set; }
    }
}

Output:

ID       Country
1        USA
2        USA
3        USA
4        USA
8        USA

Câu truy vấn LINQ sau:

var query = from e in employees
where e.Country == "USA"
select e;

Trả về các dòng có Country là “USA” với dữ liệu lấy từ tất cả các cột. Bởi vì ta chỉ cần lấy hai thuộc tính là EmployeeID và Country, câu truy vấn này có thể sửa thành:

var query = from e in employees
where e.Country == "USA"
select new
            {
                EmployeeID = e.EmployeeID,
                Country = e.Country
            };

Đoạn truy vấn trên có thể được viết dưới dạng cú pháp extension method với lambda expression:

var query = employees
   .Where(e => (e.Country == "USA"))
   .Select(
      e =>
new
         {
             EmployeeID = e.EmployeeID,
             Country = e.Country
         }
   );

Và tương đương với câu truy vấn SQL sau:

SELECT[t0].[EmployeeID], [t0].[Country]
FROM[Employees]AS[t0]
WHERE[t0].[Country]= 'USA'

Kết luận

Bài viết này giúp bạn nắm được nền tảng cơ bản của LINQ to SQL, hiểu được cách tạo ánh xạ để truy xuất dữ liệu cũng như các khái niệm quan trọng như DataContext, Entity.Qua bài này, bạn cần rút ra một điểm chính là LINQ to SQL sẽ tạo các Entity class ánh xạ từ các đối tượng trong database, qua đó bạn có thể kết nối và thực hiện truy vấn trên các đối tượng này.

Thay vì tạo các Entity class như trong bài viết này, .NET và Visual Studio hỗ trợ hai công cụ là SQLMetal và Object Relational Designer cho phép bạn làm điều này một cách dễ dàng. Tuy nhiên trước khi bắt đầu sử dụng hai công cụ này, bạn nên dành một chút thời gian để tìm hiểu cách tạo cùng cách hoạt động của các entity class cho một database hoàn chỉnh. Ta sẽ thảo luận vấn đề này trong bài viết tới.

Advertisements

8 thoughts on “LINQ to SQL – Các khái niệm cơ bản: Object-Relational Mapping, Entity Class, Association và DataContext

  1. Bài viết rất hay và khá đầy đủ.
    Sắp tới bạn có bài viết hướng dẫn sử dụng nhiều bảng trong LINQ to SQL không? Vì khi kết nối nhiều bảng, sẽ đụng tới inner join, group by, not in, …

    Trả lời
    • Mặc dù không đúng thời hạn như dự tính nhưng mình cũng sẽ viết một bài hướng dẫn về vấn đề này như đã nói trước đây trong thời gian sớm nhất có thể.
      Cảm ơn bạn vì đã nhận xét cho bài viết.

      Trả lời
  2. Mình đọc hết bài, thấy có cái kết luận: “LINQ to SQL sẽ tạo các Entity class ánh xạ từ các đối tượng trong database, qua đó bạn có thể kết nối và thực hiện truy vấn trên các đối tượng này”.
    Vậy có phả cơ chế hoạt động của LINQ to SQL là câu đó k bạn? Bạn có thể nói rõ hơn k?

    Trả lời
  3. Khi mình sử dụng .NET Object được tạo ra khi sử dụng Linq to SQL, để làm Data trong Crystal Report. Ngoại trừ các cột là khóa chính ra , thì chỉ có thể lấy được các cột có kiểu chữ ( varchar, nvarchar, text trong Database ), các cột có kiểu số ( int, float, money, datetime) thì không hiện thị để mình lựa chọn . YinYang có thể giải thích hay khắc phục vấn đề trên cho mình được không ?

    Trả lời

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s