创建一个从用户机密获取连接字符串的 DbContextFactory

Kiz*_*mar 5 c# entity-framework-core asp.net-core

使用一个WebApi项目和一个Data包含实体框架实现的单独项目来处理DotNetCore 解决方案。我们一直在升级库,所以我们使用了所有最新的 Core 东西。

Data项目中,我们创建了一个ApplicationDbContextFactory以创建迁移(需要一个无参数的构造函数)。由于添加迁移时的无参数构造函数约束,您无法通过注入IOptions<>轻松访问appsettings.json值。我们最终使用 aConfigurationBuilder来拉入WebApi'sappsettings.json文件。

我们最近更改了ApplicationDbContextFactory也拉入user-secrets。这允许每个开发人员使用自定义连接字符串,而不必忽略文件或记住不要提交某些内容。

由于进行了此更改,因此dotnet ef migrations add MIGRATION_NAME在命令行中使用效果很好。但是,add-migration MIGRATION_NAME在 Visual Studio 的包管理器控制台中使用现在似乎被破坏并出现以下错误:

add-migration : 使用“1”参数调用“Substring”的异常:“StartIndex 不能小于零。参数名称:startIndex” At line:1 char:1 + add-migration TESTING + ~~~~~~ ~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [Add-Migration], MethodInvocationException +fullyQualifiedErrorId : ArgumentOutOfRangeException,Add-Migration

我尝试了该命令的一些变体,以查看它是否需要指定上下文(除其他外),但似乎没有任何方法可以解决此错误。它似乎永远不会超过ApplicationDbContextFactory.

这是我所指的代码:

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Models.Domain.Settings;
using System;
using System.Diagnostics;

namespace Data
{
    public class ApplicationDbContextFactory : IDbContextFactory<ApplicationDbContext>
    {
        private readonly SolutionSettings _settings;

        // In order to use 'add-migration' in Visual Studio, you have to have a parameterless constructor.
        // Otherwise you get "No parameterless constructor defined for this object." when creating a migration.
        public ApplicationDbContextFactory()
        {
        }

        public ApplicationDbContextFactory(IOptions<SolutionSettings> settings)
        {
            _settings = settings.Value;
        }

        public ApplicationDbContext Create(DbContextFactoryOptions options)
        {
            // If the IOptions signature was hit, we can just pull the dbconnection from settings
            if (_settings != null && _settings.DbConnection != null)
            {
                var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>()
                    .UseSqlServer(_settings.DbConnection, opts => {
                        opts.EnableRetryOnFailure();
                        opts.MigrationsAssembly("Data");
                    });

                return new ApplicationDbContext(optionsBuilder.Options);
            }
            else
            {
                // Otherwise, we have to get the settings manually...
                return Create(options.ContentRootPath, options.EnvironmentName);
            }
        }

        private ApplicationDbContext Create(string basePath, string environmentName)
        {
            // HACK: To pull from WebApi\appsettings.json
            basePath = basePath.Replace("Data", "WebApi");

            Console.Write($"PATH & ENV: {basePath}, {environmentName}" + Environment.NewLine);

            // Pull in the WebApi\appsettings.json files, apply user secrets
            var builder = new ConfigurationBuilder()
                .SetBasePath(basePath)
                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                .AddJsonFile($"appsettings.{environmentName.ToLower()}.json", optional: true, reloadOnChange: true)
                // This needs to match the UserSecretsId value in the WebApi.csproj
                // Also added a UserSecretsId key with the same value to Data.csproj to suppress a warning
                // Adding this was the only way it would actually override values with user-secret values
                .AddUserSecrets("USER_SECRETS_ID")
                .AddEnvironmentVariables();

            var config = builder.Build();
            var connectionString = config["SolutionSettings:DbConnection"];

            Console.Write($"CONNECTION STRING: {connectionString}" + Environment.NewLine);

            return Create(connectionString);
        }

        private ApplicationDbContext Create(string connectionString)
        {
            if (string.IsNullOrEmpty(connectionString))
                throw new ArgumentException(
                    $"{nameof(connectionString)} is null or empty.",
                    nameof(connectionString));

            var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>()
                .UseSqlServer(connectionString, options => {
                    options.EnableRetryOnFailure();
                    options.MigrationsAssembly("Data");
                });

            return new ApplicationDbContext(optionsBuilder.Options);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

作为旁注;在对此进行故障排除时,我添加了opts.EnableRetryOnFailure();opts.MigrationsAssembly("Data");,但我不知道它们在这种情况下有什么不同。

我的问题:

  • 这最初是在 Core 的 RC 时代实现的,可能有点过时了。在创建迁移时是否有更好的方法来完成提取用户机密值?像这样使用工厂还有事吗?
  • 有人知道为什么我们会在 Visual Studio 的包管理器控制台中收到该错误吗?

Pao*_*mos 0

自从你发布它以来已经很长时间了,但我刚刚遇到了这个错误并找出了原因(尽管它没有任何意义)

问题出在线路上

console.Write($"CONNECTION STRING: {connectionString}" + Environment.NewLine);
Run Code Online (Sandbox Code Playgroud)

如果您在 CONNECTION STRING 之后删除冒号,它就可以工作。我不知道为什么插值中的冒号会导致此错误