--- name: ef-core-migration description: Tạo/sửa/revert EF Core 10 migration cho SOLUTION_ERP. Dùng khi thêm entity mới, thay đổi schema, rollback migration, debug DesignTimeDbContextFactory fail. Đã có 12 migration sẵn (Init → AddPurchaseEvaluations). Snapshot + Designer + Migration 3-file rule bắt buộc commit đủ. when-to-use: - "thêm migration" - "EF Core migration" - "dotnet ef migrations add" - "revert migration" - "schema DB update" - "DbContext change" - "snapshot lỗi" - "DesignTimeDbContextFactory" --- # EF Core Migration — SOLUTION_ERP > **Context:** .NET 10 + EF Core 10 + SQL Server. DbContext: `ApplicationDbContext` ở `Infrastructure/Persistence/`. Startup: `SolutionErp.Api`. ## Migration history (12 migration hiện có) | # | Name | Tables added / changed | |---|---|---| | 1 | `Init` | 7 Identity tables (AspNetUsers/Roles/UserRoles/...) | | 2 | `AddMasterData` | Suppliers, Projects, Departments | | 3 | `AddPermissions` | MenuItems, Permissions | | 4 | `AddForms` | ContractTemplates, ContractClauses | | 5 | `AddContractsWorkflow` | Contracts, ContractApprovals, ContractComments, ContractAttachments, ContractCodeSequences | | 6 | `AddNotifications` | Notifications | | 7 | `AddWorkflowTypeAssignments` | WorkflowTypeAssignments (legacy admin override) | | 8 | `AddVersionedWorkflows` | WorkflowDefinitions, WorkflowSteps, WorkflowStepApprovers + `Contracts.WorkflowDefinitionId` FK | | 9 | `AddContractDetailsAndChangelog` | 7 per-type Details (ThauPhu/GiaoKhoan/NhaCungCap/DichVu/MuaBan/NguyenTacNcc/NguyenTacDv) + ContractChangelogs | | 10 | `AddMasterCatalogs` | UnitsOfMeasure, MaterialItems, ServiceItems, WorkItems | | 11 | `AddRoleShortNameAndUserDepartment` | +Role.ShortName + User.DepartmentId/Position (cột thêm, không bảng mới) | | **12** | **`AddPurchaseEvaluations`** | **10 bảng module Duyệt NCC: PurchaseEvaluations + Suppliers + Details + Quotes + Approvals + Changelogs + Attachments + WorkflowDefinitions/Steps/StepApprovers** | Total: **46 bảng** dbo + `__EFMigrationsHistory`. Xem `docs/database/schema-diagram.md` ERD đầy đủ. **Phase 7 pending (session tiếp):** - `AddPePaymentTermFields` — tách `PurchaseEvaluations.PaymentTerms` JSON thành 6 column riêng - `AddPeDepartmentOpinions` — thêm 4 field Ý kiến phòng ban (Phê duyệt/CCM/MuaHàng/SM-PM) vào header hoặc table riêng - `AddPurchaseEvaluationCodeSequences` — atomic MaPhieu sequence (nếu user chốt format mới) ## Commands (chạy từ root repo) ### Thêm migration mới ```powershell # Sau khi chỉnh Domain entity + EF config + DbContext DbSet dotnet ef migrations add ` --project src/Backend/SolutionErp.Infrastructure ` --startup-project src/Backend/SolutionErp.Api ` --output-dir Persistence/Migrations ``` **Naming convention:** PascalCase, verb prefix: - `Add` — thêm entity / bảng - `Update` — thay đổi schema - `Remove` — xóa column / table - `Rename` — đổi tên ### Apply migration to DB ```powershell # Dev: LocalDB SolutionErp_Dev dotnet ef database update ` --project src/Backend/SolutionErp.Infrastructure ` --startup-project src/Backend/SolutionErp.Api # Hoặc đơn giản: chạy API → DbInitializer tự Migrate dotnet run --project src/Backend/SolutionErp.Api ``` ### Revert 1 migration (rollback) ```powershell # Rollback về migration trước đó (ví dụ về sau AddWorkflowTypeAssignments) dotnet ef database update AddWorkflowTypeAssignments ` --project src/Backend/SolutionErp.Infrastructure ` --startup-project src/Backend/SolutionErp.Api # Xóa file migration (3 file, xem dưới) dotnet ef migrations remove ` --project src/Backend/SolutionErp.Infrastructure ` --startup-project src/Backend/SolutionErp.Api ``` ### Gen SQL script (review trước khi apply prod) ```powershell # Script từ 1 migration → latest dotnet ef migrations script AddWorkflowTypeAssignments ` --project src/Backend/SolutionErp.Infrastructure ` --startup-project src/Backend/SolutionErp.Api ` --output tmp/migration-7-to-8.sql # Idempotent (check if exists trước mỗi operation) dotnet ef migrations script --idempotent ` --project src/Backend/SolutionErp.Infrastructure ` --startup-project src/Backend/SolutionErp.Api ``` ## 3-file rule (BẮT BUỘC commit đủ) Mỗi `migrations add` sinh **3 file** — phải commit đủ: ``` src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/ ├── {Timestamp}_{Name}.cs ← migration logic (Up/Down) ├── {Timestamp}_{Name}.Designer.cs ← model snapshot tại thời điểm migration └── ApplicationDbContextModelSnapshot.cs ← current model state (được overwrite mỗi migration) ``` **Gotcha #17:** Thiếu `ModelSnapshot.cs` → `migrations add` kế tiếp sẽ sinh duplicate columns. Thiếu `Designer.cs` → không revert được. ## Pitfalls thường gặp ### P1 — DesignTime DbContext resolve fail **Triệu chứng:** `dotnet ef migrations add` → `Unable to resolve service for type 'DbContextOptions'`. **Nguyên nhân:** EF tooling chạy đứng ngoài runtime DI, cần factory riêng. **Fix:** Đã có `src/Backend/SolutionErp.Infrastructure/Persistence/DesignTimeDbContextFactory.cs`. Nếu connection string thay đổi → update factory, không chỉ `appsettings.json`. ### P2 — Table rename / column rename gen ra DROP + CREATE **Triệu chứng:** Migration sinh `DropColumn` + `AddColumn` thay vì `RenameColumn` → mất data. **Fix:** Sửa migration thủ công sang `migrationBuilder.RenameColumn(...)`. Hoặc dùng `[Column("newname")]` attribute để EF tự detect rename. ### P3 — Query filter (soft delete) khi thêm FK **Triệu chứng:** Warning `The entity type 'X' has a global query filter but referencing entity 'Y' doesn't. This may lead to inconsistent results`. **Fix:** Entity reference cũng phải có query filter: ```csharp builder.HasQueryFilter(x => !x.IsDeleted); ``` Hoặc dùng `.IgnoreQueryFilters()` cho query cần bypass. ### P4 — FK cascade/restrict khác với expectation **Triệu chứng:** Migration apply OK nhưng DELETE parent → cascade sang child (hoặc ngược lại restrict block). **Fix:** Explicit config trong `IEntityTypeConfiguration`: ```csharp builder.HasOne(x => x.Contract) .WithMany(c => c.Approvals) .HasForeignKey(x => x.ContractId) .OnDelete(DeleteBehavior.Cascade); // hoặc Restrict, SetNull ``` **Case study quan trọng:** `Contracts.WorkflowDefinitionId` → `WorkflowDefinitions.Id` **PHẢI dùng `Restrict`** để protect HĐ cũ khi admin archive version (xem `workflow-contract.md` invariants). ### P5 — Nullable reference type gen column NOT NULL **Triệu chứng:** Property `public string? Description` → migration gen `NOT NULL`. **Fix:** Check `builder.HasQueryFilter` chỗ config, hoặc explicit `builder.Property(x => x.Description).IsRequired(false)`. ### P6 — `AddVersionedWorkflows` duplicate seed **Case study hiện tại:** Nếu thêm migration mới sau `AddVersionedWorkflows`, cẩn thận KHÔNG trigger `DbInitializer.SeedWorkflowDefinitionsAsync` 2 lần. Check `if (!db.WorkflowDefinitions.Any())` trong DbInitializer. ## Workflow khi thêm entity mới (example: add AuditLog) ``` 1. Domain/AuditLogs/AuditLog.cs — tạo entity (BaseEntity hoặc AuditableEntity) 2. Infrastructure/Persistence/Configurations/AuditLogConfiguration.cs - ToTable("AuditLogs") - Unique index trên (EntityType, EntityId) nếu cần 3. Infrastructure/Persistence/ApplicationDbContext.cs - public DbSet AuditLogs => Set(); 4. Application/Common/Interfaces/IApplicationDbContext.cs - DbSet AuditLogs { get; } 5. dotnet ef migrations add AddAuditLogs 6. Review file .cs sinh ra → OK 7. dotnet ef database update → LocalDB apply 8. Test: dotnet run → check bảng được tạo 9. Commit [CLAUDE] Domain+Infra: add AuditLog entity + migration 10. ⚠️ UPDATE docs/database/schema-diagram.md (ERD + migration table) 11. UPDATE docs/STATUS.md nếu là work lớn ``` ## Apply prod (VPS) ```powershell # Trên VPS, sau khi deploy code: # Option 1: auto migrate — DbInitializer.MigrateAsync() chạy khi API startup # Option 2: manual script dotnet ef migrations script --idempotent --output migrate.sql # Review migrate.sql → chạy qua sqlcmd: sqlcmd -S .\SQLEXPRESS -d SolutionErp -U vrapp -P -i migrate.sql ``` **Prod safety:** - Backup trước: `scripts/backup-sql.ps1` - Dry-run idempotent script local trước - Test rollback plan: script from-N-to-M-1 sẵn ## Code pointers - `src/Backend/SolutionErp.Infrastructure/Persistence/ApplicationDbContext.cs` — 24 DbSet - `src/Backend/SolutionErp.Infrastructure/Persistence/DesignTimeDbContextFactory.cs` — EF tooling factory - `src/Backend/SolutionErp.Infrastructure/Persistence/DbInitializer.cs` — seed + warn + migrate runtime - `src/Backend/SolutionErp.Infrastructure/Persistence/Configurations/` — 24 IEntityTypeConfiguration - `src/Backend/SolutionErp.Application/Common/Interfaces/IApplicationDbContext.cs` — interface Application layer ## Related - `docs/database/database-guide.md` — conventions + migration workflow chi tiết - `docs/database/schema-diagram.md` — ERD 24 bảng - `docs/gotchas.md` #7, #17 — migration pitfalls