Bagikan melalui


Terapkan migrasi Entity Framework Core di .NET Aspire

Karena .NET.NET Aspire proyek menggunakan arsitektur kontainer, database bersifat ephemeral dan dapat dibuat ulang kapan saja. Entity Framework Core (EF Core) menggunakan fitur yang disebut migrasi untuk membuat dan memperbarui skema database. Karena database dibuat ulang saat aplikasi dimulai, Anda perlu menerapkan migrasi untuk menginisialisasi skema database setiap kali aplikasi Anda dimulai. Ini dicapai dengan mendaftarkan proyek layanan migrasi di aplikasi Anda yang menjalankan migrasi selama startup.

Dalam tutorial ini, Anda mempelajari cara mengonfigurasi proyek .NET Aspire untuk menjalankan migrasi EF Core selama pengaktifan aplikasi.

Prasyarat

Untuk bekerja dengan .NET.NET Aspire, Anda memerlukan hal berikut yang diinstal secara lokal:

Untuk informasi selengkapnya, lihat penyiapan dan alat .NET.NET Aspire, dan .NET.NET Aspire SDK.

Mendapatkan aplikasi awal

Tutorial ini menggunakan aplikasi sampel yang menunjukkan cara menerapkan migrasi EF Core di .NET Aspire. Gunakan Visual Studio untuk mengkloning aplikasi sampel dari GitHub atau gunakan perintah berikut:

git clone https://github.com/MicrosoftDocs/aspire-docs-samples/

Aplikasi sampel berada di folder SupportTicketApi. Buka solusi di Visual Studio atau VS Code dan luangkan waktu sejenak untuk meninjau aplikasi sampel dan pastikan itu berjalan sebelum melanjutkan. Aplikasi sampel adalah API tiket dukungan dasar, dan berisi proyek-proyek berikut:

  • SupportTicketApi.Api: Proyek ASP.NET Core yang menjadi host API.
  • SupportTicketApi.AppHost: Berisi host dan konfigurasi aplikasi .NET.NET Aspire.
  • SupportTicketApi.Data: Berisi konteks dan model EF Core.
  • SupportTicketApi.ServiceDefaults: Berisi konfigurasi layanan default.

Jalankan aplikasi untuk memastikan aplikasi berfungsi seperti yang diharapkan. Di dasbor .NET.NET Aspire, tunggu hingga semua sumber daya berjalan dan sehat. Kemudian pilih titik akhir https Swagger dan uji titik akhir GET /api/SupportTickets API dengan memperluas operasi dan memilih Cobalah. Pilih Jalankan untuk mengirim permintaan dan melihat respons:

[
  {
    "id": 1,
    "title": "Initial Ticket",
    "description": "Test ticket, please ignore."
  }
]

Tutup tab browser yang menampilkan titik akhir Swagger dan dasbor .NET.NET Aspire lalu hentikan debugging.

Buat migrasi

Mulailah dengan membuat beberapa migrasi yang perlu diterapkan.

  1. Buka terminal (Ctrl+` di Visual Studio).

  2. Atur SupportTicketApi\SupportTicketApi.Api sebagai direktori saat ini.

  3. Gunakan alat baris perintah dotnet ef untuk membuat migrasi baru untuk mengambil status awal skema database:

    dotnet ef migrations add InitialCreate --project ..\SupportTicketApi.Data\SupportTicketApi.Data.csproj
    

    Perintah yang dilaksanakan:

    • Menjalankan alat migrasi baris perintah EF Core di direktori SupportTicketApi.Api. dotnet ef dijalankan di lokasi ini karena layanan API adalah tempat konteks DB digunakan.
    • Membuat migrasi bernama InitialCreate.
    • Membuat migrasi di folder Migrations dalam proyek SupportTicketApi.Data.
  4. Ubah model sehingga menyertakan properti baru. Buka SupportTicketApi.Data\Models\SupportTicket.cs dan tambahkan properti baru ke kelas SupportTicket:

    public sealed class SupportTicket
    {
        public int Id { get; set; }
        [Required]
        public string Title { get; set; } = string.Empty;
        [Required]
        public string Description { get; set; } = string.Empty;
        public bool Completed { get; set; }
    }
    
  5. Buat migrasi baru lainnya untuk mengambil perubahan pada model:

    dotnet ef migrations add AddCompleted --project ..\SupportTicketApi.Data\SupportTicketApi.Data.csproj
    

Sekarang Anda memiliki beberapa migrasi yang perlu diterapkan. Selanjutnya, Anda akan membuat layanan migrasi yang menerapkan migrasi ini selama pengaktifan aplikasi.

Membuat layanan migrasi

Untuk menjalankan migrasi saat startup, Anda perlu membuat layanan yang menerapkan migrasi.

  1. Tambahkan proyek Worker Service baru ke solusi. Jika menggunakan Visual Studio, klik kanan solusi di Penjelajah Solusi dan pilih Add>New Project. Pilih Worker Service, beri nama SupportTicketApi.MigrationService proyek dan target .NET 8.0. Jika menggunakan baris perintah, gunakan perintah berikut dari direktori solusi:

    dotnet new worker -n SupportTicketApi.MigrationService -f "net8.0"
    dotnet sln add SupportTicketApi.MigrationService
    
  2. Tambahkan referensi proyek SupportTicketApi.Data dan SupportTicketApi.ServiceDefaults ke proyek SupportTicketApi.MigrationService menggunakan Visual Studio atau baris perintah:

    dotnet add SupportTicketApi.MigrationService reference SupportTicketApi.Data
    dotnet add SupportTicketApi.MigrationService reference SupportTicketApi.ServiceDefaults
    
  3. Tambahkan 📦Aspire. Microsoft.EntityFrameworkCore.SqlServer referensi paket NuGet ke proyek SupportTicketApi.MigrationService menggunakan Visual Studio atau baris perintah:

    cd SupportTicketApi.MigrationService
    dotnet add package Aspire.Microsoft.EntityFrameworkCore.SqlServer -v "9.1.0"
    
  4. Tambahkan baris yang disorot ke file Program.cs dalam proyek SupportTicketApi.MigrationService:

    using SupportTicketApi.Data.Contexts;
    using SupportTicketApi.MigrationService;
    
    var builder = Host.CreateApplicationBuilder(args);
    
    builder.AddServiceDefaults();
    
    builder.Services.AddHostedService<Worker>();
    
    builder.Services.AddOpenTelemetry()
        .WithTracing(tracing => tracing.AddSource(Worker.ActivitySourceName));
    
    builder.AddSqlServerDbContext<TicketContext>("sqldata");
    
    var host = builder.Build();
    host.Run();
    

    Dalam kode sebelumnya:

  5. Ganti konten file Worker.cs dalam proyek SupportTicketApi.MigrationService dengan kode berikut:

    using System.Diagnostics;
    
    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Infrastructure;
    using Microsoft.EntityFrameworkCore.Storage;
    
    using OpenTelemetry.Trace;
    
    using SupportTicketApi.Data.Contexts;
    using SupportTicketApi.Data.Models;
    
    namespace SupportTicketApi.MigrationService;
    
    public class Worker(
        IServiceProvider serviceProvider,
        IHostApplicationLifetime hostApplicationLifetime) : BackgroundService
    {
        public const string ActivitySourceName = "Migrations";
        private static readonly ActivitySource s_activitySource = new(ActivitySourceName);
    
        protected override async Task ExecuteAsync(CancellationToken cancellationToken)
        {
            using var activity = s_activitySource.StartActivity("Migrating database", ActivityKind.Client);
    
            try
            {
                using var scope = serviceProvider.CreateScope();
                var dbContext = scope.ServiceProvider.GetRequiredService<TicketContext>();
    
                await RunMigrationAsync(dbContext, cancellationToken);
                await SeedDataAsync(dbContext, cancellationToken);
            }
            catch (Exception ex)
            {
                activity?.RecordException(ex);
                throw;
            }
    
            hostApplicationLifetime.StopApplication();
        }
    
        private static async Task RunMigrationAsync(TicketContext dbContext, CancellationToken cancellationToken)
        {
            var strategy = dbContext.Database.CreateExecutionStrategy();
            await strategy.ExecuteAsync(async () =>
            {
                // Run migration in a transaction to avoid partial migration if it fails.
                await dbContext.Database.MigrateAsync(cancellationToken);
            });
        }
    
        private static async Task SeedDataAsync(TicketContext dbContext, CancellationToken cancellationToken)
        {
            SupportTicket firstTicket = new()
            {
                Title = "Test Ticket",
                Description = "Default ticket, please ignore!",
                Completed = true
            };
    
            var strategy = dbContext.Database.CreateExecutionStrategy();
            await strategy.ExecuteAsync(async () =>
            {
                // Seed the database
                await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);
                await dbContext.Tickets.AddAsync(firstTicket, cancellationToken);
                await dbContext.SaveChangesAsync(cancellationToken);
                await transaction.CommitAsync(cancellationToken);
            });
        }
    }
    

    Dalam kode sebelumnya:

    • Metode ExecuteAsync dipanggil ketika pekerja memulai. Pada gilirannya melakukan langkah-langkah berikut:
      1. Mendapatkan referensi layanan TicketContext dari penyedia layanan.
      2. Menjalankan RunMigrationAsync untuk menerapkan migrasi yang belum diselesaikan.
      3. Memanggil SeedDataAsync untuk menyemai database dengan data awal.
      4. Menghentikan pekerja menggunakan StopApplication.
    • Metode RunMigrationAsync dan SeedDataAsync keduanya merangkum operasi database masing-masing menggunakan strategi eksekusi untuk menangani kesalahan sementara yang mungkin terjadi saat berinteraksi dengan database. Untuk mempelajari selengkapnya tentang strategi eksekusi, lihat Ketahanan Koneksi .

Pasang layanan migrasi ke orkestrator

Layanan migrasi dibuat, tetapi perlu ditambahkan ke host aplikasi .NET.NET Aspire sehingga berjalan saat aplikasi dimulai.

  1. Dalam proyek SupportTicketApi.AppHost, buka file Program.cs.

  2. Tambahkan kode yang disorot berikut ke metode ConfigureServices:

    var builder = DistributedApplication.CreateBuilder(args);
    
    var sql = builder.AddSqlServer("sql", port: 14329)
                     .WithEndpoint(name: "sqlEndpoint", targetPort: 14330)
                     .AddDatabase("sqldata");
    
    builder.AddProject<Projects.SupportTicketApi_Api>("api")
        .WithReference(sql)
        .WaitFor(sql);
    
    builder.AddProject<Projects.SupportTicketApi_MigrationService>("migrations")
        .WithReference(sql)
        .WaitFor(sql);
    
    builder.Build().Run();
    

    Ini mendaftarkan proyek SupportTicketApi.MigrationService sebagai layanan di host aplikasi .NET.NET Aspire.

  3. Jika kode tidak dapat menyelesaikan proyek layanan migrasi, tambahkan referensi ke proyek layanan migrasi di proyek AppHost:

    dotnet add SupportTicketApi.AppHost reference SupportTicketApi.MigrationService
    

    Penting

    Jika Anda menggunakan Visual Studio, dan Anda memilih opsi Enlist in Aspire orchestration saat membuat proyek Worker Service, kode serupa ditambahkan secara otomatis dengan nama layanan supportticketapi-migrationservice. Ganti kode tersebut dengan kode sebelumnya.

Hapus kode penanaman yang ada

Karena layanan migrasi menyemai database, Anda harus menghapus kode penyemaian data yang ada dari proyek API.

  1. Dalam proyek SupportTicketApi.Api, buka file Program.cs.

  2. Hapus baris yang disorot.

    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI();
    
        using (var scope = app.Services.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<TicketContext>();
            context.Database.EnsureCreated();
    
            if(!context.Tickets.Any())
            {
                context.Tickets.Add(new SupportTicket { Title = "Initial Ticket", Description = "Test ticket, please ignore." });
                context.SaveChanges();
            }
        }
    }
    

Menguji layanan migrasi

Sekarang setelah layanan migrasi dikonfigurasi, jalankan aplikasi untuk menguji migrasi.

  1. Jalankan aplikasi dan amati dasbor SupportTicketApi.

  2. Setelah menunggu sebentar, status layanan migrations akan menampilkan Selesai.

    Cuplikan layar dasbor .NET.NET Aspire dengan layanan migrasi dalam status Selesai.

  3. Pilih ikon Console logs pada layanan migrasi untuk menyelidiki log yang menunjukkan perintah SQL yang dijalankan.

Dapatkan kode

Anda dapat menemukan aplikasi sampel selesai di GitHub.

Kode sampel lainnya

Aplikasi sampel Aspire Shop menggunakan pendekatan ini untuk menerapkan migrasi. Lihat proyek AspireShop.CatalogDbManager untuk implementasi layanan migrasi.