Entity Framework Core 迁移 - 连接字符串

31

我在处理与迁移相关的DB连接字符串时遇到了问题。 我有两个项目:

  • Domain
  • Application

DbContext 在 Domain 项目中,因此这是我运行迁移的项目。 迁移概念强制我在我的 DbContext 中实现 OnConfiguring 并在其中指定数据库提供程序,例如:

protected override void OnConfiguring(DbContextOptionsBuilder builder)
{
    builder.UseSqlServer("<connection string>");
}

我的问题是我不想使用硬编码的连接字符串,这是很明显的原因,而且我也不能使用ConfigurationManager从配置文件中读取它,因为配置文件在应用程序项目中。

6个回答

16

我看到的所有示例都涉及硬编码连接字符串或将其放在我的ASP.NET Core应用程序的设置文件中。

如果您不使用ASP.NET Core,或者也许不想将本地环境的数据库详细信息提交到源代码控制中,您可以尝试使用临时环境变量。

首先,像这样实现IDesignTimeDbContextFactory(注意IDbContextFactory现已弃用):

public class AppContextFactory: IDesignTimeDbContextFactory<AppContext>
{
    public AppContextFactory()
    {
        // A parameter-less constructor is required by the EF Core CLI tools.
    }

    public AppContext CreateDbContext(string[] args)
    {
        var connectionString = Environment.GetEnvironmentVariable("EFCORETOOLSDB");
        if (string.IsNullOrEmpty(connectionString))
            throw new InvalidOperationException("The connection string was not set " +
            "in the 'EFCORETOOLSDB' environment variable.");

         var options = new DbContextOptionsBuilder<AppContext>()
            .UseSqlServer(connectionString)
            .Options;
        return new AppContext(options);
    }
}

然后,在调用Update-Database或任何其他EF Core工具时,您可以包含环境变量:

$env:EFCORETOOLSDB = "Data Source=(local);Initial Catalog=ApplicationDb;Integrated Security=True"; Update-Database

通过 PM 控制台设置(包括)环境变量,请参见此答案 - Kamerton

7

这是我做法,没有太多额外的代码或疯狂的东西。

项目结构:

  • AspNetCoreProject.Web

  • AspNetCoreProject.Data <-- 这里是DbContext

我的DbContext设置了一个构造函数,允许您注入DbContextOptions。

AspNetCoreProject.Data

public class MyContext : DbContext
{
    public MyContext(DbContextOptions<MyContext> options) : base(options)
    {
    }
}

在您的应用程序或Web应用程序中,通常会设置ConfigureServicesAspNetCoreProject.Web / Startup.cs / ConfigureServices()
services.AddDbContext<MyContext>(options => 
            options.UseSqlServer(Configuration.GetConnectionString("connection"))

现在,关于迁移怎么办?我可以"欺骗"Visual Studio UI以按预期工作。

  • 首先确保你的应用程序(AspNetCoreProject.Web项目和Startup.cs)是启动项目

  • 其次,打开Nuget包管理器控制台。在Nuget PM>控制台的顶部,有一个下拉列表项 '设置默认项目',将其指向你的AspNetCoreProject.Data或带有DbContext类的项目。

  • 正常运行你的迁移命令add-migration init 然后 update-database


这是否意味着dotnet ef migration将检查Startup.cs并找出连接字符串? - Yiping
不是很准确,但我对引擎底层的运作原理不够了解,无法回答这个问题。但它确实可以工作。 - Adam Vincent
1
是的,它确实可以。我通过在“Startup”类的“ConfigureServices(IServiceCollection services)”方法中简单地放置“Console.WriteLine($">> CONNECTION STRING: '{connectionString}'.");”来检查它,并且这行代码已经在_PM控制台_中打印出来了。 - Serg
我不想在我的演示层中引用数据层DBContext类,因此我结合了另一个Stack答案,这使我能够将services.AddDbContext卸载到我的业务层。 - Gabe
运行 'add-migration' 时,连接字符串仍为空,无法正常工作。 - A.Rowan

5
假设你的DbContext类有一个接受DbContextOptions类型参数的构造函数,dotnet ef命令对这种情况有本地支持 - 不需要代码更改或额外配置。只需在创建和运行迁移时使用“--startup-project”和“--project”参数即可。
例如,假设您有一个名为“Application”的项目,其中包含您的配置,并且有一个名为“Domain”的单独项目,其中实现了DbContext。
上下文:
public class MyContext : DbContext
{
    public MyContext(DbContextOptions<MyContext> options) : base(options)
    {
    }
} 

创业公司:

services.AddDbContext<MyContext>(options => 
            options.UseSqlServer(Configuration.GetConnectionString("connection"))

命令行界面(CLI):

dotnet ef database update --startup-project Application --project Domain

1
谢谢 - 这绝对是解决这个问题最干净的方法。 - Dan Parsonson

2
我们曾经遇到过同样的问题,有解决方案。 :)
您需要实现 IDbContextFactory<TContext>。这样做可以从您的appsettings.json中读取连接字符串。您还可以使用Add-Migration而没有错误,因为覆盖OnConfigure()已经过时。
示例实现:
public class DomainContextFactory : IDbContextFactory<DomainContext>
{
    public string BasePath { get; protected set; }

    public DomainContext Create()
    {
        var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");

        var basePath = AppContext.BaseDirectory;

        return Create(basePath, environmentName);
    }

    public DomainContext Create(DbContextFactoryOptions options)
        => Create(options.ContentRootPath, options.EnvironmentName);

    private DomainContext Create(string basePath, string environmentName)
    {
        BasePath = basePath;
        var configuration = Configuration(basePath, environmentName);
        var connectionString = ConnectionString(configuration.Build());
        return Create(connectionString);
    }

    private DomainContext Create(string connectionString)
    {
        if (string.IsNullOrEmpty(connectionString))
        {
            throw new ArgumentException($"{nameof(connectionString)} is null or empty", nameof(connectionString));
        }
        var optionsBuilder = new DbContextOptionsBuilder<DomainContext>();
        return Configure(connectionString, optionsBuilder);
    }

    protected virtual IConfigurationBuilder Configuration(string basePath, string environmentName)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(basePath)
            .AddJsonFile("constr.json")
            .AddJsonFile($"constr.{environmentName}.json", true)
            .AddEnvironmentVariables();
        return builder;
    }

    protected virtual string ConnectionString(IConfigurationRoot configuration)
    {
        string connectionString = configuration["ConnectionStrings:DefaultConnection"];
        return connectionString;
    }

    protected virtual DomainContext Configure(string connectionString, DbContextOptionsBuilder<DomainContext> builder)
    {
        builder.UseSqlServer(connectionString, opt => opt.UseRowNumberForPaging());

        DomainContext db = new DomainContext(builder.Options);
        return db;
    }


    DomainContext IDbContextFactory<DomainContext>.Create(DbContextFactoryOptions options)
        => Create(options.ContentRootPath, options.EnvironmentName);
}

我们如何使用它:
    public override IServiceResult<IList<Datei>> LoadAllData()
    {
        using (var db = this.DomainContextFactory.Create())
        {
            var files = db.Datei
                .ToListAsync<Datei>();

            return new ServiceResult<IList<Datei>>(files.Result, files.Result.Count);
        }
    }

示例配置
{
  "ConnectionStrings": {
    "DefaultConnection": "Put your connectionstring here"
  }
}

2
只要你对它在 IoC 框架之前工作没有问题,那么这个方法就可以正常使用。你必须维护解析连接字符串的代码,以便独立地用于 IoC 和这些工厂。由于无法像注入连接字符串提供程序或字符串本身那样将任何内容注入到 IDbContextFactory 中,因此您必须将连接字符串解析代码嵌入到工厂中。如果反复复制这个过程,那么你将会面临一场维护噩梦。这种缺乏注入手段的情况似乎是 EF 团队的一个巨大疏忽。肯定有更好的方法。 - crush
@crush 对的。现在有一种新方法可以避免这段代码。你只需要在 appsettings.json 和专门环境*.json文件中拥有 Connectionstring,然后在您的 ConfigureServices 中只需使用 options.UseSqlServer(this.configuration.GetconnectionString("NameFromJson")); 即可。 - user743414

1

我曾在Windows环境变量MsSql.ConnectionString中使用以下的OnConfiguring,并且运行了用于初始ef迁移创建的命令:dotnet ef migrations add InitialCreate

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    var connectionString = Environment.GetEnvironmentVariable("MsSql.ConnectionString");
    if(string.IsNullOrEmpty(connectionString))
        throw new ConfigurationErrorsException("Sql server connection string configuration required");
    
    if (!optionsBuilder.IsConfigured)
    {
        optionsBuilder
            .UseSqlServer(connectionString)
            .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
    }
}

配置环境变量:

  1. 使用 Win + R 快捷键组合打开 运行 命令窗口
  2. 输入 systempropertiesadvanced 并按 Enter 键
  3. 高级 选项卡上点击 环境变量
  4. 单击 新建... 按钮
  5. 变量名称 字段中输入 MsSql.ConnectionString
  6. 变量值 字段中输入您的连接字符串值

确保在添加新变量并运行与 dotnet ef 相关的命令之前重新启动控制台(以及任何启动控制台的程序)。


https://www.benday.com/2017/02/17/ef-core-migrations-without-hard-coding-a-connection-string-using-idbcontextfactory/ - Techiemanu

0

我在我的控制台应用程序中有我的DBContext,并使用了带有几个参数的构造函数(例如连接字符串等),因为EF Core Migrations正在使用默认的无参数构造函数,因此连接字符串未被填充,导致迁移失败。

只需在我的默认构造函数中添加代码以从ConfigurationBuilder获取连接字符串即可绕过此问题。

我只是在玩控制台应用程序和EF Core,所以现在对我有效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接