# Πιθανότατα κάνεις λάθος στις Μεταναστεύσεις EF...

Τρέξιμο `MigrateAsync()` Υπάρχει ένας καλύτερος τρόπος - τα πακέτα μετανάστευσης EF σας επιτρέπουν να εκτελέσετε τις μεταναστεύσεις ως ένα ελεγχόμενο βήμα CI, κρατώντας την εφαρμογή παραγωγής σας ασφαλή. Αλλά εδώ είναι το θέμα: μερικές φορές ο "λάθος" τρόπος είναι πραγματικά μια χαρά. Ας εξερευνήσουμε πότε να χρησιμοποιήσουμε κάθε προσέγγιση.

<datetime class="hidden">2025-11-23T18:39</datetime>

<!--category--  Entity Framework, Migrations, GitHub, CI -->
**Επίσημα έγγραφα:** [Επισκόπηση των μεταναστευτικών ροών](https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/) | [Εφαρμογή των Μεταναστών](https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/applying) | [ΜπάντλςCity name (optional, probably does not need a translation)](https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/applying?tabs=dotnet-core-cli#bundles)

[TOC]

# Ο "λάθος" τρόπος (που χρησιμοποιώ)

Αυτό το blog χρησιμοποιεί `MigrateAsync()` Κατά την εκκίνηση - η προσέγγιση που είμαι έτοιμος να σας πω να μην χρησιμοποιήσετε . Εδώ είναι γιατί αυτό είναι εντάξει για μένα , και γιατί πιθανώς δεν είναι για εσάς .

Στο `Program.cs` αρχείο Έχω τα ακόλουθα:

```csharp
    using (var scope = app.Services.CreateScope())
    {
        var blogContext = scope.ServiceProvider.GetRequiredService<IMostlylucidDBContext>();
        await blogContext.Database.MigrateAsync();
    }
```

[`MigrateAsync()`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.relationaldatabasefacadeextensions.migrateasync) εφαρμόζει εν αναμονή της μετανάστευσης και δημιουργεί τη βάση δεδομένων, εάν χρειάζεται. Απλό - αλλά προβληματικό:

1. **Εξάρτηση εκκίνησης** Η εφαρμογή σου δεν ξεκινάει.
2. **Παραβίαση της ασφάλειας** - Η εφαρμογή σας χρειάζεται `db_owner` Μόλις έδωσες στην εφαρμογή σου τα κλειδιά για να πετάξεις τραπέζια.

Γιατί τη γλιτώνω: δημόσια δεδομένα, ενιαίο δίκτυο Ντόκερ, προσωπικό έργο. **Μάλλον δεν μπορείς.**

## Όταν οι Μεταναστεύσεις σε Διαρκή Χρόνο Είναι Καλές

- **Local dev** - Η γρήγορη φαγούρα κερδίζει την τελετή.
- **Προσωπικά έργα** - Χαμηλή ακτίνα έκρηξης, χωρίς ευαίσθητα δεδομένα
- **Docker-σύνθετο περιβάλλον dev** - Η ευκολία κερδίζει.
- **Πρωτότυπο** - Ο Σίμα αλλάζει συνεχώς έτσι κι αλλιώς.

## Όταν δεν είναι

- **Πολλαπλές περιπτώσεις εφαρμογής** - Οι συνθήκες των αγώνων θολώνουν.
- **Ευαίσθητα δεδομένα** - PII, οικονομικός, ρυθμιζόμενος = απαιτούμενος σωστός διαχωρισμός
- **Παραγωγή με πραγματικούς χρήστες** - Αποτυχημένη μετανάστευση = έξοδος

# Ο σωστός τρόπος: Bundles EF

Ένα πακέτο EF είναι ένα αυτόνομο εκτελέσιμο που περιέχει τις συγκεντρωμένες μεταναστεύσεις σας. `dotnet ef database update` συσκευάζεται σε ένα αυτόνομο `.exe`.

**Γιατί τα πακέτα κερδίζουν:**

- **Δεν υπάρχουν εξαρτήσεις χρόνου λειτουργίας** - Ο στόχος δεν χρειάζεται SDK ή EF CLI
- **Ορθός διαχωρισμός** - Το App δεν χρειάζεται ποτέ. `db_owner`; μόνο CI runner κάνει, μόνο κατά τη διάρκεια της ανάπτυξης
- **Ορατότητα CI** - Οι αποτυχίες εμφανίζονται σε αρχεία καταγραφής αγωγών, όχι θαμμένες στην εκκίνηση της εφαρμογής
- **Ασφάλεια αναστροφής** - Η μετανάστευση αποτυγχάνει; Η ανάπτυξη σταματά πριν αναπτυχθεί κακός κώδικας
- **Είσαι πανίσχυρος.** - Εντοπίζει ό, τι εφαρμόζεται, τρέχει μόνο ό, τι χρειάζεται

> **Σημείωση:** Για ασφάλεια ποιότητας παραγωγής, χρήση [Διαχειριζόμενη Ταυτότητα](https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) Αλλά οι δέσμες εξακολουθούν να είναι ένα σημαντικό βήμα προς τα πάνω από τις μεταναστεύσεις runtime.

## Παράδειγμα ενεργειών GitHub

```yaml
      - name: Install EF Core tools
        run: dotnet tool install --global dotnet-ef

      - name: Add EF tools to PATH
        run: echo "$HOME/.dotnet/tools" >> $GITHUB_PATH

      - name: Generate EF migration bundle
        run: |
          dotnet ef migrations bundle \
            --project ${{ env.WEB_PROJECT }} \
            --output efbundle.exe \
            --configuration ${{ env.BUILD_CONFIGURATION }} \
            --runtime ${{ env.RUNTIME_IDENTIFIER }} \
            --context AdminDbContext \
        env:
          AdminSite__ConnectionString: ${{ secrets.PROD_SQL_CONNECTIONSTRING }}

      - name: Run EF migration bundle
        run: |
          ./efbundle.exe
        env:
          AdminSite__ConnectionString: ${{ secrets.PROD_SQL_CONNECTIONSTRING }}
```

Το πακέτο διαβάζει συμβολοσειρές σύνδεσης από τις περιβαλλοντικές μεταβλητές και εφαρμόζει εν αναμονή των μεταναστευτικών ροών.

# Τοπικά BundlesName

Δεν υπάρχει πληροφοριοδότης; Θέλετε να δοκιμάσετε πριν σπρώξετε; Φτιάξτε δέσμες τοπικά.

**Χρήση περιπτώσεων:** Δοκιμή πριν από CI, DBA παράδοση (αυτοσυντηρούμενη exe, δεν χρειάζεται SDK), ανάπτυξη stage, αποσφαλμάτωση με `--verbose`.

## Δημιουργία ενός Bundle

```bash
# Install EF CLI (once)
dotnet tool install --global dotnet-ef

# Basic bundle
dotnet ef migrations bundle \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid \
    --output efbundle.exe

# Self-contained (includes runtime - portable to machines without .NET)
dotnet ef migrations bundle \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid \
    --output efbundle.exe \
    --self-contained

# Cross-platform (e.g., build on Windows, deploy to Linux)
dotnet ef migrations bundle \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid \
    --output efbundle \
    --runtime linux-x64
```

## Τρέχω τη Γέννηση Σας

```bash
# Using default connection string from appsettings.json
./efbundle.exe

# Override with a specific connection string
./efbundle.exe --connection "Host=localhost;Database=mostlylucid;Username=postgres;Password=secret"

# Using an environment variable (matches your config key)
$env:ConnectionStrings__DefaultConnection="Host=localhost;..." # PowerShell
export ConnectionStrings__DefaultConnection="Host=localhost;..." # Bash
./efbundle.exe
```

## Χρήσιμες επιλογές Bundle

```bash
# See what migrations would be applied without running them
./efbundle.exe --dry-run

# Verbose output for debugging
./efbundle.exe --verbose

# Apply migrations up to a specific migration (useful for testing)
./efbundle.exe --target-migration "20231115_AddUserTable"

# Combine options
./efbundle.exe --verbose --dry-run
```

## Τοπική ροή εργασίας δοκιμής

```bash
# 1. Create migration
dotnet ef migrations add AddNewFeature \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid

# 2. Build bundle
dotnet ef migrations bundle \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid \
    --output efbundle.exe

# 3. Dry run first
./efbundle.exe --dry-run --verbose

# 4. Run for real
./efbundle.exe --verbose

# 5. Broken? Remove and retry
dotnet ef migrations remove \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid
```

Σφάλματα σύνταξης αλιευμάτων, παραβιάσεις περιορισμών, θέματα FK - όλα *πριν* ΚΚΠ ή παραγωγή.

## Επιδόσεις κατασκευής

**Η γενιά Bundle είναι αργή** - 30+ δευτερόλεπτα σε μεγάλα έργα.

- Δημιουργήστε χειροκίνητα κατά τη δοκιμή τοπικά
- Δημιουργία σε CI μόνο κατά τη διάρκεια της ανάπτυξης, όχι κάθε PR
- Πακέτα cache αν οι μεταναστεύσεις δεν έχουν αλλάξει

Αν θέλετε πραγματικά αυτό-γενιά, προσθέστε ένα MSBuild στόχο:

```xml
<Target Name="BuildMigrationBundle">
  <Exec Command="dotnet ef migrations bundle --output $(OutputPath)efbundle.exe --force" />
</Target>
```

Τότε: `dotnet build -t:BuildMigrationBundle`

# Υβριδική προσέγγιση

Οι καλύτεροι και από τους δύο κόσμους: ευκολία τοπικά, ασφάλεια στην παραγωγή.

```csharp
if (builder.Environment.IsDevelopment())
{
    using var scope = app.Services.CreateScope();
    var context = scope.ServiceProvider.GetRequiredService<IMostlylucidDBContext>();
    await context.Database.MigrateAsync();
}
// Production: CI pipeline runs the bundle
```

# Εναλλακτικές λύσεις για Bundles

## SQL Scripts

Δημιουργήστε απλό SQL αντί για εκτελέσιμο. Μεγάλη για αναθεώρηση DBA και τις υπάρχουσες διαδικασίες διαχείρισης αλλαγών.

```bash
# All migrations
dotnet ef migrations script --output migrations.sql

# Idempotent (safe to run multiple times) - USE THIS
dotnet ef migrations script --idempotent --output migrations.sql

# Range of migrations
dotnet ef migrations script FromMigration ToMigration --output migrations.sql
```

**Pros:** Πλήρης ορατότητα, κάθε πελάτης SQL μπορεί να το τρέξει, φιλική έκδοση ελέγχου, DBA ροή εργασίας έγκριση.

**Κατά:** Δεν υπάρχει αυτόματη παρακολούθηση (χρήση) `--idempotent`), χειρωνακτική εκτέλεση, πιθανή μετατόπιση εάν τα σενάρια τροποποιούνται.

Βλέπεις; [επίσημα έγγραφα σχετικά με τα σενάρια SQL](https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/applying?tabs=dotnet-core-cli#sql-scripts).

### SQL Scripts in CI

```yaml
- name: Generate and apply migrations
  run: |
    dotnet ef migrations script --idempotent --output migrations.sql
    # SQL Server
    sqlcmd -S ${{ secrets.DB_SERVER }} -d ${{ secrets.DB_NAME }} -i migrations.sql
    # Or PostgreSQL
    PGPASSWORD=${{ secrets.DB_PASSWORD }} psql -h ${{ secrets.DB_HOST }} -f migrations.sql
```

## DIACAC (μόνο Server SQL)

[ΔΑΚΑΚ](https://learn.microsoft.com/en-us/sql/relational-databases/data-tier-applications/data-tier-applications) είναι *κρατικής βάσης* Όχι, όχι. *με βάση τη μετανάστευση*. Μπορείτε να καθορίσει το επιθυμητό σχήμα, και SqlPackage diffs αυτό στη βάση δεδομένων στόχου.

```bash
SqlPackage.exe /Action:Publish /SourceFile:MyDatabase.dacpac /TargetConnectionString:"..."
```

**Pros:** Schema ως κώδικας, παραγωγή auto-diff, χειρίζεται τα πάντα (τραπέζια, απόψεις, SPs, ευρετήρια), εργαλεία επιχειρήσεων.

**Κατά:** SQL Server μόνο, schema σε δύο θέσεις (EF μοντέλα + SQL έργο), diff κινητήρα κάνει αμφισβητήσιμες επιλογές, στήλη μετονομάσεις μοιάζουν με drop+add.

Βλέπεις; [SqlPackage docs](https://learn.microsoft.com/en-us/sql/tools/sqlpackage/sqlpackage).

## Πίνακας σύγκρισης

Η προσέγγιση είναι η καλύτερη για την χρήση .NET Αυτόματες tracks Εφαρμοσμένες DBA Friendly _ DBA Friendly Cross-platform DB
|----------|----------|---------------|---------------------|--------------|-------------------|
| `MigrateAsync()` Ναι, ναι, ναι, ναι, όχι, ναι, ναι, ναι, ναι, ναι, ναι, ναι, ναι.
Ναι, ναι, ναι, ναι, ναι, ναι.
SQL Scripts DBA-ελεγχόμενα περιβάλλοντα `--idempotent` Ναι, ναι, ναι, ναι, ναι, ναι, ναι.
Ναι (βασισμένο στο κράτος) Ναι, ναι, ναι, όχι, όχι, όχι, όχι.

# Συμβουλές

## Ο Σχεδιαστής Αρχείο Πήρε

Οι μεταναστεύσεις δουλεύουν τοπικά αλλά όχι στον πληροφοριοδότη; **Έλεγξες και τα δύο αρχεία:**

- `20231115_AddUserTable.cs` - Ο κώδικας μετανάστευσης
- `20231115_AddUserTable.Designer.cs` - Το μοντέλο στιγμιότυπο

Λείπει το αρχείο Designer = σιωπηλή αποτυχία.

## Πολλαπλά DbContexts

```bash
dotnet ef migrations bundle --context BlogDbContext --output blog-migrations.exe
dotnet ef migrations bundle --context IdentityDbContext --output identity-migrations.exe
```

## Προτεραιότητα συμβολοσειράς σύνδεσης

1. `--connection` επιχείρημα
2. Μεταβλητή περιβάλλοντος
3. `appsettings.json`

Χρήση μεταβλητών περιβάλλοντος σε CI.

## IDesignTimeDbContextFactory

Εάν το DbContext σας βρίσκεται σε ξεχωριστό έργο ή έχει περίπλοκη εκκίνηση, εφαρμόστε το [`IDesignTimeDbContextFactory<T>`](https://learn.microsoft.com/en-us/ef/core/cli/dbcontext-creation?tabs=dotnet-core-cli#from-a-design-time-factory):

```csharp
public class AdminDbContextFactory : IDesignTimeDbContextFactory<AdminDbContext>
{
    public AdminDbContext CreateDbContext(string[] args)
    {
        var config = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: true)
            .AddEnvironmentVariables()
            .AddUserSecrets<AdminDbContextFactory>()
            .Build();

        var connectionString = config["AdminSite:ConnectionString"]
            ?? throw new InvalidOperationException("Missing connection string");

        var optionsBuilder = new DbContextOptionsBuilder<AdminDbContext>();
        optionsBuilder.UseSqlServer(connectionString, sql => sql.CommandTimeout(120));

        return new AdminDbContext(optionsBuilder.Options);
    }
}
```

Χρήση όταν: DbContext σε ξεχωριστό έργο, περίπλοκη εκκίνηση, χρειάζονται μυστικά χρηστών για το σχεδιασμό-χρόνο.

# Τι θα γίνει με...;

Συνηθισμένες ερωτήσεις και αναπηδήσεις που έλαβα.

## "Γιατί δεν τρέχεις απλά `dotnet ef database update` στην CI;"

Καλύπτεται παραπάνω, αλλά η σύντομη έκδοση: τα πακέτα είναι φορητά αντικείμενα. Το βήμα ανάπτυξής σας δεν χρειάζεται EF CLI, πηγαίο κώδικα, ή ανάλυση σχεδιασμού-χρόνου.

## "Δεν είναι υπερβολή για μια μικρή εφαρμογή;"

Αν είσαι μόνος, τα δεδομένα είναι δημόσια και η ακτίνα έκρηξης είναι χαμηλή... `MigrateAsync()` Αλλά τη στιγμή που θα προσθέσετε ένα δεύτερο προγραμματιστή, ευαίσθητα δεδομένα, ή πολλαπλά περιβάλλοντα, πακέτα πληρώνουν για τον εαυτό τους.

## "Τι γίνεται με τις αναποδογυρίσεις;"

Η EF δεν κάνει αυτόματα rollbacks.

- Δημιουργία ενός `Down()` μετανάστευση και να το τρέξει (αλλά θα πρέπει να έχετε γράψει)
- Επαναφορά από το backup
- Γράψτε μια χειροκίνητη μετανάστευση για να αναιρέσετε τις αλλαγές

Για τα κρίσιμα συστήματα: δοκιμή μετανάστευσης κατά ενός κλώνου βάσης δεδομένων πρώτα.

## "Μπορώ να τρέξω μεταναστεύσεις σε ένα δοχείο Kubernetes init;"

Ναι. Το δοχείο Bundle + init είναι ένα συμπαγές μοτίβο:

```yaml
initContainers:
  - name: migrate
    image: myapp:latest
    command: ["./efbundle.exe"]
    env:
      - name: ConnectionStrings__Default
        valueFrom:
          secretKeyRef:
            name: db-secrets
            key: connection-string
```

Το δοχείο App περιμένει να ολοκληρωθεί το init.

## "Τι γίνεται με το FluentMigrator / DbUp / άλλα εργαλεία;"

Τα πακέτα EF είναι η ιθαγενή λύση EF, αλλά [FluentMigrator](https://fluentmigrator.github.io/) και [DbUpName](https://dbup.readthedocs.io/) έχουν τους οπαδούς τους. Βασική διαφορά: αυτά είναι εργαλεία ειδικά για τη μετανάστευση, ενώ τα πακέτα EF προέρχονται από το υπάρχον μοντέλο EF σας.

## "Ο DBA μου θέλει να επανεξετάσει όλα τα SQL πριν τρέξει"

Χρήση `--idempotent` σενάρια:

```bash
dotnet ef migrations script --idempotent --output migrations.sql
```

DBA κριτικές και εγκρίνει. Στη συνέχεια είτε:

- Εκτελέστε το σενάριο χειροκίνητα, ή
- Μόλις εγκριθεί, τρέξε το πακέτο (το οποίο κάνει το ίδιο πράγμα)

## "Πώς χειρίζομαι τις μεταναστεύσεις με μηδενικό χρόνο;"

Αυτό είναι ζήτημα στρατηγικής ανάπτυξης, όχι ζήτημα μετανάστευσης.

1. Κάντε τη μετανάστευση προς τα πίσω συμβατή (προσθέστε στήλες που δεν μπορούν να ακυρωθούν, μην μετονομάσετε)
2. Αναπτύξτε νέο κώδικα που χειρίζεται τόσο παλιά όσο και νέα schema
3. Εκτέλεση της μετανάστευσης
4. Αναπτύξτε κώδικα που χρησιμοποιεί μόνο νέο σχήμα
5. Καθαρισμός (σταγόνα παλιές στήλες σε μεταγενέστερη μετανάστευση)

Οι bundles δεν το λύνουν αυτό - απλά κάνουν το 3ο βήμα πιο προβλέψιμο.