web-dev-qa-db-fra.com

ASP.NET Core change la chaîne de connexion EF lorsque l'utilisateur se connecte

Après quelques heures de recherche et sans moyen de le faire; il est temps de poser la question.

J'ai un projet ASP.NET Core 1.1 utilisant EF Core et MVC qui est utilisé par plusieurs clients. Chaque client a sa propre base de données avec exactement le même schéma. Ce projet est actuellement une application Windows en cours de migration vers le Web. Sur l'écran de connexion, l'utilisateur a trois champs, Code d'entreprise, Nom d'utilisateur et Mot de passe. Je dois être en mesure de modifier la chaîne de connexion lorsque l'utilisateur tente de se connecter en fonction de ce qu'il saisit dans le code d'entreprise, puis de se souvenir de son entrée pendant toute la durée de la session.

J'ai trouvé des moyens de le faire avec une base de données et plusieurs schémas, mais aucun avec plusieurs bases de données utilisant le même schéma.

La façon dont j'ai résolu ce problème n'est pas une solution réelle au problème, mais un travail qui a fonctionné pour moi. Mes bases de données et mon application sont hébergées sur Azure. Ma solution a été de mettre à niveau mon service d'application vers un plan qui prend en charge les machines à sous (seulement 20 $ de plus par mois pour 5 machines à sous). Chaque emplacement a le même programme mais la variable d'environnement qui contient la chaîne de connexion est propre à l'entreprise. De cette façon, je peux également accéder à chaque entreprise si je le souhaite. Bien que cette approche ne soit peut-être pas ce que les autres feraient, elle était la plus rentable pour moi. Il est plus facile de publier sur chaque emplacement que de passer des heures à faire l'autre programmation qui ne fonctionne pas correctement. Jusqu'à ce que Microsoft facilite la modification de la chaîne de connexion, c'est ma solution.

En réponse à la réponse d'Herzl

Cela semble pouvoir fonctionner. J'ai essayé de le mettre en œuvre. Une chose que je fais cependant est d'utiliser une classe de référentiel qui accède à mon contexte. Mes contrôleurs obtiennent le référentiel qui leur est injecté pour appeler des méthodes dans le référentiel qui accèdent au contexte. Comment faire cela dans une classe de référentiel. Il n'y a pas de surcharge OnActionExecuting dans mon référentiel. De plus, si cela persiste pendant la session, que se passe-t-il lorsqu'un utilisateur ouvre de nouveau son navigateur à l'application et est toujours connecté avec un cookie qui dure 7 jours? N'est-ce pas une nouvelle session? On dirait que l'application lèverait une exception car la variable de session serait nulle et n'aurait donc pas de chaîne de connexion complète. Je suppose que je pourrais également le stocker en tant que revendication et utiliser la revendication si la variable de session est nulle.

Voici ma classe de référentiel. IDbContextService était ProgramContext mais j'ai commencé à ajouter vos suggestions pour essayer de le faire fonctionner.

public class ProjectRepository : IProjectRepository
{
    private IDbContextService _context;
    private ILogger<ProjectRepository> _logger;
    private UserManager<ApplicationUser> _userManager;

    public ProjectRepository(IDbContextService context,
                            ILogger<ProjectRepository> logger,
                            UserManager<ApplicationUser> userManger)
    {
        _context = context;
        _logger = logger;
        _userManager = userManger;
    }

    public async Task<bool> SaveChangesAsync()
    {
        return (await _context.SaveChangesAsync()) > 0;
    }
}

En réponse à la réponse de The FORCE JB

J'ai essayé de mettre en œuvre votre approche. Je reçois une exception dans Program.cs en ligne

Host.Run();

Voici ma classe 'Program.cs'. Intacte.

using System.IO;
using Microsoft.AspNetCore.Hosting;

namespace Project
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var Host = new WebHostBuilder()
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseIISIntegration()
                .UseStartup<Startup>()
                .Build();

            Host.Run();
        }
    }
}

Et ma classe 'Startup.cs'.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using Project.Entities;
using Project.Services;

namespace Project
{
    public class Startup
    {
        private IConfigurationRoot _config;

        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json")
                .AddEnvironmentVariables();

            _config = builder.Build();
        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton(_config);
            services.AddIdentity<ApplicationUser, IdentityRole>(config =>
            {
                config.User.RequireUniqueEmail = true;
                config.Password.RequireDigit = true;
                config.Password.RequireLowercase = true;
                config.Password.RequireUppercase = true;
                config.Password.RequireNonAlphanumeric = false;
                config.Password.RequiredLength = 8;
                config.Cookies.ApplicationCookie.LoginPath = "/Auth/Login";
                config.Cookies.ApplicationCookie.ExpireTimeSpan = new TimeSpan(7, 0, 0, 0); // Cookies last 7 days
            })
            .AddEntityFrameworkStores<ProjectContext>();
            services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, AppClaimsPrincipalFactory>();
            services.AddScoped<IProjectRepository, ProjectRepository>();
            services.AddTransient<MiscService>();
            services.AddLogging();
            services.AddMvc()
            .AddJsonOptions(config =>
            {
                config.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            });
        }

        public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
        {
            Dictionary<string, string> connStrs = new Dictionary<string, string>();
            connStrs.Add("company1", "1stconnectionstring"));
            connStrs.Add("company2", "2ndconnectionstring";
            DbContextFactory.SetDConnectionString(connStrs);
            //app.UseDefaultFiles();

            app.UseStaticFiles();
            app.UseIdentity();
            app.UseMvc(config =>
            {
                config.MapRoute(
                    name: "Default",
                    template: "{controller}/{action}/{id?}",
                    defaults: new { controller = "Auth", action = "Login" }
                    );
            });
        }
    }
}

Et l'exception:

InvalidOperationException: Unable to resolve service for type 'Project.Entities.ProjectContext' while attempting to activate 'Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`4[Project.Entities.ApplicationUser,Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole,Project.Entities.ProjectContext,System.String]'.

Je ne sais pas quoi faire ici.

Modification partielle du succès

D'accord, votre exemple fonctionne. Je peux définir la chaîne de connexion dans mon constructeur de référentiel en utilisant un identifiant différent. Mon problème est maintenant de me connecter et de choisir la bonne base de données. J'ai pensé à faire extraire le référentiel d'une session ou d'une revendication, tout ce qui n'était pas nul. Mais je ne peux pas définir la valeur avant d'utiliser le SignInManager dans le contrôleur de connexion car SignInManager est injecté dans le contrôleur qui crée un contexte avant de mettre à jour la variable de session. La seule façon dont je peux penser est d'avoir une connexion de deux pages. La première page demandera le code de l'entreprise et mettra à jour la variable de session. La deuxième page utilisera le SignInManager et injectera le référentiel dans le constructeur des contrôleurs. Cela se produirait après la première page met à jour la variable de session. Cela peut en fait être plus attrayant visuellement avec des animations entre les deux vues de connexion. Sauf si quelqu'un a des idées sur la façon de le faire sans deux vues de connexion, je vais essayer d'implémenter la connexion de deux pages et publier le code si cela fonctionne.

Il est en fait cassé

Quand cela fonctionnait, c'est parce que j'avais toujours un cookie valide. Je lancerais le projet et il sauterait la connexion. Maintenant, je reçois l'exception InvalidOperationException: No database provider has been configured for this DbContext après avoir vidé mon cache. J'ai parcouru tout cela et le contexte est créé correctement. Je suppose que l'identité a des problèmes. Le code ci-dessous, ajoutant les magasins de structure d'entité dans ConfigureServices, pourrait-il être à l'origine du problème?

services.AddIdentity<ApplicationUser, IdentityRole>(config =>
{
    config.User.RequireUniqueEmail = true;
    config.Password.RequireDigit = true;
    config.Password.RequireLowercase = true;
    config.Password.RequireUppercase = true;
    config.Password.RequireNonAlphanumeric = false;
    config.Password.RequiredLength = 8;
    config.Cookies.ApplicationCookie.LoginPath = "/Company/Login";
    config.Cookies.ApplicationCookie.ExpireTimeSpan = new TimeSpan(7, 0, 0, 0); // Cookies last 7 days
})
.AddEntityFrameworkStores<ProgramContext>();

Modifier

J'ai vérifié que Identity est le problème. J'ai extrait des données de mon référentiel avant d'exécuter PasswordSignInAsync et cela a très bien tiré les données. Comment le DbContext est-il créé pour l'identité?

10
ozziwald

Créer une fabrique DbContext

public static class DbContextFactory
{
    public static Dictionary<string, string> ConnectionStrings { get; set; }

    public static void SetConnectionString(Dictionary<string, string> connStrs)
    {
        ConnectionStrings = connStrs;
    }

    public static MyDbContext Create(string connid)
    {
        if (!string.IsNullOrEmpty(connid))
        {
            var connStr = ConnectionStrings[connid];
            var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
            optionsBuilder.UseSqlServer(connStr);
            return new MyDbContext(optionsBuilder.Options);
        }
        else
        {
            throw new ArgumentNullException("ConnectionId");
        }
    }
}

Intialiser la fabrique DbContext

Dans startup.cs

public void Configure()
{
  Dictionary<string, string> connStrs = new Dictionary<string, string>();
  connStrs.Add("DB1", Configuration["Data:DB1Connection:ConnectionString"]);
  connStrs.Add("DB2", Configuration["Data:DB2Connection:ConnectionString"]);
  DbContextFactory.SetConnectionString(connStrs);
}

Usage

var dbContext= DbContextFactory.Create("DB1");
19
The FORCE JB

Selon votre question, je vais fournir une solution en supposant certaines choses:

Tout d'abord, j'ai créé trois bases de données dans mon instance SQL Server locale:

create database CompanyFoo
go

create database CompanyBar
go

create database CompanyZaz
go

Ensuite, je vais créer une table avec une ligne dans chaque base de données:

use CompanyFoo
go

drop table ConfigurationValue
go

create table ConfigurationValue
(
    Id int not null identity(1, 1),
    Name varchar(255) not null,
    [Desc] varchar(max) not null
)
go

insert into ConfigurationValue values ('Company name', 'Foo Company')
go

use CompanyBar
go

drop table ConfigurationValue
go

create table ConfigurationValue
(
    Id int not null identity(1, 1),
    Name varchar(255) not null,
    [Desc] varchar(max) not null
)
go

insert into ConfigurationValue values ('Company name', 'Bar Company')
go

use CompanyZaz
go

drop table ConfigurationValue
go

create table ConfigurationValue
(
    Id int not null identity(1, 1),
    Name varchar(255) not null,
    [Desc] varchar(max) not null
)
go

insert into ConfigurationValue values ('Company name', 'Zaz Company')
go

L'étape suivante consiste à créer un utilisateur avec l'authentification SQL et à autoriser l'accès à la lecture des bases de données, dans mon cas, mon nom d'utilisateur est johnd et mon mot de passe est 123.

Une fois ces étapes terminées, nous procédons à la création d'une application MVC dans ASP.NET Core, j'ai utilisé MultipleCompany comme nom de projet, j'ai deux contrôleurs: Accueil et Administration, l'objectif est d'afficher d'abord une vue de connexion puis de rediriger vers une autre vue pour afficher les données selon la base de données sélectionnée dans la vue "connexion".

Pour accomplir votre exigence, vous devrez utiliser la session sur l'application ASP.NET Core, vous pouvez changer cette façon de stockage et lire les données plus tard, pour l'instant, c'est uniquement pour le test de concept.

Code HomeController:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using MultipleCompany.Models;

namespace MultipleCompany.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public IActionResult Index(LoginModel model)
        {
            HttpContext.Session.SetString("CompanyCode", model.CompanyCode);
            HttpContext.Session.SetString("UserName", model.UserName);
            HttpContext.Session.SetString("Password", model.Password);

            return RedirectToAction("Index", "Administration");
        }

        public IActionResult Error()
        {
            return View();
        }
    }
}

Code du contrôleur d'administration:

using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using MultipleCompany.Models;
using MultipleCompany.Services;

namespace MultipleCompany.Controllers
{
    public class AdministrationController : Controller
    {
        protected IDbContextService DbContextService;
        protected CompanyDbContext DbContext;

        public AdministrationController(IDbContextService dbContextService)
        {
            DbContextService = dbContextService;
        }

        public override void OnActionExecuting(ActionExecutingContext context)
        {
            DbContext = DbContextService.CreateCompanyDbContext(HttpContext.Session.CreateLoginModelFromSession());

            base.OnActionExecuting(context);
        }

        public IActionResult Index()
        {
            var model = DbContext.ConfigurationValue.ToList();

            return View(model);
        }
    }
}

Code pour la vue d'accueil:

@{
    ViewData["Title"] = "Home Page";
}

<form action="/home" method="post">
    <fieldset>
        <legend>Log in</legend>

        <div>
            <label for="CompanyCode">Company code</label>
            <select name="CompanyCode">
                <option value="CompanyFoo">Foo</option>
                <option value="CompanyBar">Bar</option>
                <option value="CompanyZaz">Zaz</option>
            </select>
        </div>

        <div>
            <label for="UserName">User name</label>
            <input type="text" name="UserName" />
        </div>

        <div>
            <label for="Password">Password</label>
            <input type="password" name="Password" />
        </div>

        <button type="submit">Log in</button>
    </fieldset>
</form>

Code pour la vue Administration:

@{
    ViewData["Title"] = "Home Page";
}

<h1>Welcome!</h1>

<table class="table">
    <tr>
        <th>Name</th>
        <th>Desc</th>
    </tr>
    @foreach (var item in Model)
    {
        <tr>
            <td>@item.Name</td>
            <td>@item.Desc</td>
        </tr>
    }
</table>

Code de modèle:

using System;
using Microsoft.AspNetCore.Http;

namespace MultipleCompany.Models
{
    public class LoginModel
    {
        public String CompanyCode { get; set; }

        public String UserName { get; set; }

        public String Password { get; set; }
    }

    public static class LoginModelExtensions
    {
        public static LoginModel CreateLoginModelFromSession(this ISession session)
        {
            var companyCode = session.GetString("CompanyCode");
            var userName = session.GetString("UserName");
            var password = session.GetString("Password");

            return new LoginModel
            {
                CompanyCode = companyCode,
                UserName = userName,
                Password = password
            };
        }
    }
}

Code CompanyDbContext:

using System;
using Microsoft.EntityFrameworkCore;

namespace MultipleCompany.Models
{
    public class CompanyDbContext : Microsoft.EntityFrameworkCore.DbContext
    {
        public CompanyDbContext(String connectionString)
        {
            ConnectionString = connectionString;
        }

        public String ConnectionString { get; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(ConnectionString);

            base.OnConfiguring(optionsBuilder);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
        }

        public DbSet<ConfigurationValue> ConfigurationValue { get; set; }
    }
}

Code de valeur de configuration:

using System;

namespace MultipleCompany.Models
{
    public class ConfigurationValue
    {
        public Int32? Id { get; set; }

        public String Name { get; set; }

        public String Desc { get; set; }
    }
}

Code AppSettings:

using System;

namespace MultipleCompany.Models
{
    public class AppSettings
    {
        public String CompanyConnectionString { get; set; }
    }
}

Code IDbContextService:

using MultipleCompany.Models;

namespace MultipleCompany.Services
{
    public interface IDbContextService
    {
        CompanyDbContext CreateCompanyDbContext(LoginModel model);
    }
}

Code DbContextService:

using System;
using Microsoft.Extensions.Options;
using MultipleCompany.Models;

namespace MultipleCompany.Services
{
    public class DbContextService : IDbContextService
    {
        public DbContextService(IOptions<AppSettings> appSettings)
        {
            ConnectionString = appSettings.Value.CompanyConnectionString;
        }

        public String ConnectionString { get; }

        public CompanyDbContext CreateCompanyDbContext(LoginModel model)
        {
            var connectionString = ConnectionString.Replace("{database}", model.CompanyCode).Replace("{user id}", model.UserName).Replace("{password}", model.Password);

            var dbContext = new CompanyDbContext(connectionString);

            return dbContext;
        }
    }
}

Code de démarrage:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using MultipleCompany.Models;
using MultipleCompany.Services;

namespace MultipleCompany
{
    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();
            Configuration = builder.Build();
        }

        public IConfigurationRoot Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddMvc();

            services.AddEntityFrameworkSqlServer().AddDbContext<CompanyDbContext>();

            services.AddScoped<IDbContextService, DbContextService>();

            services.AddDistributedMemoryCache();
            services.AddSession();

            services.AddOptions();

            services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));

            services.AddSingleton<IConfiguration>(Configuration);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();

            app.UseSession();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

J'ai ajouté ces packages pour mon projet:

"Microsoft.EntityFrameworkCore": "1.0.1",
"Microsoft.EntityFrameworkCore.SqlServer": "1.0.1",
"Microsoft.AspNetCore.Session":  "1.0.0"

Mon fichier appsettings.json:

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  },
  "AppSettings": {
    "CompanyConnectionString": "server=(local);database={database};user id={user id};password={password}"
  }
}

Veuillez vous concentrer sur le concept de connexion à la base de données sélectionnée dans la vue d'accueil, vous pouvez modifier n'importe quelle partie de ce code comme une amélioration, n'oubliez pas que je fournis cette solution en faisant des hypothèses en fonction de votre brève question, n'hésitez pas à demander sur tout aspect exposé dans cette solution pour améliorer ce morceau de code en fonction de vos besoins.

Fondamentalement, nous devons définir un service pour créer l'instance de contexte db en fonction de la base de données sélectionnée, c'est l'interface IDbContextService et DbContextService c'est l'implémentation de cette interface.

Comme vous pouvez le voir sur le code DbContextService, nous remplaçons les valeurs à l'intérieur de {} pour créer une chaîne de connexion différente, dans ce cas, j'ai ajouté les noms de la base de données dans la liste déroulante mais en développement réel, évitez de cette façon car pour des raisons de sécurité, c'est mieux pour ne pas exposer les vrais noms de vos bases de données et autres configurations; vous pouvez avoir une table de parité du côté du contrôleur pour résoudre le code d'entreprise selon la base de données sélectionnée.

Une amélioration pour cette solution, ce serait d'ajouter du code pour sérialiser le modèle de connexion en tant que json dans la session au lieu de stocker chaque valeur de manière distincte.

Veuillez me faire savoir si cette réponse est utile. PD: Faites-moi savoir dans les commentaires si vous voulez que le code complet soit téléchargé sur un seul lecteur

4
H. Herzl

Vous avez trouvé votre réponse, mais mon message peut peut-être être utile à quelqu'un. J'ai eu un problème similaire comme cette question. J'ai dû changer ma chaîne de connexion de structure d'entité pour connecter un serveur de base de données différent après la connexion de l'utilisateur. Et pour la solution, j'ai d'abord supprimé cette fonction de ma classe de contexte,

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. See http://go.Microsoft.com/fwlink/?LinkId=723263 for guidance on storing connection strings.
                optionsBuilder.UseSqlServer("your connectionstring...");
            }

parce que je ne pouvais pas appeler cette fonction de l'extérieur. Et j'avais ce constructeur généré automatiquement

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

Après la suppression, j'ai ajouté ce code à ma classe de contexte.

public ClientContext CreateConnectionFromOut(string connectionString)
{
    var optionsBuilder = new DbContextOptionsBuilder<Esdesk_ClientContext>();
    optionsBuilder.UseSqlServer(connectionString);
    var context = new ClientContext(optionsBuilder.Options);
    return context;
}

Maintenant, enfin, je peux changer ma chaîne de connexion où je veux. C'est comme ça,

   ClientContext cc = new ClientContext();
   var db = cc.CreateConnectionFromOut("your connection string");

J'espère que cela peut convenir à quelqu'un.

1
Emre SOLUĞAN

Puisque vous créez une application Web multi-locataire, vous devez d'abord décider comment allez-vous faire la distinction entre les locataires. Allez-vous utiliser une URL différente? ou peut-être la même URL mais en ajoutant une partie dans l'URL?

En supposant que vous avez choisi ce dernier, le locataire 1 aurait donc une URL similaire à celle-ci: http: // localhost: 9090/tenant1/Orders

Le locataire 2 aurait une URL comme: http: // localhost: 9090/tenant2/Orders

Vous pouvez le faire en utilisant le routage URL:

 routes.MapRoute(
                name: "Multitenant",
                url: "{tenant}/{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );

Quant à la chaîne de connexion, vous avez besoin d'une classe pour décider de la chaîne de connexion en fonction de l'URL et injecter cette classe dans le contexte de la base de données.

public interface ITenantIdentifier
{
 string GetCurrentTenantId();
}

public class UrlTenantIdentifier : ITenantIdentifier
{
  public string GetCurrentTenantId()
  { 
    //Get the current Http Context and get the URL, you should have a table or configration that maps the URL to the tenant ID and connection string
  }
}

Dans votre contexte DB:

public class MyDbContext: DbContext
{
 public MyDbContext(ITenantIdentifier tenantIdentifier)
 { 
   var connectionStringName = "TenantConnectionString"+tenantIdentifier.GetCurrentTenantId(); //here assuming that you are following a pattern, each tenant has a connection string in the shape of TenantConnectionString+ID

  var connectionString = //get connection string
  base(connectionString);
 }
}
1
Haitham Shaddad

Vous pouvez essayer ce qui suit lors de la création de votre instance de contexte:

// in class DBHelper
public static YourEntities GetDbContext(string tenantName) 
{
    var connectionStringTemplate =
      @"metadata=res://*/yourModel.csdl|res://*/yourModel.ssdl|res://*/yourModel.msl;" +
      @"provider=System.Data.SqlClient;" +
      @"provider connection string=""data source=.;" +
      @"initial catalog={0};" +
      @"user id=sa;password=pwd;" +
      @"MultipleActiveResultSets=True;App=EntityFramework"";";

     var connectionString = string.Format(connection, tenantName);
     var db = new YourEntities(connectionString);
     return db;
}

Créez ensuite un constructeur dans votre classe de contexte qui accepte la chaîne comme paramètre et utilisez-le comme:

var db = DBHelper.GetDbContext(name of database to connect);
0
Awais Mahmood

Mettre à jour pour transmettre la chaîne de connexion

Pour passer la connexion générée dynamiquement à votre contexte, créez une classe partielle dans le même contexte que votre classe partielle de contexte garantirait qu'elle reste intacte si quelqu'un a exécuté l'outil personnalisé (pour edmx), le code généré automatiquement sera effacé et régénéré . Si vous avez ce code dans une classe partielle, il ne sera pas effacé. Pour le code d'abord, cela ne s'appliquera pas. Voici le code:

public class YourContext : DbContext
{
    public YourContext(string connString)
    {

    }

}

La façon dont je l'ai fait dans le passé est d'avoir une base de données où les comptes (noms d'utilisateur, mots de passe) de tous les clients sont stockés. Le compte sous lequel l'application s'exécute serait utilisé pour communiquer avec cette base de données pour authentifier le client qui se connecte (CompanyID, Password).

Ensuite, une fois authentifié, un jeton est généré. Par la suite, l'utilisateur authentifié interagira avec la base de données (entreprise) de ce client. Pour cette partie, vous pouvez créer la connexion à la volée comme indiqué ici mais je vais aussi la copier et la coller:

// Specify the provider name, server and database.
string providerName = "System.Data.SqlClient";
string serverName = ".";
string databaseName = "AdventureWorks";

// Initialize the connection string builder for the
// underlying provider.
SqlConnectionStringBuilder sqlBuilder =
    new SqlConnectionStringBuilder();

// Set the properties for the data source.
sqlBuilder.DataSource = serverName;
sqlBuilder.InitialCatalog = databaseName;
sqlBuilder.IntegratedSecurity = true;

// Build the SqlConnection connection string.
string providerString = sqlBuilder.ToString();

// Initialize the EntityConnectionStringBuilder.
EntityConnectionStringBuilder entityBuilder =
    new EntityConnectionStringBuilder();

//Set the provider name.
entityBuilder.Provider = providerName;

// Set the provider-specific connection string.
entityBuilder.ProviderConnectionString = providerString;

// Set the Metadata location.
entityBuilder.Metadata = @"res://*/AdventureWorksModel.csdl|
                res://*/AdventureWorksModel.ssdl|
                res://*/AdventureWorksModel.msl";
Console.WriteLine(entityBuilder.ToString());

Vous devrez fournir vos propres noms csdl, ssdl et msl dans le code ci-dessus. Si vous utilisez Code First, votre chaîne de connexion n'aura pas besoin des métadonnées.

0
CodingYoshi