Create a DbContextFactory that gets the connection string from user secrets

Working with the DotNetCore solution with a project WebApiand a separate project Datathat hosts the Entity Framework implementation. We update libraries as they become available, so we use all the latest Core materials.

In the project, Datawe created ApplicationDbContextFactoryto create migrations (we need a constructor without parameters). Due to the constructor’s lack of parameters, when adding hyphenation, you cannot enter IOptions<>to easily access values appsettings.json. We have finished using ConfigurationBuilderto pull out files WebApi appsettings.json.

Recently, we changed ApplicationDbContextFactoryto also pull user-secrets. This allows each developer to use a custom connection string without having to ignore the file or remember not to commit something.

Since making this change, use dotnet ef migrations add MIGRATION_NAMEhas worked great on the command line. However, when used add-migration MIGRATION_NAMEin the Visual Studio Package Manager Console, the following error is now displayed:

add-migration: An exception that throws a "Substring" with an argument of "1": "StartIndex cannot be less than zero. Parameter name: startIndex" At line: 1 char: 1 + add-migration TEST + ~~~~~~~~ ~~~~~~~~~~~~~~ + 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 . ? factory :
  • - , Visual Studio?
+2

Source: https://habr.com/ru/post/1684209/


All Articles