Top 7 kinh nghiệm thực tế về LINQ trong C# và .NET

Top 7 kinh nghiệm thực tế về LINQ trong C# và .NET
Top 7 kinh nghiệm thực tế về LINQ trong C# và .NET

LINQ giúp bạn viết truy vấn dữ liệu ngay trong C#, từ danh sách trong bộ nhớ đến bảng trong cơ sở dữ liệu qua Entity Framework. Nhưng dùng LINQ tiện không có nghĩa là dùng đúng. Nhiều lỗi hiệu năng xuất phát từ vài thói quen nhỏ. Dưới đây là bảy kinh nghiệm thực tế mà chúng tôi thấy hữu ích khi debug và tối ưu ứng dụng .NET. Mỗi phần chỉ tập trung vào một mẹo hoặc một lỗi cụ thể.

Giữ truy vấn ở dạng IQueryable

Giữ truy vấn ở dạng IQueryable
Giữ truy vấn ở dạng IQueryable

Khi làm việc với Entity Framework, biến dữ liệu thường là IQueryable. Điều này có nghĩa là LINQ được dịch thành câu SQL và chạy trên máy chủ cơ sở dữ liệu. Nhưng nếu bạn gọi AsEnumerable() hoặc ToList() quá sớm, mọi dòng được tải về bộ nhớ trước, rồi mới lọc tiếp bằng C#.

Ví dụ, một bảng đơn hàng có vài triệu dòng. Viết context.Orders.ToList().Where(o => o.Status == “Paid”) sẽ tải toàn bộ bảng. Viết lại thành context.Orders.Where(o => o.Status == “Paid”).ToList() thì chỉ những dòng cần thiết mới về. Sự khác biệt này thường thấy rõ trên máy chủ thật, không chỉ trên máy phát triển.

Dùng Any thay cho Count

Dùng Any thay cho Count
Dùng Any thay cho Count

Nhiều người viết orders.Count(o => o.CustomerId == id) > 0 để kiểm tra khách có đơn hàng hay không. Cách này buộc hệ thống đếm toàn bộ dòng khớp. Any() chỉ cần tìm thấy một dòng là dừng, nên nhanh hơn nhiều khi bảng lớn.

Đổi thành orders.Any(o => o.CustomerId == id) vừa nhanh hơn vừa đọc rõ nghĩa hơn. Câu hỏi là “có hay không”, không phải “bao nhiêu”. Code nói đúng điều bạn muốn biết.

Lưu ý là Any() không phải lúc nào cũng thay được Count(). Nếu bạn cần con số chính xác để hiển thị hoặc tính phí, Count() vẫn là lựa chọn đúng. Điều quan trọng là chọn đúng công cụ cho đúng câu hỏi.

Tránh liệt kê một truy vấn nhiều lần

Tránh liệt kê một truy vấn nhiều lần
Tránh liệt kê một truy vấn nhiều lần

Truy vấn LINQ thường được trì hoãn. Mỗi lần bạn foreach hoặc gọi Count(), Sum() lên cùng một biến, truy vấn có thể chạy lại. Với database, điều đó nghĩa là nhiều lượt gọi đến server cho cùng một dữ liệu.

Ví dụ, một màn hình vừa hiển thị số lượng sản phẩm vừa hiển thị danh sách. Nếu code đếm rồi lại lặp qua cùng biến truy vấn, bạn đã gọi database hai lần. Giải pháp là gọi ToList() một lần rồi dùng biến kết quả. Tuy nhiên, đừng ToList() những truy vấn lớn mà bạn chỉ cần một phần nhỏ. Với dữ liệu ít thay đổi, bạn có thể xem thêm cách cache kết quả truy vấn để giảm tải database.

Chỉ lấy cột cần dùng bằng Select

Chỉ lấy cột cần dùng bằng Select
Chỉ lấy cột cần dùng bằng Select

Nếu màn hình danh sách chỉ cần tên và giá, đừng tải cả entity với hàng chục cột. Dùng Select để chiếu sang một kiểu gọn hơn, như DTO hoặc anonymous type. Với Entity Framework, câu SQL cũng chỉ chọn những cột đó, nên dữ liệu về ít hơn hẳn.

Khi truy vấn chỉ để đọc, thêm AsNoTracking(). EF không cần theo dõi thay đổi cho các đối tượng này, nên tốn ít bộ nhớ hơn. Đừng dùng AsNoTracking khi bạn định sửa rồi lưu lại entity, vì khi đó bạn cần change tracking.

Khi DTO đã được đặt tên rõ nghĩa, người đọc code biết ngay màn hình cần dữ liệu gì. Đồng thời, bạn giảm rủi ro đưa nhầm trường nhạy cảm như mật khẩu hoặc ghi chú nội bộ ra giao diện.

Cẩn thận với biến vòng lặp khi truy vấn bị trì hoãn

Đây là lỗi khó thấy. Khi bạn tạo truy vấn trong vòng for và dùng biến đếm i trong lambda, truy vấn chưa chạy ngay. Biến i có thể đã thay đổi lúc truy vấn thực thi, nên mọi truy vấn có thể dùng cùng một giá trị cuối cùng.

Cách tránh là gán biến tạm trong vòng lặp, ví dụ var id = ids[i] rồi mới dùng id trong lambda. Với foreach, từ C# 5 trở đi mỗi vòng có một biến mới, nên lỗi này ít gặp hơn. Nhưng với vòng for, bạn vẫn cần cẩn thận.

Đọc câu SQL mà EF sinh ra

Khi một truy vấn chậm, đừng đoán. Trong EF Core, bạn có thể gọi ToQueryString() để xem câu SQL trước khi chạy. Với nhiều dự án, chỉ cần xem SQL một lần là đã thấy truy vấn thiếu điều kiện, thiếu chỉ mục, hoặc phát sinh nhiều lượt gọi không cần thiết.

Nếu muốn nhìn lại các lỗi quen thuộc khi làm việc với Entity Framework, bạn có thể đọc các sai lầm thường gặp về Entity Framework. Nhiều lỗi trong danh sách đó bắt nguồn từ cách viết truy vấn.

Một cách kiểm tra nhanh khác là bật log SQL trong môi trường phát triển. Bạn sẽ thấy một thao tác trên giao diện sinh ra bao nhiêu câu truy vấn. Khi con số đó lớn hơn bạn nghĩ, hãy quay lại xem biến nào đang bị liệt kê lặp lại.

Với câu SQL đã xem, hãy đặt nó cạnh kế hoạch thực thi của cơ sở dữ liệu nếu có. Thường chỉ mất vài phút là thấy một bảng đang bị quét toàn bộ, trong khi truy vấn chỉ cần một điều kiện trên cột đã có chỉ mục. Đó là chỗ nên sửa trước tiên, thay vì viết lại cả đoạn code.

Chọn method syntax hay query syntax theo ngữ cảnh

Hai kiểu viết LINQ được trình biên dịch chuyển thành cùng một dạng gọi phương thức. Vì vậy, kiểu nào cũng chạy đúng. Query syntax giống SQL, dễ đọc khi có nhiều join hoặc group by. Method syntax tiện hơn khi bạn cần các toán tử như Take hoặc Skip, hay muốn nối nhiều bước xử lý liên tiếp.

Trong đội nhóm, hãy thống nhất một kiểu cho dự án để code dễ đọc hơn. Nếu cần so sánh chi tiết hơn, bạn có thể đọc so sánh LINQ method syntax và query syntax.

Lời kết

LINQ không xấu. Nó chỉ dễ bị dùng sai khi bạn quên rằng câu lệnh có thể chạy ở nơi khác, như máy chủ cơ sở dữ liệu. Hãy đo trước khi tối ưu, xem SQL khi làm việc với database, và giữ dữ liệu ở dạng truy vấn càng lâu càng tốt. Nếu dự án của bạn xử lý dữ liệu lớn, có thể tìm hiểu thêm tại Query Consulting.