Chuỗi bài EF Core trên W3Dev đã lên 7 bài, cover từ performance traps cơ bản đến RAG pipeline với vector search. Mỗi bài viết riêng lẻ, nhưng ghép lại thành một lộ trình học EF Core + PostgreSQL khá hoàn chỉnh — từ dev mới bắt đầu dùng EF Core đến dev muốn khai thác tận cùng Postgres trong .NET.
Bài viết này gom tất cả lại thành roadmap có thứ tự, tóm tắt nội dung chính mỗi bài, và thêm checklist để bạn tự đánh giá mình đang ở đâu. Bookmark bài này làm index — quay lại khi cần tra cứu.
Roadmap tổng quan
Level 1: Cơ bản → Performance fundamentals
Level 2: Trung cấp → Cross-cutting concerns + Raw SQL
Level 3: Nâng cao → Postgres-specific features
Level 4: Expert → AI + Database integration
Mỗi level xây trên level trước. Nếu bạn chưa hiểu N+1 Query (Level 1), đừng nhảy sang Partitioning (Level 3). Kiến thức tích lũy — không shortcut được.
Level 1: Cơ bản — Performance Fundamentals
Bài 1: EF Core Performance: N+1 Query, Tracking và Compiled Query
Đọc bài đầy đủ: EF Core Performance
Đây là bài nên đọc ĐẦU TIÊN nếu bạn dùng EF Core. Ba vấn đề performance phổ biến nhất mà gần như project nào cũng gặp.
Nội dung chính:
N+1 Query xảy ra khi bạn access navigation property trong loop — mỗi iteration bắn thêm một query. 100 orders với items = 101 queries thay vì 1. Fix bằng .Include() (eager loading) hoặc .Select() (projection). Khi Include nhiều bảng gây Cartesian Explosion, dùng .AsSplitQuery().
Change Tracking tốn memory cho mọi entity load từ database. Nếu chỉ đọc, .AsNoTracking() giảm 40% RAM và nhanh gần gấp đôi. Set mặc định NoTracking cho toàn DbContext, chỉ bật tracking khi cần update.
Compiled Query cache query plan — hữu ích cho hot path gọi hàng nghìn lần mỗi phút. Nhưng EF Core 6+ đã tự cache ở mức độ nhất định, nên benchmark trước khi dùng.
Checklist tự đánh giá:
- [ ] Biết bật SQL logging để xem query EF Core generate
- [ ] Nhận diện N+1 Query trong code
- [ ] Biết khi nào dùng
IncludevsSelectvsAsSplitQuery - [ ] Hiểu
AsNoTrackingvà khi nào nên set mặc định - [ ] Biết dùng
ToQueryString()debug query
Level 2: Trung cấp — Cross-cutting Concerns + Raw SQL
Bài 2: EF Core Interceptors: Audit Log, Soft Delete Tự Động
Đọc bài đầy đủ: EF Core Interceptors
Khi bạn cần logic chạy cho MỌI SaveChanges — audit timestamp, soft delete, domain events — mà không muốn nhét code vào từng service method.
Nội dung chính:
SaveChangesInterceptor hook vào pipeline trước/sau save. AuditInterceptor tự set CreatedAt, CreatedBy, ModifiedAt, ModifiedBy cho mọi entity implement IAuditable — dev không cần nhớ gọi, nó chạy tự động. Trick quan trọng: set IsModified = false cho CreatedAt/CreatedBy khi update để không bị overwrite.
SoftDeleteInterceptor convert EntityState.Deleted thành EntityState.Modified + set IsDeleted = true. Application code vẫn gọi Remove() bình thường, interceptor âm thầm biến DELETE thành UPDATE. Kết hợp Global Query Filter tự động exclude deleted records khỏi mọi query.
DomainEventInterceptor dispatch events SAU khi save thành công — dùng SavedChangesAsync (quá khứ) chứ không phải SavingChangesAsync (hiện tại). Nếu save fail, events không dispatch.
Checklist:
- [ ] Biết tạo
SaveChangesInterceptorcustom - [ ] Hiểu thứ tự interceptor ảnh hưởng kết quả
- [ ] Biết dùng Global Query Filter cho soft delete
- [ ] Biết
IgnoreQueryFilters()khi cần thấy deleted records - [ ] Hiểu tại sao inject Scoped service vào interceptor constructor là sai
Bài 3: Raw SQL và Stored Procedure Trong EF Core: Khi Nào Dùng?
Đọc bài đầy đủ: Raw SQL và Stored Procedure
LINQ mạnh nhưng không toàn năng. Window function, recursive CTE, database-specific syntax — đều cần raw SQL. Nhưng raw SQL mở cửa cho SQL injection nếu dùng sai.
Nội dung chính:
FromSql (interpolated string) — AN TOÀN, EF Core tự parameterize. FromSqlRaw với string interpolation — NGUY HIỂM, nối chuỗi thẳng. Luôn dùng FromSql trừ khi cần build dynamic query.
SqlQuery<T> (EF Core 8) — trả về DTO/scalar không cần entity. Window function ROW_NUMBER() OVER, recursive CTE cho hierarchical data — những thứ LINQ không diễn tả được.
ExecuteSql — bulk UPDATE/DELETE, DDL, refresh materialized view. Bypass change tracker nên entity đã load sẽ stale.
Stored procedure gọi qua FromSql. Output parameter cần ADO.NET thuần. Repository pattern gom raw SQL vào một chỗ thay vì rải khắp codebase.
SQL injection checklist: giá trị luôn parameterize, tên column/table whitelist, KHÔNG nối chuỗi từ user input.
Checklist:
- [ ] Phân biệt
FromSql(safe) vsFromSqlRaw(dangerous) - [ ] Biết dùng
SqlQuery<T>cho DTO projection - [ ] Biết khi nào
ExecuteSqlvsExecuteUpdate - [ ] Hiểu tại sao column name không parameterize được
- [ ] Biết gom raw SQL vào repository thay vì rải khắp nơi
Level 3: Nâng cao — Postgres-Specific Features
Bài 4: EF Core + PostgreSQL: Tips Tối Ưu Riêng Cho Postgres
Đọc bài đầy đủ: EF Core + PostgreSQL Tips
Postgres không phải "SQL Server nhưng free". Nó có bộ feature riêng — dùng đúng thì nhiều thứ tưởng cần infrastructure thêm (Redis, Elasticsearch, RabbitMQ) thực ra Postgres làm được built-in.
Nội dung chính — 12 tips:
JSONB Column — schema linh hoạt trong relational database. Map Dictionary<string, object> hoặc typed object sang jsonb, GIN index cho query nhanh. Npgsql translate LINQ operators sang PostgreSQL JSON operators.
Array Columns — List<string> → text[], List<int> → integer[]. GIN index cho Contains() query. Thay junction table cho many-to-many đơn giản.
Generated tsvector — full-text search column tự cập nhật khi data thay đổi. Query bằng LINQ thuần qua Matches(), Rank().
EXPLAIN ANALYZE — extension method xem execution plan từ EF query. Nhận diện Seq Scan vs Index Scan, đo actual execution time.
Bulk Upsert — unnest() + ON CONFLICT nhanh gấp 28 lần so với EF AddRange. COPY binary protocol cho insert thuần nhanh gấp 57 lần.
Enum mapping — native Postgres enum type thay vì lưu integer. Database-level validation, readable values.
Connection pooling — Pgbouncer cho multi-instance, hoặc tune Npgsql pool size. Tắt Npgsql pooling khi dùng Pgbouncer.
NodaTime — Instant → timestamptz, LocalDate → date. Hết nhầm timezone.
Optimistic concurrency — xmin system column làm concurrency token. Zero overhead, không cần trigger.
Advisory Locks — distributed lock không cần Redis. pg_try_advisory_lock cho background job singleton.
Partial Index — index chỉ cover subset data. Partial unique constraint cho soft delete pattern.
LISTEN/NOTIFY — real-time push từ database đến .NET app, không polling.
Checklist:
- [ ] Biết map JSONB column và query bằng LINQ
- [ ] Biết dùng array column thay junction table khi phù hợp
- [ ] Biết setup tsvector generated column + GIN index
- [ ] Biết đọc EXPLAIN ANALYZE output
- [ ] Biết bulk upsert bằng
unnest()+ON CONFLICT - [ ] Biết dùng
xmincho optimistic concurrency - [ ] Biết advisory lock thay Redis distributed lock
Bài 5: Full-Text Search PostgreSQL: Thay Thế Elasticsearch?
Đọc bài đầy đủ: Full-Text Search PostgreSQL
Khi nào PostgreSQL FTS đủ, khi nào vẫn cần Elasticsearch — kèm benchmark thực tế 1 triệu documents.
Nội dung chính:
tsvector (document representation) + tsquery (search query) + GIN index = search engine built-in. Từ 320ms xuống 4ms chỉ bằng thêm GIN index.
Weight A/B/C/D cho ranking — match ở title score cao hơn match ở body. ts_headline tạo snippet với keyword highlighted.
Tiếng Việt: unaccent extension strip dấu, custom text search configuration. "nha hang" match "nhà hàng".
pg_trgm cho fuzzy matching — typo tolerance. "angulr" match "angular".
Prefix search cho autocomplete. Kết hợp FTS + fuzzy cho search toàn diện.
Benchmark 1M docs: PostgreSQL ~6-25ms, Elasticsearch ~3-10ms. Postgres chậm hơn nhưng vẫn trong ngưỡng chấp nhận. Postgres thắng ở: $0 infra cost, consistency miễn phí, transaction support.
Elasticsearch vẫn cần khi: dataset 10M+ với query phức tạp, faceted search nâng cao, multi-language stemming, geo+text search kết hợp.
Checklist:
- [ ] Biết tạo tsvector column + trigger/generated column
- [ ] Biết dùng
plainto_tsqueryvswebsearch_to_tsquery - [ ] Biết setup GIN index cho full-text search
- [ ] Biết tạo
unaccentconfig cho tiếng Việt - [ ] Biết dùng
pg_trgmcho fuzzy/autocomplete - [ ] Biết khi nào FTS Postgres đủ và khi nào cần Elasticsearch
Bài 6: PostgreSQL Partitioning: Khi Nào Cần Và Cách Triển Khai
Đọc bài đầy đủ: PostgreSQL Partitioning
Bảng 200 triệu row, query chậm dần, VACUUM chạy hàng tiếng — partitioning là bước tiếp theo sau khi index không cứu được nữa.
Nội dung chính:
Ba kiểu: Range (theo thời gian — phổ biến nhất), List (theo tenant/region), Hash (distribute đều). Declarative syntax từ PostgreSQL 10+.
Partition pruning — query chỉ scan partition liên quan. WHERE created_at = '2025-06-15' chỉ đọc orders_2025, bỏ qua tất cả partition khác.
Migration thực tế: tạo bảng mới đã partition, migrate data theo batch (50.000 row/batch), swap tên bảng trong transaction. Zero-downtime với logical replication.
Tự động tạo partition bằng pg_cron — function check IF NOT EXISTS rồi CREATE TABLE.
Xóa data cũ: DETACH PARTITION + DROP TABLE thay vì DELETE — millisecond thay vì hours.
Bài học đau thương: partition key phải nằm trong PK, foreign key reference hạn chế, quá nhiều partition (1000+) làm planner chậm, default partition phình to nếu quên tạo partition mới.
Checklist:
- [ ] Biết ba kiểu partition và khi nào dùng loại nào
- [ ] Biết tiêu chí quyết định có nên partition hay không
- [ ] Biết migrate bảng đang chạy sang partitioned table
- [ ] Biết setup tự động tạo partition mới
- [ ] Biết xóa data cũ bằng DETACH PARTITION
- [ ] Biết monitor partition size và default partition
Level 4: Expert — AI + Database Integration
Bài 7: RAG Cho .NET Developer: Từ Lý Thuyết Đến Triển Khai
Đọc bài đầy đủ: RAG Cho .NET Developer
Khi EF Core + PostgreSQL gặp AI — xây RAG pipeline dùng pgvector cho semantic search, kết hợp embedding và LLM generation.
Nội dung chính:
RAG pipeline 5 bước: Document Ingestion (đọc PDF/HTML) → Chunking (cắt text + overlap) → Embedding (text → vector 1536 chiều) → Vector Store (pgvector) → Augmented Generation (context + query → LLM).
Chunking strategy ảnh hưởng chất lượng nhiều nhất: paragraph-aware + 200 char overlap cho precision 82% vs fixed-size 60%.
pgvector extension cho PostgreSQL — vector(1536) column type, IVFFlat index, cosine distance search. EF Core integration qua Pgvector.EntityFrameworkCore.
Vector search service: embed query → cosine similarity → top-K chunks → filter minimum score. minScore = 0.7 loại chunk không liên quan.
Prompt engineering: buộc LLM chỉ trả lời từ context, cite nguồn [Nguồn X], nói rõ khi không có thông tin.
Tối ưu: metadata filtering thu hẹp search scope, reranking bằng LLM lọc lần hai.
Cost: ~$0.50 index 50K chunks, ~$0.005/query, ~$150/tháng cho 1000 queries/ngày.
Checklist:
- [ ] Hiểu RAG pipeline 5 bước
- [ ] Biết chunking strategy và tại sao overlap quan trọng
- [ ] Biết setup pgvector trong PostgreSQL + EF Core
- [ ] Biết implement vector search với cosine similarity
- [ ] Biết viết prompt chống hallucination cho RAG
- [ ] Biết estimate cost embedding + generation
Bài bổ trợ: PostgreSQL vs SQL Server
Đọc bài đầy đủ: PostgreSQL vs SQL Server
Không thuộc series trực tiếp nhưng là context quan trọng — tại sao series này chọn PostgreSQL, và khi nào SQL Server vẫn là lựa chọn đúng.
Tóm tắt: PostgreSQL thắng ở licensing ($0), JSONB, extension ecosystem, MVCC concurrency. SQL Server thắng ở SSMS tooling, Temporal Tables, Azure deep integration, enterprise support. EF Core code gần như giống nhau cho cả hai — chỉ đổi 1 dòng UseNpgsql vs UseSqlServer.
Đọc theo thứ tự nào?
Nếu bạn mới dùng EF Core
Bài 1 (N+1, Tracking) → Bài 3 (Raw SQL) → Bài 2 (Interceptors)
Hiểu performance trước, biết escape hatch khi LINQ không đủ, rồi mới tổ chức cross-cutting concern.
Nếu đã quen EF Core, mới chuyển sang Postgres
Bài bổ trợ (PG vs SS) → Bài 4 (Postgres Tips) → Bài 5 (FTS) → Bài 6 (Partitioning)
Hiểu khác biệt giữa hai database trước, rồi deep dive vào Postgres-specific features.
Nếu muốn tích hợp AI
Bài 4 (Postgres Tips, đặc biệt JSONB + pgvector preview) → Bài 7 (RAG Pipeline)
Cần hiểu Postgres extension ecosystem trước khi dùng pgvector cho RAG.
Nếu đọc từ đầu đến cuối
Bài 1 → 2 → 3 → Bổ trợ (PG vs SS) → 4 → 5 → 6 → 7
Đúng thứ tự difficulty. Mỗi bài xây trên kiến thức bài trước.
Checklist tổng — bạn đang ở đâu?
Đếm số checkbox bạn tick được:
0-5 checked: Level 1 — Bắt đầu từ Bài 1
6-12 checked: Level 2 — Chuyển sang Bài 2, 3
13-20 checked: Level 3 — Sẵn sàng cho Postgres-specific
21-27 checked: Level 4 — Explore RAG + AI
28+ checked: Bạn đã master series này 🎉
Tiếp theo — những bài sẽ viết
Series vẫn đang mở. Vài chủ đề mình dự định viết tiếp:
Multi-tenancy với EF Core — shared database, schema-per-tenant, database-per-tenant. Global query filter cho tenant isolation. Row-Level Security trong Postgres.
EF Core + Outbox Pattern — reliable event publishing. Ghi event vào bảng outbox trong cùng transaction với business data, background worker publish ra message broker.
Database Testing — Testcontainers chạy Postgres thật trong Docker cho integration test. Respawn reset data giữa test cases. Không dùng InMemory provider.
EF Core Migration trong Production — zero-downtime migration, blue-green deployment, backward-compatible schema changes. Migration pipeline trong CI/CD.
Connection Resiliency & Retry — transient error handling, EnableRetryOnFailure, circuit breaker pattern với Polly cho database connection.
Tổng kết
7 bài viết, mỗi bài focus một khía cạnh cụ thể của EF Core + PostgreSQL. Không phải lý thuyết — toàn bộ code đều production-ready, đã test, có benchmark khi cần.
EF Core năm 2026 mạnh hơn rất nhiều so với 3 năm trước. SqlQuery<T>, ExecuteUpdate, interceptors, global query filter, bulk operations — công cụ đủ cho phần lớn use case mà không cần rời LINQ. Khi cần rời, raw SQL API an toàn và gọn gàng.
PostgreSQL là partner hoàn hảo — JSONB, array, FTS, pgvector, partitioning — mỗi feature giải quyết một class vấn đề mà trước đây phải thêm infrastructure riêng. Database đơn giản hơn, ít moving parts hơn, ít thứ phải maintain hơn.
Bookmark bài này, quay lại khi cần tra cứu. Mỗi bài trong series là reference độc lập — không cần đọc lại từ đầu khi chỉ cần xem lại cách setup JSONB hay bulk upsert.
Leave a comment
Your email address will not be published. Required fields are marked *