Session 53 closeout (HMW-mode ON, 'làm hết' full close). Code already shipped in44b9e54(Mig 47, Run #260) +dbf6648(C+D, Run #261), both prod-verified. - STATUS/HANDOFF: S53 entry (mig 46->47, test 200->203, menu +Off_AttendanceReport, bundle admin DfCfHUE9, database-agent verified-runtime). - Doc-drift E (H1 top-5): ef-core skill 43->47, agents/README roster 10->11 + plugin nac, CLAUDE.md root 45->47 mig + 186->203 test, docs/CLAUDE.md 56->57 gotcha + 91->92 ERD. - adap-report: database-agent executed-file -> verified-runtime (spawn-test caught Mig 46-unapplied-local). - session log 2026-06-08-S53 + 4 agent diaries (S53 work). - Memory: +project_database_agent_verified_local_drift (user-memory, outside repo). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
22 KiB
name, description, when-to-use
| name | description | when-to-use | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| ef-core-migration | 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ó 47 migration sẵn (Init → FilterMasterCatalogUniqueIndexesByIsDeleted Mig 47, S53). Snapshot + Designer + Migration 3-file rule bắt buộc commit đủ. |
|
EF Core Migration — SOLUTION_ERP
Context: .NET 10 + EF Core 10 + SQL Server. DbContext:
ApplicationDbContextởInfrastructure/Persistence/. Startup:SolutionErp.Api.
Migration history (47 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 |
| 13 | AddPurchaseEvaluationCodeSequences |
1 bảng PurchaseEvaluationCodeSequences (Prefix PK, LastSeq) — atomic sequence cho MaPhieu format PE/{YYYY}/{A|B}/{Seq:D3} |
| 14 | AddBudgets |
4 bảng module Ngân sách: Budgets + BudgetDetails + BudgetApprovals + BudgetChangelogs. + nullable FK index Contracts.BudgetId & PurchaseEvaluations.BudgetId |
| 15 | AddPurchaseEvaluationDepartmentOpinions |
1 bảng PurchaseEvaluationDepartmentOpinions (UNIQUE PEId+Kind, max 1 row mỗi PeDepartmentKind per phiếu — Phê duyệt/Ccm/MuaHang/SmPm) |
| 16 | AddTwoStageDeptApprovalAndSmartReject |
3 bảng Contract/PurchaseEvaluation/Budget DepartmentApprovals (UNIQUE TargetId+Phase+Dept+Stage cho 2-stage NV.Review → TPB.Confirm per phòng × phase) + 4 ALTER (Users.CanBypassReview bit cho NV bypass + 3 RejectedFromPhase int cho smart reject jump-back). Phase 9 — đóng bug "NV duyệt được hết phase" anh Kiệt (FDC) báo. Logic 2-stage trong PurchaseEvaluationWorkflowService chỉ áp PE; HĐ + Budget defer. |
| 17 | AddManualBudgetFieldsToPeAndContract |
4 ALTER (PE + HĐ × BudgetManualName nvarchar(200) + BudgetManualAmount decimal(18,2)) — manual budget fallback khi user không link Budget entity approved. KHÔNG XOR với BudgetId, cả 2 cùng null OK. Carry-forward pe.BudgetManualName/Amount → contract ở CreateContractFromEvaluation. Phase 9 — Session 11 (2026-05-07). |
| 18 | AddPeWorkflowInnerStepsAndPositionLevel |
N-stage workflow PE — 1 CREATE TABLE PurchaseEvaluationWorkflowStepInnerSteps (Order, DepartmentId, PositionLevel, Name, SlaDays, IsRequired) + 2 ALTER (Users.PositionLevel int? 1=NV/2=PP/3=TP + PEDeptApproval.InnerStepId Guid?) + 3 IX + FK Cascade Step / Restrict Dept+InnerStep. Phase 9+ — Session 12 (2026-05-07). |
| 19 | AlterPeDeptApprovalsUniqueFilteredForInnerSteps |
Filtered unique split: drop UNIQUE cũ Mig 16 → 2 filtered: legacy WHERE InnerStepId IS NULL (Stage Review/Confirm) + N-stage WHERE InnerStepId IS NOT NULL (per inner step). Tránh conflict 2 inner step cùng dept Stage=Confirm. Session 12. |
| 20 | AddContractWorkflowInnerStepsAndAlterDeptApprovalUnique |
N-stage workflow Contract mirror PE Mig 18+19 — GỘP 1 migration: CREATE TABLE WorkflowStepInnerSteps + ALTER ContractDeptApproval.InnerStepId + DropIndex old + Recreate filtered legacy/N-stage + 3 IX + FK. Session 13 (2026-05-07). |
| 21 | RefactorWorkflowToFlatModel |
🎯 DRASTIC REFACTOR (Session 16, 2026-05-08): bỏ phase enum legacy, dùng ChoDuyet=10 đơn nhất + currentStepIndex tracking. Workflow flat (Phòng × Cấp + Approvers). 4 ALTER (PE/Contract +CurrentWorkflowStepIndex/RejectedAtStepIndex) + 2 ALTER (WorkflowStep +DepartmentId/PositionLevel PE+Contract) + DROP TABLE × 2 (PE+Contract WorkflowStepInnerSteps Mig 18+20) + DROP COLUMN InnerStepId × 2 + DROP filtered indexes Mig 19/20 + RESTORE simple unique non-filtered × 2. |
| 22 | AddApprovalWorkflowsV2 |
🎯 V2 schema mới (Session 17, 2026-05-08) — riêng cho UAT trước khi drop legacy V1. 3 entity ApprovalWorkflow + Step + Level + enum ApplicableType (DuyetNcc=1 / DuyetNccPhuongAn=2 / Contract=3). 3 CREATE TABLE + UNIQUE (Code, Version) + FK Cascade Step→Workflow + Level→Step + FK Restrict Department + ApproverUserId. Cấu trúc: Quy trình > Bước (Phòng) > Cấp (1 NV cụ thể qua ApproverUserId, OR-of-N rows cùng Order = same Cấp). DbInitializer +menu V2 + leaf AwV2_DuyetNcc/DuyetNccPhuongAn. |
| 23 | AddApprovalWorkflowIdToPurchaseEvaluation |
Pin V2 vào PE — PE.ApprovalWorkflowId Guid? + EF FK Restrict. Workspace Select bắt buộc workflow lúc create + Validate ApplicableType match PE.Type. Session 17. |
| 24 | AddCurrentApprovalLevelOrderToPe |
Service V2 wire — PE.CurrentApprovalLevelOrder int? track Cấp đang chờ trong Step hiện tại khi pin ApprovalWorkflowId. Null khi V1 legacy hoặc terminal. Service ApproveV2Async group Levels by Order = Cấp (OR-of-N), match actor.Id ∈ ApproverUserId, advance levelOrder++ trong Step → idx++ + reset levelOrder=1 → DaDuyet. Synthetic Policy ForV2Schema() cho FE nextPhases. Session 17. |
| 25 | AddIsUserSelectableToApprovalWorkflows |
ALTER ApprovalWorkflows +IsUserSelectable bit NOT NULL DEFAULT 0 — admin pin/unpin workflow cho user pick lúc create phiếu (multi-select, độc lập IsActive — multiple version cùng selectable). Backfill WHERE IsActive=1 SET 1 giữ behavior cũ (active workflows vẫn pickable). Designer +badge "Cho user chọn" + button Ghim/Bỏ ghim + mutation toggle. Workspace dropdown filter isUserSelectable=true only. API PATCH /api/approval-workflows-v2/{id}/user-selectable admin-only. Session 18 (2026-05-08). |
| 26 | AddPeLevelOpinionsForV2 |
🎯 Section 5 dynamic theo Workflow V2 (Session 19, 2026-05-09) — bảng mới PurchaseEvaluationLevelOpinions UNIQUE composite (PEId, ApprovalWorkflowLevelId), FK Cascade Pe + Restrict Level. Comment nvarchar(2000) + SignedAt + SignedByUserId + SignedByFullName denorm. Service ApproveV2Async UPSERT auto khi NV duyệt (Q1=1B sync gắn với Duyệt, KHÔNG endpoint CRUD rời). Match level theo ApproverUserId; Admin override fallback first level (FE banner "Admin duyệt thay" khi SignedByUserId !== ApproverUserId). Comment empty → "(duyệt — không ý kiến)" placeholder Q4 bonus. Phiếu V1 legacy fallback Mig 15 4 box readOnly. Mig 15 deprecated cho V2 phiếu (drop sau UAT confirm Mig 27+). |
| 27 | AddVisibilityAndDisplayLabelToMenuItems |
2 ALTER MenuItems (+IsVisible bit=1 +DisplayLabel nvarchar(200)?) — admin ẩn/hiện + đổi tên menu eOffice (fe-user). Không bảng mới. Session 20. |
| 28 | AddAdvancedOptionsToApprovalWorkflows |
ALTER ApprovalWorkflows — advanced options (skip/optional level config). Session 22. |
| 29 | RefactorAdvancedOptionsToPerLevelAndDrafterUser |
Refactor advanced options → per-ApprovalWorkflowLevel + DrafterUser scope. Session 22. |
| 30 | AddAllowApproverEditBudgetToLevels |
ALTER ApprovalWorkflowLevels +AllowApproverEditBudget bit. Session 22. |
| 31 | RefactorSkipToFinalToApproverLevel |
Per-Approver-slot skip-to-final (Level → Approver slot swap). Session 23. |
| 32 | AddApprovalWorkflowToContract |
🎯 Contract V2 wire (Plan B S29) — 2 ALTER Contracts (+ApprovalWorkflowId Guid? +CurrentApprovalLevelOrder int?) cookie-cutter mirror PE Mig 23+24. Không bảng mới. |
| 33 | AddContractLevelOpinions |
1 bảng ContractLevelOpinions (UNIQUE composite ContractId+LevelId, FK Cascade Contract + Restrict Level) — mirror PE Mig 26. Session 29. |
| 34 | AddEmployeeProfiles |
HRM core — EmployeeProfiles + N satellite tables cùng parent (Pattern 12-ter). Session 34. |
| 35 | AddHrmConfigs |
HRM config catalogs (LeaveTypes / Holidays UNIQUE composite / declarative KIND_CONFIG). Session 35. |
| 36 | AddMeetingRooms |
Office — MeetingRooms + booking. Session 36-37. |
| 37 | ExtendApplicableTypeForWorkflowApps |
Enum extend ApplicableType (+Proposal +WorkflowApps values). Không bảng mới. Session 37-38. |
| 38 | AddProposals |
Module Đề xuất (Proposal) — tables + workflow V2 mirror PE/Contract. Session 37. |
| 39 | AddWorkflowApps |
Workflow Apps skeleton — Leave/OT/Travel/Vehicle/Ticket request tables (Phase 10 Plan G-O4/O5). Session 38. |
| 40 | AddAttendances |
Chấm công (Attendance) tables + Dashboard NS skeleton. Session 38. |
| 41 | WireWorkflowAppsApprovalV2 |
🎯 Phase 11 P11-A (S42) — +4 {Leave,Ot,Travel,Vehicle}LevelOpinions (UNIQUE composite + Cascade/Restrict) + WorkflowAppCodeSequences (atomic MaDonTu) + 4 ALTER RejectedFromStatus + enum TravelRequest=9. |
| 42 | AddLeaveBalances |
🎯 Phase 11 P11-B (S43) — 1 bảng LeaveBalances (User×LeaveType×Year + Entitled/Used/Adjustment, UNIQUE composite + FK LeaveTypes Restrict). Trừ phép tự động khi đơn nghỉ duyệt cuối. |
| 43 | FilterHolidayUniqueIndexByIsDeleted |
🎯 S45 — DropIndex + CreateIndex IX_Holidays_Year_Date UNIQUE filtered WHERE [IsDeleted]=0 (was unfiltered). Fix reachable 500 khi recreate ngày lễ trên slot soft-deleted. Không bảng mới (table vẫn 91). gotcha #57 — soft-delete UNIQUE phải filter. |
Total: 91 bảng dbo + __EFMigrationsHistory (last Mig 43 FilterHolidayUniqueIndexByIsDeleted, index-only). Xem docs/database/schema-diagram.md migration table + §11-15 module ERD (§16+ Mig 27-42 chi tiết pending).
N-stage workflow pattern (Mig 18-20 — Session 12-13)
Architecture decision đáng ghi cho session sau:
Pattern: Mỗi WorkflowStep cha (= 1 phase) cấu hình động chuỗi InnerSteps con theo Department × PositionLevel với Order sequential. Approver cần khớp DeptId + PositionLevel + Order tiếp theo chưa duyệt. CanBypassReview cho user level cao hơn skip cấp dưới cùng dept.
Per-module migration (PE first, mirror sang Contract sau):
- PE: 2 migration tách (Mig 18 schema + Mig 19 alter index) — vì index issue phát hiện sau khi schema đã commit
- Contract: 1 migration GỘP (Mig 20) — gộp CREATE TABLE + ALTER + DropIndex + Recreate filtered. Cleaner cho mirror.
Filtered unique trick: legacy 2-stage rows (Mig 16, Stage=Review/Confirm) + N-stage rows (per InnerStep) cùng table *DepartmentApprovals → cần unique split:
UX_*_Phase_Dept_Stage WHERE InnerStepId IS NULL(legacy, prevent duplicate Review/Confirm cùng phase × dept)UX_*_Phase_InnerStep WHERE InnerStepId IS NOT NULL(N-stage, prevent duplicate per inner step)
Backward compat 100%: workflow no InnerSteps configured → service fallback legacy 2-stage logic Mig 16. Data legacy InnerStepId=null vẫn enforce unique cũ qua filtered index.
Budget defer: Budget chưa có versioned WorkflowDefinition entity (hardcoded BudgetPolicy.Default). Để mirror N-stage Budget cần migration AddBudgetVersionedWorkflow trước (4 bảng + ALTER Budget.WorkflowDefinitionId?). Defer cho user quyết riêng.
Phase 7 pending:
AddPePaymentTermFields— táchPurchaseEvaluations.PaymentTermsJSON thành 6 column riêng (Session 11+++++ đã thay UI Workspace<Textarea>JSON →<Select>8 preset, BE schema vẫn nvarchar(max). Migration tách field defer cho khi BE cần aggregate/filter từng terms 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 (đã làm Mig 15 — bảng riêng UNIQUE PEId+Kind)
Phase 8 pending (Budget refinement):
AddBudgetCodeSequences— atomic sequence khi format MaNganSach chốt (mirror Contract/PE pattern, hiện Random.Shared)AddBudgetVersionedWorkflow— nếu Solutions cần admin config UI (3 bảngBudgetWorkflowDefinitions/Steps/StepApprovers+Budgets.WorkflowDefinitionId?)
Phase 8 update (2026-04-29 Session 5):
- Migration 15
AddPurchaseEvaluationDepartmentOpinions— 1 bảng riêng (UNIQUE PEId+Kind), max 4 row mỗi phiếu cho 4 phòng ban (Phê duyệt/Ccm/MuaHang/SmPm). UPDATE in-place khi user đổi ý, audit qua Changelog. - Tests:
tests/SolutionErp.Domain.Tests/(58 test policy state machine) +tests/SolutionErp.Infrastructure.Tests/(96 test = codegen + 2-stage/N-stage + AuthorizePolicy + V2 actor scope + WorkflowApps ApproveV2 + LeaveBalance/guard). Total 154 test pass (current S43; S22-S25 baseline was 111). S22-S23 +20 cumulative regression: ReturnMode + DraftGuard + Reflection AuthorizePolicy + V2 actor scope reject + per-NV lookup discrimination (Plan N+O). Mig 21 drop 19 legacy (PE 2-stage S9 + PE N-stage S12 + Contract N-stage S13 + PE Reject S14) — flat workflow stabilized post-S22 V2 ground truth. - CI optimize 3 fix (29/04):
- Manual checkout bypass github.com (gotcha #39) — fix TCP timeout 21s
- Path filter docs-only skip (gotcha #41) — commit MD-only KHÔNG trigger CI
- npm junction cache (gotcha #40) — rolled back, debug session sau
- Migration verify pattern thêm: chạy
dotnet test SolutionErp.slnxlocal trước mỗi migration commit để chống regression policy/codegen.
Phase 7 update (2026-04-28):
- Migration 14
AddBudgets— 4 bảng (Budgets/Details/Approvals/Changelogs) + 2 cộtBudgetId?thêm vào Contracts & PurchaseEvaluations. Workflow hardcodedBudgetPolicy.Default(chưa versioned). - 14 demo user thật
@solutions.com.vnquaSeedDemoUsersAsyncreconcile (PRO 5 + CCM 7 + ISO 1 + CEO 1).
Phase 6 update (2026-04-24):
- Enum
PurchaseEvaluationAttachmentPurpose.ComparisonTable = 4mới (file bảng so sánh tổng). Int column, không cần migration. BackfillUserEmailDomainAsynctrong DbInitializer — rename in-place 4 field Email/NormalizedEmail/UserName/NormalizedUserName. Idempotent.
Commands (chạy từ root repo)
Thêm migration mới
# Sau khi chỉnh Domain entity + EF config + DbContext DbSet
dotnet ef migrations add <MigrationName> `
--project src/Backend/SolutionErp.Infrastructure `
--startup-project src/Backend/SolutionErp.Api `
--output-dir Persistence/Migrations
Naming convention: PascalCase, verb prefix:
Add<Thing>— thêm entity / bảngUpdate<Thing>— thay đổi schemaRemove<Thing>— xóa column / tableRename<Thing>— đổi tên
Apply migration to DB
# 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)
# 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)
# 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<ApplicationDbContext>'.
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:
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<T>:
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<AuditLog> AuditLogs => Set<AuditLog>();
4. Application/Common/Interfaces/IApplicationDbContext.cs
- DbSet<AuditLog> 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)
# 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 <LastApplied> --idempotent --output migrate.sql
# Review migrate.sql → chạy qua sqlcmd:
sqlcmd -S .\SQLEXPRESS -d SolutionErp -U vrapp -P <pw> -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— DbSet cho 91 bảng (42 migration)src/Backend/SolutionErp.Infrastructure/Persistence/DesignTimeDbContextFactory.cs— EF tooling factorysrc/Backend/SolutionErp.Infrastructure/Persistence/DbInitializer.cs— seed + warn + migrate runtime + backfill (idempotent reconcile pattern)src/Backend/SolutionErp.Infrastructure/Persistence/Configurations/— IEntityTypeConfiguration per entitysrc/Backend/SolutionErp.Application/Common/Interfaces/IApplicationDbContext.cs— interface Application layer
Related
docs/database/database-guide.md— conventions + migration workflow chi tiếtdocs/database/schema-diagram.md— ERD 91 bảng + §11 PE + §12 Budget + §13 PEDeptOpinions (Mig 15) + §14 ApprovalWorkflow V2 (Mig 22-25) + §15 PE Level Opinions V2 (Mig 26); §16+ Mig 27-42 detail pending (xem migration table)docs/gotchas.md#7, #17, #38 — migration pitfalls + Identity 4-field rename