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
. Đố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
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
, với TEntity là kiểu của entity class chứa
foreign key.
public EntitySet 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
, lưu ý rằng đây không phải
là một collection.
public EntityRef 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
mà phải là EntityRef
và EntitySet. 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
() với kiểu trả về là một
đối tượng Table. Bởi vì lớp Table này được
hiện thực các interface IEnumerable và IQueryable,
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 employees = db.GetTable();
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:
1 | [Table(Name = "Employees" )] |
4 | [Column(IsPrimaryKey = true )] |
5 | public int EmployeeID { get ; set ; } |
7 | 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:
1 | [Table(Name = "Employees" )] |
4 | [Column(IsPrimaryKey = true ,Name= "EmployeeID" )] |
5 | public int EmployeeID { get ; set ; } |
7 | 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”:
03 | using System.Data.Linq; |
04 | using System.Data.Linq.Mapping; |
06 | namespace LinqToSqlTest |
14 | DataContext db = new DataContext( "C:\\SampleDatabases\\Northwnd.mdf" ); |
16 | Table employees = db.GetTable(); |
18 | var query = from e in employees |
19 | where e.Country == "USA" |
22 | Console.WriteLine( "ID \t Country" ); |
23 | foreach (var emp in query) |
24 | Console.WriteLine( "{0} \t {1}" , |
25 | emp.EmployeeID, emp.Country); |
31 | [Table(Name = "Employees" )] |
34 | [Column(IsPrimaryKey = true )] |
35 | public int EmployeeID { get ; set ; } |
37 | 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:
1 | var query = from e in employees |
2 | where e.Country == "USA" |
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:
1 | var query = from e in employees |
2 | where e.Country == "USA" |
5 | EmployeeID = e.EmployeeID, |
Đ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:
02 | .Where(e => (e.Country == "USA" )) |
07 | EmployeeID = e.EmployeeID, |
Và tương đương với câu truy vấn SQL sau:
1 | SELECT [t0].[EmployeeID], [t0].[Country] |
3 | 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.
(http://yinyangit.wordpress.com/2011/08/11/linq-to-sql-basic-concepts-object-relational-mapping-entity-class-association-and-datacontext/)