dev/prod 的 dotnet ef 独立数据库提供程序

dotnet ef separate database provider for dev/prod

尝试在本地开发中使用 sqlite,在生产中使用 sqlserver。
如果我根据环境切换提供程序,仍然存在仅为其中一个提供程序生成迁移的问题(sqlite 迁移不适用于 sqlserver,反之亦然)。

与在产品中相比,为开发使用单独的数据库的可行方法是什么?

诀窍是为 dev 创建一个单独的 dbcontext,让它继承主 dbcontext 并只更改连接字符串。 从那里开始,就是为主要 + 开发 dbcontexts 生成迁移,然后根据环境选择要使用的上下文。

代码设置

主 dbcontext 将使用依赖注入来获取包含您的连接字符串的配置。

using app.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace app.Data
{
    public class AppDbContext : DbContext
    {
        protected readonly IConfiguration Configuration;

        public AppDbContext(IConfiguration config)
        {
            Configuration = config;
        }

        protected override void OnConfiguring(DbContextOptionsBuilder options)
        {
            options.UseSqlServer(Configuration["SQLConnectionString"]);
        }

        // whatever your app does
        public DbSet<Dog> Dogs { get; set; }
    }
}

dev dbcontext 将仅继承主要的,使用不同的配置块来指定 sqlite。

using app.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace app.Data
{
    public class DevAppDbContext : AppDbContext
    {
        public DevAppDbContext(IConfiguration config) : base(config)
        {
        }

        protected override void OnConfiguring(DbContextOptionsBuilder options)
        {
            options.UseSqlite(Configuration.GetConnectionString("app"));
        }
    }
}

对于我们的appsettings.Development.json,我们将添加开发数据库连接字符串

...
  "ConnectionStrings": {
    "app": "Data Source=app.db"
  }
}

在startup.cs中我们将通过依赖注入添加IWebHostEnvironment来获取环境。如果在 dev 中,那么我们将指定额外的通用参数,指定 dev dbcontext 作为实现。

根据 vscode 中的文档,方法签名是: public static IServiceCollection AddDbContext<TContextService, TContextImplementation>


        public Startup(IConfiguration configuration, IWebHostEnvironment env)
        {
            _config = configuration;
            _env = env;
        }

        private readonly IConfiguration _config;
        private readonly IWebHostEnvironment _env;

        public void ConfigureServices(IServiceCollection services)
        {
            if (_env.IsDevelopment())
                services.AddDbContext<AppDbContext, DevAppDbContext>();
            else
                services.AddDbContext<AppDbContext>();
            services.AddDatabaseDeveloperPageExceptionFilter();

添加迁移

添加迁移现在需要切换环境并指定我们正在为其创建迁移的 dbcontext。这可以抽象为一个相当简单的 powershell 脚本:

[CmdletBinding()]
param(
    [string]$Name
);

$prev = $Env:ASPNETCORE_ENVIRONMENT;
Write-Host "Previous env=""$prev""";
try {
    Write-Host "Adding migration for production";
    $Env:ASPNETCORE_ENVIRONMENT = "Production";
    dotnet ef migrations add $Name `
        --context "AppDbContext" `
        --output-dir "Migrations/SqlServerMigrations";
        
    Write-Host "Adding migration for development";
    $Env:ASPNETCORE_ENVIRONMENT = "Development";
    dotnet ef migrations add $Name `
        --context "DevAppDbContext" `
        --output-dir "Migrations/SqliteMigrations";
}
finally {
    Write-Host "Restoring env=""$prev""";
    $Env:ASPNETCORE_ENVIRONMENT = $prev;
}

用法示例:

> .\scripts\add-migration.ps1 -Name init
Previous env=""
Adding migration for production
Build started...
Build succeeded.
Done. To undo this action, use 'ef migrations remove'
Adding migration for development
Build started...
Build succeeded.
Done. To undo this action, use 'ef migrations remove'
Restoring env=""

应用迁移

和创建迁移一样,我们需要根据环境切换上下文。不过这一次,我们只会更新用户在调用脚本时指定的单个环境。

[CmdletBinding()]
param(
    [string]
    [ValidateSet("Development", "Production")]
    $Environment
)

$context = @{
    "Development" = "DevAppDbContext" 
    "Production"  = "AppDbContext"
}.$Environment;

$prev=$Env:ASPNETCORE_ENVIRONMENT;
try {
    $Env:ASPNETCORE_ENVIRONMENT=$Environment;
    dotnet ef database update --context $context;
}
finally {
    Write-Host "Restoring env=""$prev""";
    $Env:ASPNETCORE_ENVIRONMENT = $prev;
}

用法示例:

> .\scripts\apply-migrations.ps1 -Environment Development
Setting environment to Development
Build started...
Build succeeded.
No migrations were applied. The database is already up to date.
Done.
Restoring env=""

结论

这应该足以开始使用 sqlite 进行本地开发,使用 sqlserver 进行实时开发。这种方法的一些限制是 sqlite 不支持 sqlserver 的所有功能,因此您也必须设置一个开发 sqlserver 数据库,或者需要决定避免使用这些功能。

进一步阅读

我习惯在web.config文件中放两个同名的连接字符串,而且总是其中一个必须被注释掉。 当你必须进行测试时,注释 Prod 的连接字符串。 发布解决方案时注释 Dev 的连接字符串。