ABP踩坑记录-目录html
由于以往习惯在User Secrets中保存链接字符串之类信息,但当我把链接字符串移到secrets.json中后,却发如今迁移过程当中会报以下的错误:shell
简单说,也就是迁移时没法获取到链接字符串信息。数据库
在Qincai.EntityFrameworkCore
项目中,找到QincaiDbContextFactory.cs
文件,修改以下注释处代码。json
public class QincaiDbContextFactory : IDesignTimeDbContextFactory<QincaiDbContext> { public QincaiDbContext CreateDbContext(string[] args) { var builder = new DbContextOptionsBuilder<QincaiDbContext>(); //var configuration = AppConfigurations.Get(WebContentDirectoryFinder.CalculateContentRootFolder()); var configuration = AppConfigurations.Get(WebContentDirectoryFinder.CalculateContentRootFolder(), addUserSecrets: true); QincaiDbContextConfigurer.Configure(builder, configuration.GetConnectionString(QincaiConsts.ConnectionStringName)); return new QincaiDbContext(builder.Options); } }
这个问题看似解决容易,但仍是费了我很多时间才找到缘由。app
首先,我怀疑是否是secrets.json文件有问题,就把Qincai.Web.Host.csproj
中的UserSecretsId
删掉,从新生成了一个。但难受的是,这下不单是迁移有问题了,连应用都没法启动了,固然报错信息也是没法找到链接字符串。ide
因此,我开始在StartUp
中找注入配置的代码,而后发现不一样于微软模板代码中直接注入IConfiguration
对象的作法,Module Zero本身实现了一个拓展方法IHostingEnvironment.GetAppConfiguration
。其源码以下:工具
public static IConfigurationRoot GetAppConfiguration(this IHostingEnvironment env) { // 这里第三个参数表明是否添加User Secrets // 能够看到Module Zero默认只在开发环境中添加 return AppConfigurations.Get(env.ContentRootPath, env.EnvironmentName, env.IsDevelopment()); }
这里调用了AppConfigurations
类,注意这个类是定义在Qincai.Core项目中,这是致使问题的关键。ui
而后,咱们来研究一下AppConfigurations
类中的这段代码:this
private static IConfigurationRoot BuildConfiguration(string path, string environmentName = null, bool addUserSecrets = false) { var builder = new ConfigurationBuilder() .SetBasePath(path) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); if (!environmentName.IsNullOrWhiteSpace()) { builder = builder.AddJsonFile($"appsettings.{environmentName}.json", optional: true); } builder = builder.AddEnvironmentVariables(); if (addUserSecrets) { // 在这里添加了User Secrets builder.AddUserSecrets(typeof(AppConfigurations).GetAssembly()); } return builder.Build(); }
乍一看好像没问题,可是注意,我刚才说了AppConfigurations
是属于Qincai.Core
项目,即根据默认的配置,typeof(AppConfigurations).GetAssembly()
获取到的是Qincai.Core.dll
下的程序集。spa
也就是说,这里添加的User Secrets是根据Qincai.Core.csproj
中定义的UserSecretsId,而不是Qincai.Web.Host.csproj
中定义的。
打开Qincai.Core.csproj
,咱们确实注意到有一个UserSecretsId字段,且和Qincai.Web.Host.csproj
中未修改器前的一致。
这里就是Module Zero取巧的地方了,由于在VS中,只有Web项目才能在右键中找到管理用户机密,因此经过两个配置两个相同的UserSecretsId,使其能经过VS的快捷方式修改Qincai.Core
项目的secrets.json文件。
既然找到缘由了,那么只需将UserSecretsId恢复成一致便可,痛快地敲下F5,启动成功。
所以注意,一旦在你修改了Qincai.Web.Host.csproj
中的UserSecretsId后,千万不要忘了修改Qincai.Core.csproj
,务必确保两个UserSecretsId一致,否则你再怎么改,程序也是届不到的。
不是完了吗?才怪嘞!!咱们的问题是迁移时没法读取User Secrets,以上经历只能说又回到了起点。
到这里,咱们已经能够在程序运行时成功读取到User Secrets,可是在数据库迁移过程当中,仍是会报错:
System.ArgumentNullException: Value cannot be null. Parameter name: connectionString
让咱们打开ef工具的详细输出-v
来看一看,其中有这么一段输出:
Finding DbContext classes... Finding IDesignTimeDbContextFactory implementations... Finding application service provider... Finding IWebHost accessor... Using environment 'Development'. Using application service provider from IWebHost accessor on 'Program'. Finding DbContext classes in the project... Found DbContext 'QincaiDbContext'. Using DbContext factory 'QincaiDbContextFactory'.
它提到了QincaiDbContextFactory
这个类,源码以下:
/* This class is needed to run "dotnet ef ..." commands from command line on development. Not used anywhere else */ public class QincaiDbContextFactory : IDesignTimeDbContextFactory<QincaiDbContext> { public QincaiDbContext CreateDbContext(string[] args) { var builder = new DbContextOptionsBuilder<QincaiDbContext>(); // 注意这里 var configuration = AppConfigurations.Get(WebContentDirectoryFinder.CalculateContentRootFolder(); QincaiDbContextConfigurer.Configure(builder, configuration.GetConnectionString(QincaiConsts.ConnectionStringName)); return new QincaiDbContext(builder.Options); } }
看到注释,咱们应该就是找对地方了,注意我在代码中标出的位置。它也经过AppConfigurations.Get
来获取配置,可是没有给出AddUserSecrets
参数(默认为false
),而根据此前的代码可知,它没有添加User Secrets。
那么解决方案就很简单了,显式给出AddUserSecrets
参数便可。
var configuration = AppConfigurations.Get(WebContentDirectoryFinder.CalculateContentRootFolder(), addUserSecrets: true);