ASP.NET Core 資料庫自動產生預設值的最佳實踐

有可能是因為 客戶的業務邏輯、使用者的習慣、既有的流程等,又或是系統的第一個帳號、第一個角色、預分配權限等,有一些定好的、寫死的資料需要被新增到資料庫中。 當然我們也可以直接手動執行 INSERT 或是 UPSERT (UPdate+inSERT) 的 SQL 語法,不過這裡來分享一下筆者最新的做法,在程式啟動時自動註冊所有處理資料庫預設值的類別並自動執行。

先建立 IDataSeeder 介面,只要實作這個介面的類別就代表是用來往資料庫中加入預設值的類別:
    
public interface IDataSeeder
{
    Task SeedAsync();
}
    

建立 DataSeederSetup 靜態類別,DataSeederSetup 的用途是使用反射找到所有實作 IDataSeeder 介面的物件,建立臨時服務,並且逐一執行每個物件中的 SeedAsync 方法。
    
public static class DataSeederSetup
{
    internal static IServiceCollection AddDataSeeder(this IServiceCollection services, IConfiguration config)
    {
        AddDataSeedersUsingReflection(services);

        return services;
    }

    internal static async Task UseDataSeeder(this IHost host, IConfiguration config)
    {
        using var scope = host.Services.CreateScope();
        var serviceProvider = scope.ServiceProvider;

        var seeders = serviceProvider.GetServices<IDataSeeder>();
        foreach (var seeder in seeders)
        {
            await seeder.SeedAsync();
        }
    }

    private static void AddDataSeedersUsingReflection(this IServiceCollection services)
    {
        var assemblies = AppDomain.CurrentDomain.GetAssemblies();

        var seederTypes = assemblies
            .SelectMany(a => a.GetTypes())
            .Where(t => typeof(IDataSeeder).IsAssignableFrom(t) && t is { IsClass: true, IsAbstract: false })
            .ToList();

        foreach (var seederType in seederTypes)
        {
            services.AddTransient(typeof(IDataSeeder), seederType);
        }
    }
}
    

在 Program.cs 中註冊我們 DataSeederSetup 的方法:
    
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDataSeeder(builder.Configuration);
    
var app = builder.Build();

await app.UseDataSeeder(builder.Configuration);

app.Run();
    

接下來就是依據要自動產生的資料實作 IDataSeeder 介面了。
假設我們要在 Roles 資料表中檢查有沒有 Name 為 ADMIN 的資料,如果沒有就自動新增,可以這樣做:
    
public class RoleSeeder(
    ILogger<RoleSeeder> logger,
    ApplicationDbContext dbContext
) : IDataSeeder
{
    public async Task SeedAsync()
    {
        var entity = await dbContext.Roles.FirstOrDefaultAsync(x => x.Name == "ADMIN");
        if (entity != null)
            return;

        entity = new Role
        {
            Name = "ADMIN",
            NormalizedName = "ADMIN"
        };
        dbContext.Roles.Add(entity);
        logger.LogInformation("新增角色: {Role}", entity.Name);

        await dbContext.SaveChangesAsync();
    }
}
app.Run();
    

註: ApplicationDbContext 是自訂的 EF Core 操作資料庫的類別,繼承於 DbContext

這樣在程式啟動時就會自動檢查 Roles 資料表並新增預設值。

有其他資料表要塞入預設值也可以繼續建立其他類別,只要實作 IDataSeeder 介面就可以了!

留言