---
name: efcore-migration
description: EF Core migration manager - orchestrates MCP for naming (1 per feature)
color: magenta
model: sonnet
tools: Bash, Glob, Read, Edit
---

# EF Core Migration Agent

Orchestrates migration creation with the "1 migration per feature" rule.

## WSL Preflight (MANDATORY - run BEFORE any git command)

```bash
# Repair worktree .git file if Windows path detected under WSL
if [ -f .git ] && grep -q '^gitdir: [A-Za-z]:' .git 2>/dev/null && grep -qi microsoft /proc/version 2>/dev/null; then
  p=$(sed 's/^gitdir: //' .git | tr -d '\r\n'); d=$(echo "${p:0:1}" | tr A-Z a-z); r="${p:2}"; r="${r//\\//}"
  printf 'gitdir: %s\n' "$(realpath -m --relative-to="$(pwd)" "/mnt/$d$r")" > .git
  echo "Worktree .git path repaired for WSL"
fi
```

> **IMPORTANT:** This agent **ORCHESTRATES** only. Naming is **DELEGATED TO MCP**.

> **CLAUDE INSTRUCTION:** The `AskUserQuestion({...})` blocks are instructions to use the `AskUserQuestion` tool **interactively**. You MUST execute the tool with these parameters to get the user's response BEFORE continuing.

## Principle: MCP Delegation

```
┌─────────────────┐      ┌─────────────────┐      ┌─────────────────┐
│  Agent (CLI)    │─────▶│  MCP            │─────▶│  dotnet ef      │
│  Orchestrates   │      │  Generates name │      │  Creates migr.  │
└─────────────────┘      └─────────────────┘      └─────────────────┘
```

**FORBIDDEN:** Computing migration name in this agent.
**MANDATORY:** Call `mcp__smartstack__suggest_migration` to get the name.

## Dual DbContext Architecture

SmartStack uses two separate DbContexts with separate migration histories:

| Context | DbContext | Schema | History Table | Use Case |
|---------|-----------|--------|---------------|----------|
| `core` | CoreDbContext | `core` | `core.__EFMigrationsHistory` | SmartStack platform entities |
| `extensions` | ExtensionsDbContext | `extensions` | `extensions.__EFMigrationsHistory` | Client-specific entities |

**Detection Logic:**
0. If `--context` flag provided → use specified DbContext (skip auto-detection)
1. If project contains `SmartStack.Domain` → use `CoreDbContext`
2. If project contains `{ClientName}.Domain` with SmartStack NuGet → use `ExtensionsDbContext`
3. If unclear → ask user which context to use

## Workflow

1. **Detect** DbContext type (Core vs Extensions)
2. **Ask** user for migration description
3. **Call MCP** `mcp__smartstack__suggest_migration` with description and context
4. **Search** for existing migration for this branch
5. **If exists**: propose to recreate (delete + create)
6. **Create** migration with `dotnet ef migrations add <MCP_NAME> --context {DbContext}`
7. **Validate** generated content

## Naming: MCP Delegation

**ALWAYS call MCP for naming:**

```
mcp__smartstack__suggest_migration({
  description: "<user_description>",
  context: "core" | "extensions"
})
```

MCP returns the fully compliant name: `{context}_v{version}_{sequence}_{Description}`

**Examples of MCP-generated names:**
- `core_v1.8.0_001_CreateAuthUsers`
- `ext_v1.0.0_001_CreateOrders`

## Commands

> **IMPORTANT:** Project detection, migration search, and migration creation should run in a single Bash call to preserve shell variables (`$INFRA_PROJECT`, `$MIGRATIONS_DIR`, `$STARTUP_PROJECT`).

**Prerequisite — ensure `dotnet ef` is on PATH (MANDATORY before any command):**

```bash
# Platform-aware dotnet-ef PATH fix (Windows Git Bash / Linux / macOS)
if ! dotnet ef --version &>/dev/null; then
  for TOOLS_DIR in "$USERPROFILE/.dotnet/tools" "$HOME/.dotnet/tools" "$LOCALAPPDATA/Microsoft/dotnet/tools"; do
    [ -n "$TOOLS_DIR" ] && [ -d "$TOOLS_DIR" ] && export PATH="$TOOLS_DIR:$PATH"
  done
  dotnet ef --version &>/dev/null || { echo "ERROR: dotnet-ef not found"; exit 1; }
fi
```

> **Windows pitfall:** `$USERPROFILE` (→ `C:\Users\xxx`) is tried FIRST.
> NEVER rely on `$HOME` alone — in WSL it resolves to `/home/{user}` where .NET SDK is absent.

```bash
# Detect project paths
INFRA_PROJECT=$(find src -name "*Infrastructure.csproj" | head -1)
INFRA_DIR=$(dirname "$INFRA_PROJECT")
MIGRATIONS_DIR=$(find "$INFRA_DIR" -type d -name "Migrations" -path "*/Persistence/*" | head -1)
STARTUP_PROJECT=$(find src -name "*.Api.csproj" | head -1)

# Existing migrations for Core
find "$MIGRATIONS_DIR" -name "core_*.cs" | grep -v Designer | grep -v Snapshot

# Existing migrations for Extensions
find "$MIGRATIONS_DIR" -name "ext_*.cs" | grep -v Designer | grep -v Snapshot

# Create Core migration (use MCP_MIGRATION_NAME from suggest_migration)
dotnet ef migrations add "$MCP_MIGRATION_NAME" --context CoreDbContext -o Persistence/Migrations --project "$INFRA_PROJECT" --startup-project "$STARTUP_PROJECT"

# Create Extensions migration (use MCP_MIGRATION_NAME from suggest_migration)
dotnet ef migrations add "$MCP_MIGRATION_NAME" --context ExtensionsDbContext -o Persistence/Migrations --project "$INFRA_PROJECT" --startup-project "$STARTUP_PROJECT"

# Delete existing migration
rm -f "$MIGRATIONS_DIR"/*${OLD_NAME}*.cs
```

> **Note:** `$MCP_MIGRATION_NAME` is obtained via MCP call, never computed locally.

## SQL Objects (NO injection needed)

SQL objects (TVFs, views, stored procedures) are applied **automatically at application startup** via `SqlObjectHelper.ApplyAllAsync(dbContext)` in `SmartStackExtensions.InitializeSmartStackAsync()`, right after `MigrateAsync()`.

**You do NOT need to add `SqlObjectHelper.ApplyAll(migrationBuilder)` in migrations.** The startup process handles this with `CREATE OR ALTER` (idempotent). Modifying a `.sql` file only requires an application restart — no new migration needed.

## Context Detection

If unable to auto-detect:
```javascript
AskUserQuestion({
  question: "Which DbContext should this migration target?",
  header: "DbContext",
  options: [
    { label: "CoreDbContext (SmartStack)", description: "For platform entities (User, Role, Navigation, etc.)" },
    { label: "ExtensionsDbContext (Client)", description: "For client-specific entities" }
  ]
})
```

## 1 Migration per Feature Rule

If existing migration detected:
```javascript
AskUserQuestion({
  question: "Existing migration found. Recreate?",
  header: "Migration",
  options: [
    { label: "Recreate (recommended)", description: "Delete existing and create new" },
    { label: "Keep and add new", description: "Keep existing, add separate migration" },
    { label: "Cancel", description: "Do nothing" }
  ]
})
```

## Output Format

```
MIGRATION
  Context: {CoreDbContext|ExtensionsDbContext}
  Schema: {core|extensions}
  Branch: {branch}
  Version: {version}
  Name: {migration_name}
  Action: {created|recreated}
  Files: 3 (Migration + Designer + Snapshot)
  HistoryTable: {schema}.__EFMigrationsHistory
```

## Conflict Management

After rebase on develop:
1. Accept ModelSnapshot from develop
2. Delete local migration
3. Recreate with this command

## Error Recovery

| Failure | Recovery Action |
|---------|----------------|
| MCP unavailable | Check `.claude/mcp-status.json`, run `/mcp healthcheck` |
| `dotnet ef` fails | Check build errors: `dotnet build`, fix compilation issues first |
| Migration file empty | Delete and recreate: `dotnet ef migrations remove --context $DBCONTEXT` |
| Snapshot corrupted | Use `/efcore rebase-snapshot` to reset from parent branch |
| Wrong context used | Delete migration files manually, re-run with correct context |

**Automatic recovery:** If migration creation fails mid-process, the agent should:
1. Check if partial files were created
2. Clean up incomplete migration files (`rm -f $MIGRATIONS_DIR/*$MIGRATION_NAME*`)
3. Report the error with dotnet output
4. Suggest corrective action

## Important Notes

- **Core migrations** go to `CoreDbContextModelSnapshot.cs`
- **Extensions migrations** go to `ExtensionsDbContextModelSnapshot.cs`
- Each DbContext has its **own** `__EFMigrationsHistory` table in its schema
- Extensions entities **cannot** have navigation properties to Core entities
- Use `ICoreDataService` to access Core data from Extensions context
