創建一個從用戶機密獲取連接字符串的DbContextFactory

asp.net-core c# entity-framework-core

使用WebApi項目和包含實體框架實現的單獨Data項目處理DotNetCore解決方案。我們一直在升級庫,所以我們使用了所有最新的Core。

Data項目中,我們創建了一個ApplicationDbContextFactory來創建遷移(需要一個無參數構造函數)。由於添加遷移時無參數構造函數約束,因此無法注入IOptions<>以輕鬆訪問appsettings.json值。我們最終使用ConfigurationBuilderWebApiappsettings.json文件。

我們最近更改了ApplicationDbContextFactory以引入user-secrets 。這允許每個開發人員使用自定義連接字符串,而不必忽略文件或記住不提交某些內容。

自從進行此更改後,使用dotnet ef migrations add MIGRATION_NAME在命令行中運行正常。但是,在Visual Studio的程序包管理器控制台中使用add-migration MIGRATION_NAME現在似乎已被破壞,並顯示以下錯誤:

add-migration:使用“1”參數調用“子串”的異常:“StartIndex不能小於零。參數名稱:startIndex”行: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);
        }
    }
}

作為旁注;在排除故障時我添加了opts.EnableRetryOnFailure();opts.MigrationsAssembly("Data"); ,但我不知道他們在這方面有任何不同。

我的問題:

  • 這最初是在RC時代的Core期間實現的,可能有點過時了。創建遷移時是否有更好的方法來完成提取用戶機密值?像這樣使用工廠仍然是一件事嗎?
  • 任何人都知道我們為什麼在Visual Studio的軟件包管理器控制台中收到錯誤?

熱門答案

你發布它已經有很長一段時間了,但我剛剛發現了這個錯誤,並找出了原因(儘管沒有意義)

問題就在於此

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

如果你在CONNECTION STRING之後將冒號移開它就可以了。我不知道為什麼插值中的冒號導致了這個錯誤



Related

許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow