web-dev-qa-db-fra.com

Modifier dynamiquement la chaîne de connexion dans Asp.Net Core

Je souhaite modifier la chaîne de connexion SQL dans le contrôleur, pas dans ApplicationDbContext. J'utilise Asp.Net Core et Entity Framework Core.

Par exemple:

public class MyController : Controller {
    private readonly ApplicationDbContext _dbContext
    public MyController(ApplicationDbContext dbContext)
    {
        _dbContext = dbContext;
    }
    private void ChangeConnectionString()
    {
    // So, what should be here?
    } }

Comment puis-je faire ceci?

13
Yurii N.

Nous avons un cas similaire à vous. Ce que nous avons fait, c'est utiliser la surcharge implementationfactory de IServiceCollection dans ConfigureServices méthode de la classe Démarrage, comme ceci:

//First register a custom made db context provider
services.AddTransient<ApplicationDbContextFactory>();
//Then use implementation factory to get the one you need
services.AddTransient(provider => provider.GetService<ApplicationDbContextFactory>().CreateApplicationDbContext());

Il m'est très difficile en ce moment d'implémenter CreateApplicationDbContext pour vous, car cela dépend totalement de ce que vous voulez exactement. Mais une fois que vous avez compris comment vous voulez le faire exactement, les bases de la méthode devraient de toute façon ressembler à ceci:

public ApplicationDbContext CreateApplicationDbContext(){
  //TODO Something clever to create correct ApplicationDbContext with ConnectionString you need.
} 

Une fois cela implémenté, vous pouvez injecter le bon ApplicationDbContext dans votre contrôleur comme vous l'avez fait dans le constructeur:

public MyController(ApplicationDbContext dbContext)
{
    _dbContext = dbContext;
}

Ou une méthode d'action dans le contrôleur:

public IActionResult([FromServices] ApplicationDbContext dbContext){
}

Quelle que soit la façon dont vous implémentez les détails, l'astuce est que la fabrique d'implémentation construira votre ApplicationDbContext chaque fois que vous l'injecterez.

Dites-moi si vous avez besoin d'aide pour implémenter cette solution.

Mise à jour # 1 Yuriy N. a demandé quelle est la différence entre AddTransient et AddDbContext, ce qui est une question valide ... Et ce n'est pas le cas. Laissez-moi expliquer.

Ce n'est pas pertinent pour la question d'origine.

MAIS ... Cela dit, implémenter votre propre `` usine d'implémentation '' (qui est la chose la plus importante à noter dans ma réponse) peut dans ce cas avec le framework d'entité être un peu plus délicat que ce dont nous avions besoin.

Cependant, avec des questions comme celles-ci, nous pouvons de nos jours heureusement regarder le code source dans GitHub, alors j'ai recherché ce que AddDbContext fait exactement. Et bien ... Ce n'est pas vraiment difficile. Ces méthodes d'extension "ajouter" (et "utiliser") ne sont rien de plus que des méthodes pratiques, souvenez-vous-en. Vous devez donc ajouter tous les services offerts par AddDbContext, ainsi que les options. Peut-être que vous pouvez même réutiliser la méthode d'extension AddDbContext, ajoutez simplement votre propre surcharge avec une fabrique d'implémentation.

Donc, pour revenir à votre question. AddDbContext fait des choses spécifiques à EF. Comme vous pouvez le voir, ils vous permettront de passer toute une vie dans une version ultérieure (transitoire, singleton). AddTransient est Asp.Net Core qui vous permet d'ajouter tout service dont vous avez besoin. Et vous avez besoin d'une usine d'implémentation.

Est-ce que cela le rend plus clair?

10

Cela suffit si vous souhaitez choisir une chaîne de connexion par requête http, en fonction des paramètres de la requête http active.

    using Microsoft.AspNetCore.Http;

    //..

    services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

    services.AddDbContext<ERPContext>((serviceProvider, options) =>
        {
            var httpContext = serviceProvider.GetService<IHttpContextAccessor>().HttpContext;
            var httpRequest = httpContext.Request;
            var connection = GetConnection(httpRequest);
            options.UseSqlServer(connection);
        });

Cependant, vous n'avez pas facilement accès à l'utilisateur ni à ses réclamations. Sauf si vous l'avez fait manuellement.

Comment extraire et obtenir une réclamation à partir d'un jeton?

12
ginalx

J'ai pu changer la chaîne de connexion pour chaque demande en déplaçant la logique de la chaîne de connexion dans la méthode OnConfiguring du DbContext.

Dans la méthode Startup.cs#ConfigureServices: services.AddDbContext<MyDbContext>();

Dans MyDbContext.cs, j'ai ajouté les services dont j'avais besoin injectés au constructeur.

    private IConfigurationRoot _config;
    private HttpContext _httpContext;

    public MyDbContext(DbContextOptions options, IConfigurationRoot config, IHttpContextAccessor httpContextAccessor) 
          : base(options)
    {
        _config = config;
        _httpContext = httpContextAccessor.HttpContext;
    }

Remplacez ensuite OnConfiguring:

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var connString = BuildConnectionString(); // Your connection string logic here

        optionsBuilder.UseSqlServer(connString);
    }
11
jhr

Les réponses de @ginalx et @jcmordan correspondent parfaitement à mon cas d'utilisation. Ce que j'aime dans ces réponses, c'est que je peux tout faire dans Startup.cs et garder toutes les autres classes propres du code de construction. Je veux fournir un paramètre de chaîne de requête facultatif à une demande Web Api et le faire substituer dans la chaîne de connexion de base qui crée le DbContext. Je garde la chaîne de base dans le fichier appsettings.json et je la formate en fonction du paramètre passé ou d'une valeur par défaut si aucune n'est fournie, à savoir:

"IbmDb2Formatted": "DATABASE={0};SERVER=servername;UID=userId;PWD=password"

La méthode finale ConfigureServices pour moi ressemble à (obvs. Je me connecte à DB2 pas SQL, mais c'est accessoire):

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();

        services.AddDbContext<Db2Context>(((serviceProvider, options) =>
        {
            var httpContext = serviceProvider.GetService<IHttpContextAccessor>().HttpContext;
            var httpRequest = httpContext.Request;

            // Get the 'database' querystring parameter from the request (if supplied - default is empty).
           // TODO: Swap this out for an enum.
            var databaseQuerystringParameter = httpRequest.Query["database"].ToString();

            // Get the base, formatted connection string with the 'DATABASE' paramter missing.
            var db2ConnectionString = Configuration.GetConnectionString("IbmDb2Formatted");

            if (!databaseQuerystringParameter.IsNullOrEmpty())
            {
                // We have a 'database' param, stick it in.
                db2ConnectionString = string.Format(db2ConnectionString, databaseQuerystringParameter);
            }
            else
            {
                // We havent been given a 'database' param, use the default.
                var db2DefaultDatabaseValue = Configuration.GetConnectionString("IbmDb2DefaultDatabaseValue");
                db2ConnectionString = string.Format(db2ConnectionString, db2DefaultDatabaseValue);
            }

            // Build the EF DbContext using the built conn string.
            options.UseDb2(db2ConnectionString, p => p.SetServerInfo(IBMDBServerType.OS390));
        }));

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new Info
            {
                Title = "DB2 API",
                Version = "v1"
            });
        });
    }
4
Ciaran

Cela fonctionne pour moi:

public void ConfigureServices(IServiceCollection services)
{
    // .....
    services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    services.AddTransient<School360DbContext>(provider =>
    {
        return ResolveDbContext(provider, hostingEnv);
    });
    // ..
}

private MyDbContext ResolveDbContext(IServiceProvider provider, IHostingEnvironment hostingEnv)
{
    string connectionString = Configuration.GetConnectionString("DefaultConnection");

    string SOME_DB_IDENTIFYER = httpContextAccessor.HttpContext.User.Claims
        .Where(c => c.Type == "[SOME_DB_IDENTIFYER]").Select(c => c.Value).FirstOrDefault();
    if (!string.IsNullOrWhiteSpace(SOME_DB_IDENTIFYER))
    {
        connectionString = connectionString.Replace("[DB_NAME]", $"{SOME_DB_IDENTIFYER}Db");
    }

    var dbContext = new DefaultDbContextFactory().CreateDbContext(connectionString);

    // ....
    return dbContext;
}
2
jcmordan

Je sais que d'autres personnes ont déjà répondu. Mais je voudrais partager mon approche pour les personnes qui souhaitent modifier la chaîne de connexion DB lors de l'exécution.

Mon application a été construite avec asp.net core 2.2 avec Entity Framework et MySql .

StartUp.cs

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddDbContext<MyDbContext>();

    ...

Classe MyDbContext

public partial class MyDbContext : DbContext
{
    public MyDbContext()
    {
    }

    public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
    {
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (DbManager.DbName != null && !optionsBuilder.IsConfigured)
        {
            var dbName = DbManager.DbName;
            var dbConnectionString = DbManager.GetDbConnectionString(dbName);
            optionsBuilder.UseMySql(dbConnectionString);
        }
    }

    ...

Json - Fichier contenant des informations de connexion

[
  {
    "name": "DB1",
    "dbconnection": "server=localhost;port=3306;user=username;password=password;database=dbname1"
  },
  {
    "name": "DB2",
    "dbconnection": "server=localhost;port=3306;user=username;password=password;database=dbname2"
  }
]

Classe DbConnection

using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;


public class DbConnection
{
    [JsonProperty("name")]
    public string Name { get; set; }

    [JsonProperty("dbconnection")]
    public string Dbconnection { get; set; }

    public static List<DbConnection> FromJson(string json) => JsonConvert.DeserializeObject<List<DbConnection>>(json, Converter.Settings);
}

    internal static class Converter
    {
        public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
        {
            MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
            DateParseHandling = DateParseHandling.None,
            Converters =
            {
                new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
            },
        };
    }
}

Classe DbConnectionManager

public static class DbConnectionManager
{
    public static List<DbConnection> GetAllConnections()
    {
        List<DbConnection> result;
        using (StreamReader r = new StreamReader("myjsonfile.json"))
        {
            string json = r.ReadToEnd();
            result = DbConnection.FromJson(json);
        }
        return result;
    }

    public static string GetConnectionString(string dbName)
    {
        return GetAllConnections().FirstOrDefault(c => c.Name == dbName)?.Dbconnection;
    }
}

Classe DbManager

public static class DbManager
{
    public static string DbName;

    public static string GetDbConnectionString(string dbName)
    {
        return DbConnectionManager.GetConnectionString()dbName;
    }
}

Ensuite, vous auriez besoin d'un contrôleur qui définisse dbName up.

Classe de contrôleur

[Route("dbselect/{dbName}")]
public IActionResult DbSelect(string dbName)
{
    // Set DbName for DbManager.
    DbManager.DbName = dbName;

    dynamic myDynamic = new System.Dynamic.ExpandoObject();
    myDynamic.DbName = dbName;
    var json = JsonConvert.SerializeObject(myDynamic);
    return Content(json, "application/json");
}

Vous devrez peut-être faire quelque chose de truc ici et là. mais vous obtiendrez l'idée. Au début de l'application, il n'a pas de détails de connexion. vous devez donc le configurer explicitement à l'aide de Controller. J'espère que cela aidera quelqu'un.

1
Jin Lim

Startup.cs pour une connexion statique

services.AddScoped<MyContext>(_ => new MyContext(Configuration.GetConnectionString("myDB")));

Repository.cs pour une connexion dynamique

using (var _context = new MyContext(@"server=....){
context.Table1....
}

Table1MyContext.cs

public MyContext(string connectionString) : base(GetOptions(connectionString))
{
}

private static DbContextOptions GetOptions(string connectionString)
{
    return SqlServerDbContextOptionsExtensions.UseSqlServer(new DbContextOptionsBuilder(), connectionString).Options;
}
0
Lakmal