EngineeringSenior DeveloperTechnical Sessions

Session 25: SQL Server Internals, EF Core & Raw SQL

Bài viết này đi sâu vào kiến trúc bên dưới của SQL Server và cách tương tác hiệu quả với Database thông qua 2 trường phái: Entity Framework Core (LINQ) và Raw SQL (Stored Procedures/Dapper).


1️⃣ SQL Server Storage Engine Internals

Mọi dữ liệu bạn lưu trữ đều được chia thành các Page và Extent.

  • Page (8KB): Đơn vị lưu trữ nhỏ nhất. Mỗi Page chứa header, data rows và row offset array.
  • Extent (64KB): Gồm 8 liên tiếp Pages. Khi bảng cần thêm data, SQL Server cấp phát theo Extent.

Buffer Pool & Data Caching

Bạn không bao giờ đọc dữ liệu trực tiếp từ ổ cứng.

  1. SQL Server đưa Page từ đĩa vào Buffer Pool (Memory cache).
  2. Khi UPDATE, dữ liệu được sửa thẳng trên RAM (gọi là Dirty Page).
  3. Song song, Log được ghi xuống đĩa trước (WAL - Write Ahead Logging).
  4. Tiến trình Checkpoint hoặc Lazy Writer sẽ ghi các Dirty Page xuống đĩa sau.

👉 Suy ra: Ram càng lớn, Buffer Pool chứa được càng nhiều Page, truy vấn càng tránh phải I/O vào Disk vật lý -> hiệu năng cực cao.


2️⃣ Tham Chiến: EF Core (LINQ) vs Raw SQL (Stored Procedure)

Hệ thống nên dùng cái nào? Câu trả lời của Architect: Cả hai. Sự kết hợp giữa EF Core cho CRUD, Domain Logic và Raw SQL (Dapper, SP) cho Report, Bulk Ops là mô hình hoàn hảo.

A. Entity Framework Core (LINQ Approach)

Ưu điểm:

  • Năng suất siêu cao (Type-safe, dễ refactor).
  • Change Tracking tuyệt vời cho Update/Delete.
  • Tự động map Object (ORM).

Yếu điểm (Pitfalls):

  • N+1 Query: Nếu quên dùng .Include() hoặc dùng sai vòng lặp.
  • Cartesian Explosion: Khi .Include() nhiều bảng 1-N, dữ liệu trả về bị nhân lên gấp nhiều lần. (Khắc phục bằng .AsSplitQuery()).
  • Khó Tuning ở mức vi mô (Ví dụ không thể dùng cụm từ WITH (NOLOCK) dễ dàng hoặc các Window functions/CTE phức tạp).

Code Mẫu (Tối ưu LINQ):

csharp
// ❌ Bad: N+1 hoặc lấy thừa dữ liệu
var users = await dbContext.Users.Include(u => u.Orders).ToListAsync();

// ✅ Good: AsNoTracking (dành cho Read-only), Projection
var userDtos = await dbContext.Users
    .AsNoTracking()
    .Where(u => u.IsActive)
    .Select(u => new UserDto {
        Id = u.Id,
        TotalOrders = u.Orders.Count()
    })
    .ToListAsync();

B. Raw SQL & Stored Procedures (Dapper Approach)

Ưu điểm:

  • Hiệu năng tối đa, nắm toàn quyền kiểm soát Execution Plan.
  • Có thể dùng các tính năng nâng cao của SQL: CTE, CROSS APPLY, Window Functions (ROW_NUMBER(), RANK()), Temporary Tables (#Temp).
  • Giảm tái khởi tạo Plan nhờ Parameterization mặc định của Stored Procedure.

Yếu điểm:

  • Code base khó bảo trì hơn, khó refactor do gõ "string".
  • Logic Business dễ bị rò rỉ (leak) xuống Database thay vì nằm ở layer C# Application.

Code Mẫu (Stored Procedure with Dapper):

csharp
// Dapper gọi SP siêu tốc độ
var p = new DynamicParameters();
p.Add("@DateFrom", startDate);
p.Add("@DateTo", endDate);

// Lấy danh sách Report thống kê nặng, không dùng EF Core
var report = await connection.QueryAsync<DailyReportDto>(
    "sp_GetComplexDailyReport", 
    p, 
    commandType: CommandType.StoredProcedure);

3️⃣ Khi nào dùng cái gì? (Architect's Decision)

  1. Transaction / Domain Logic: Dùng EF Core. Nó giúp gom các bước thay đổi State của Bounded Context (DDD) vào chung 1 .SaveChanges().
  2. Read-heavy / Dashboard / Reporting: Dùng Dapper + Stored Procedure hoặc Dapper + Raw SQL (Views, CTE) vì chỉ cần Get ra DTO nhanh nhất có thể.
  3. Bulk Insert / Update: Dùng SqlBulkCopy (hoặc thư viện EFCore.BulkExtensions) thay vì Add và SaveChanges từng dòng với EF Core.