web-dev-qa-db-fra.com

Connexion à une base de données MySQL dynamique pour Entity Framework 6

Je souhaite passer une chaîne de connexion dynamique au contexte de structure d'entité. J'ai plus de 150 schémas identiques (un par compte) et je souhaite sélectionner la connexion en tant que telle:

ApplicationDbContext db = new ApplicationDbContext("dbName");

En théorie, ce serait assez facile, car je peux créer un connectionString et le passer comme argument pour le constructeur, par exemple:

public ApplicationDbContext(string dbName) : base(GetConnectionString(dbName))
{
}

public static string GetConnectionString(string dbName)
{
    // The connectionString passed is something like:
    // Server=localhost;Database={0};Uid=username;Pwd=password
    var connString =  ConfigurationManager
                         .ConnectionStrings["MyDatabase"]
                         .ConnectionString
                         .ToString();

    return String.Format(connString, dbName);
}

Je peux me connecter avec succès lorsque je passe simplement le nom de la chaîne de connexion, mais pas lorsque je le génère dynamiquement comme ci-dessous. Je me rends compte maintenant que c'est parce que la chaîne de connexion dans web.config a le providerName="MySql.Data.MySqlClient" attribut dedans.

Cependant, lorsque je passe dynamiquement la chaîne de connexion réelle à la connexion, il suppose qu'il doit se connecter à SQL Server plutôt qu'à MySQL et échoue en raison de la chaîne de connexion non valide.

La question est, comment puis-je transmettre le nom du fournisseur à la chaîne de connexion si je le crée dynamiquement?

22
francisco.preller

Entity Framework 6 propose quelques modifications subtiles pratiques qui aident à la fois à faire fonctionner MySQL et à créer des connexions de base de données dynamiques.

Faire fonctionner MySQL avec Entity Framework 6

Tout d'abord, à la date de ma réponse à cette question, le seul pilote de connecteur .Net compatible avec EF6 est le MySQL .Net Connectior 6.8.1 (version de développement bêta) qui peut être trouvé sur le site officiel de MySQL ici =.

Après l'installation, référencez les fichiers suivants à partir de votre solution Visual Studio:

  • Mysql.Data.dll
  • Mysql.Data.Entity.EF6.dll

Vous devrez également copier ces fichiers quelque part où ils seront accessibles au projet pendant la construction, comme le répertoire bin.

Ensuite, vous devez ajouter des éléments à votre fichier Web.config (ou App.config si sur un ordinateur de bureau).

Une chaîne de connexion:

<connectionStrings>
    <add name="mysqlCon"
         connectionString="Server=localhost;Database=dbName;Uid=username;Pwd=password" 
         providerName="MySql.Data.MySqlClient" />
</connectionStrings>

Ajoutez également le fournisseur, à l'intérieur du <entityFramework /> et <providers /> nœuds, en option (c'est un must absolu dans la deuxième partie de ma réponse, lorsque vous traitez avec des bases de données définies dynamiquement) vous pouvez changer le <defaultConnectionFactory /> noeud:

<entityFramework>
    <defaultConnectionFactory type="MySql.Data.Entity.MySqlConnectionFactory, MySql.Data.Entity.EF6" />
    <providers>
        <provider invariantName="MySql.Data.MySqlClient" type="MySql.Data.MySqlClient.MySqlProviderServices, MySql.Data.Entity.EF6" />
    </providers>
</entityFramework>

Si vous modifiez defaultConnectionFactory à partir de la connexion au serveur SQL par défaut, n'oubliez pas de supprimer le <parameter> nœuds imbriqués dans le nœud defaultConnectionFactory. MysqlConnectionFactory ne prend aucun paramètre pour son constructeur et échouera si les paramètres sont toujours là.

À ce stade, il est assez facile de se connecter à MySQL avec Entity, vous pouvez simplement vous référer à la connectionString ci-dessus par son nom. Notez que si vous vous connectez par nom, cela fonctionnera même si le nœud defaultConnectionFactory pointe toujours vers SQL Server (ce qu'il fait par défaut).

public class ApplicationDbContext: DbContext
{
    public ApplicationDbContext() : base("mysqlCon")
    {
    }
}

Il s'agit simplement de se connecter normalement:

ApplicationDbContext db = ApplicationDbContext();

Connexion à un nom de base de données sélectionné dynamiquement

À ce stade, il est facile de se connecter à une base de données que nous pouvons transmettre en tant que paramètre, mais il y a quelques choses que nous devons faire.

Note importante

Si vous ne l'avez pas déjà fait, vous DEVEZ changer le defaultConnectionFactory dans Web.config si vous souhaitez vous connecter dynamiquement à MySQL. Comme nous transmettrons une chaîne de connexion directement au constructeur de contexte, il ne saura pas quel fournisseur utiliser et se tournera vers sa fabrique de connexions par défaut, sauf indication contraire dans web.config. Voir ci-dessus sur la façon de procéder.

Vous pouvez passer une chaîne de connexion manuellement au contexte comme ceci:

public ApplicationDbContext() : base("Server:localhost;...")
{
}

Mais pour le rendre un peu plus facile, nous pouvons apporter une petite modification à la chaîne de connexion que nous avons faite ci-dessus lors de la configuration de mySQL. Ajoutez simplement un espace réservé comme indiqué ci-dessous:

<add name="mysqlCon" connectionString="Server=localhost;Database={0};Uid=username;Pwd=password" providerName="MySql.Data.MySqlClient" />

Nous pouvons maintenant créer une méthode d'assistance et modifier la classe ApplicationDbContext comme indiqué ci-dessous:

public class ApplicationDbContext: DbContext
{
    public ApplicationDbContext(string dbName) : base(GetConnectionString(dbName))
    {
    }

    public static string GetConnectionString(string dbName)
    {
        // Server=localhost;Database={0};Uid=username;Pwd=password
        var connString = 
            ConfigurationManager.ConnectionStrings["mysqlCon"].ConnectionString.ToString();

        return String.Format(connString, dbName);
    }
}

Si vous utilisez des migrations de base de données, l'étape suivante est importante

Si vous utilisez des migrations, vous constaterez que le ApplicationDbContext sera transmis à votre méthode Seed par le framework et il échouera car il ne transmettra pas le paramètre que nous avons mis pour la base de données Nom.

Ajoutez la classe suivante au bas de votre classe de contexte (ou n'importe où vraiment) pour résoudre ce problème.

public class MigrationsContextFactory : IDbContextFactory<ApplicationDbContext>
{
    public ApplicationDbContext Create()
    {
        return new ApplicationDbContext("developmentdb");
    }
}

Vos migrations et méthodes initiales de code en premier cibleront désormais le schéma developmentdb dans votre base de données MySQL.

J'espère que cela aide quelqu'un :)

54
francisco.preller

C'est maintenant 2019, bien sûr, les choses ont un peu changé, mais l'exemple de Franciso m'a vraiment aidé à ce sujet. C'est la solution la plus simple que j'ai pu trouver et la seule qui a réellement fonctionné. Je l'ai un peu changé par rapport à ce qu'il a montré. Suivez ceci jusqu'à la fin, vous devriez vous retrouver avec une solution de travail.

J'ai dû changer quelques choses. Je vais être très explicite dans ce qui doit être fait et je vais utiliser mes noms de fichiers réels, etc. afin que vous n'ayez pas à deviner les substitutions. De nombreux exemples sont également courts sur la façon de le faire fonctionner à la fin. Cet exemple contient tout ce que vous devez savoir.

Ceci a été construit sur Visual Studio 2015 Entityframework 6 en utilisant le serveur MySql 8.0.16.0.
Malheureusement, les connecteurs et les bibliothèques MySql sont un gâchis complet. Le connecteur/net 8.0.xx.0 et MySql.Data.Entity.EF6 et MySql.Data sont complètement inutiles. J'ai installé Connector Net 6.10.7.0, MySql.Data.Entity.EF6 6.10.7.0 et MySql.Data 6.10.7.0. Cela fonctionne pour moi et je m'opposerai vigoureusement à ce changement.

C'est pour MySql mais je ne sais vraiment pas pourquoi cela ne pourrait pas fonctionner pour n'importe quel db.

Scénario

J'ai une situation multi-locataire où j'ai une base de données commune et plusieurs bases de données permanentes, une par client. L'identifiant client est conservé dans la base de données commune à des fins de connexion et d'autorisation et l'identifiant client indique la base de données à utiliser. Les bases de données client sont toutes appelées myclientdb_x où x est le numéro de client. myclientdb_1, myclientdb_2, myclientdb_35 et ainsi de suite.

J'ai besoin de passer dynamiquement à n'importe quel clientdb_x que le code sert actuellement. Il existe un client de base de données initial appelé myclient_0 qui est le modèle pour toutes les autres bases de données myclient_x.

Étape 1

J'ai créé une chaîne de connexion spécifique dans mon Web.config pour cela, cela ressemble à ceci. Il permet des connexions au clientdb_0

<add name="DefaultClientConnection" providerName="MySql.Data.MySqlClient" 
    connectionString="server=localhost;user id=xxx;
     password=xxxx; persistsecurityinfo=True;database=clientdb_0" />

Étape 2

J'ai créé une nouvelle entité appelée ClientDbUserUpdater à l'aide de l'assistant. L'entité de données est appelée

ClientDbUserUpdater.edmx

Je lui ai dit d'utiliser "DefaultClientConnection" comme connexion DB Je lui ai dit d'enregistrer cette nouvelle chaîne de connexion dans Web.config

Cela a créé une nouvelle chaîne de connexion d'entité dans le fichier Web.config et elle ressemblera à

<add name="myclient_0Entities" connectionString="metadata=
    res://*/Areas.Authorizations.Models.ClientDbUserUpdater.csdl|
    res://*/Areas.Authorizations.Models.ClientDbUserUpdater.ssdl|
    res://*/Areas.Authorizations.Models.ClientDbUserUpdater.msl;
     provider=MySql.Data.MySqlClient;provider connection string=&quot;
      server=localhost;user id=xxxx;password=yyyyy;
     persistsecurityinfo=True;database=myclient_0&quot;" providerName="System.Data.EntityClient" />

Vous devrez peut-être creuser un peu, car l'assistant ne sait pas mettre\n aux endroits appropriés.

Notez que cette chaîne de connexion est fondamentalement la même que la chaîne de connexion initiale à l'exception de son nom et du fait qu'elle a

    res://*/Areas.Authorizations.Models.ClientDbUserUpdater.csdl|
    res://*/Areas.Authorizations.Models.ClientDbUserUpdater.ssdl|
    res://*/Areas.Authorizations.Models.ClientDbUserUpdater.msl;

Les chaînes res: sont nécessaires à l'entité de données et c'est pourquoi vous ne pouvez pas simplement envoyer une chaîne de connexion standard dans l'entité de données.

Si vous essayez d'envoyer la chaîne de connexion initiale

 <add name="DefaultClientConnection" providerName="MySql.Data.MySqlClient" 
        connectionString="server=localhost;user id=xxx;
         password=xxxx; persistsecurityinfo=True;database=clientdb_0" />

vous obtiendrez une exception de

      protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            throw new UnintentionalCodeFirstException();
        }

Étape

Cette nouvelle chaîne de connexion est celle que vous devez modifier. Je ne l'ai pas testé mais je suis sûr que si vous modifiez le modèle d'entité de données avec l'assistant, vous devrez effectuer à nouveau ce changement. Prenez la chaîne:

<add name="myclient_0Entities" connectionString="metadata=
    res://*/Areas.Authorizations.Models.ClientDbUserUpdater.csdl|
    res://*/Areas.Authorizations.Models.ClientDbUserUpdater.ssdl|
    res://*/Areas.Authorizations.Models.ClientDbUserUpdater.msl;
     provider=MySql.Data.MySqlClient;provider connection string=&quot;
      server=localhost;user id=xxxx;password=yyyyy;
     persistsecurityinfo=True;database=myclient_0&quot;" providerName="System.Data.EntityClient" />

et changez-le en:

<add name="myclient_0Entities" connectionString="metadata=
    res://*/Areas.Authorizations.Models.ClientDbUserUpdater.csdl|
    res://*/Areas.Authorizations.Models.ClientDbUserUpdater.ssdl|
    res://*/Areas.Authorizations.Models.ClientDbUserUpdater.msl;
     provider=MySql.Data.MySqlClient;provider connection string=&quot;
      server=localhost;user id=xxxx;password=yyyyy;
     persistsecurityinfo=True;database={0}&quot;" providerName="System.Data.EntityClient" />

Notez que la seule partie modifiée est database = myclient_0 en database = {0}

Étape 4

L'entité de données a créé du code derrière ClientDbUserUpdater.edmx. Le fichier est appelé ClientDbUserUpdater.Context.cs.

Le code est ...

namespace what.ever.your.namespace.is
{
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;

    public partial class client_0Entities : DbContext
    {
        public client_0Entities()
            : base("name=client_0Entities")
        {
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            throw new UnintentionalCodeFirstException();
        }

        public virtual DbSet<user> users { get; set; }
    }
}

Notez que c'est une classe partielle. Cela signifie que vous pouvez étendre cette classe et ajouter un nouveau constructeur.

Ajoutez la classe suivante.

using System;
using System.Configuration ;
using System.Data.Entity ;

namespace what.ever.your.namespace.is
{  
  public partial class client_0Entities : DbContext
  {
    public client_0Entities(string dbName) : base(GetConnectionString(dbName))
    {
    }

    public static string GetConnectionString(string dbName)
    {       
       var connString = ConfigurationManager.ConnectionStrings["client_0Entities"].ConnectionString.ToString();
      // obviously the next 2 lines could be done as one but creating and 
      // filling a string is better for debugging.  You can see what happened 
      // by looking a  conn
      // return  String.Format(connString, dbName);
      string conn =  String.Format(connString, dbName);
      return conn ;
    }
  } 
}

La classe ajoute un nouveau constructeur qui vous permet d'obtenir la chaîne de connexion de base pour le modèle d'entité de données qui ci-dessus ressemble à:

<add name="myclient_0Entities" connectionString="metadata=
    res://*/Areas.Authorizations.Models.ClientDbUserUpdater.csdl|
    res://*/Areas.Authorizations.Models.ClientDbUserUpdater.ssdl|
    res://*/Areas.Authorizations.Models.ClientDbUserUpdater.msl;
     provider=MySql.Data.MySqlClient;provider connection string=&quot;
      server=localhost;user id=xxxx;password=yyyyy;
     persistsecurityinfo=True;database={0}&quot;" providerName="System.Data.EntityClient" />

et modifiez-le au moment de l'exécution pour changer le schéma.

L'appel String.Format () dans la nouvelle classe partielle remplace le nom du schéma de base de données dans cette chaîne de connexion au moment de l'exécution.

À ce stade, toute la configuration est terminée.

Étape 5

Maintenant, vous pouvez y aller. Pour mieux comprendre cet exemple, il est agréable de savoir à quoi ressemble le modèle pour cette entité. C'est très simple parce que je testais juste et essayais de le faire avancer.

En explorant ClientDbUserUpdater.edmx et en ClientDbUserUpdater.tt, vous trouverez votre modèle dans modelname.cs. Mon modèle est appelé "utilisateur" donc mon nom de fichier est appelé user.cs

namespace what.ever.your.namespace.is
{
    using System;
    using System.Collections.Generic;

    public partial class user
    {
        public int UserId { get; set; }
        public string Email { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public Nullable<bool> Active { get; set; }
    }
}

Maintenant, vous pouvez généralement accéder à votre modèle comme celui-ci.

 client_0Entities _client_0Entities = new client_0Entities("schemaName");

et ce code peut être n'importe où dans votre solution qui peut voir la classe client_0Entities.

qui en pratique est une ligne similaire à l'un des 3 ci-dessous qui sont respectivement connectés aux bases de données client_19, client_47 et client_68.

 client_0Entities _client_0Entities = new client_0Entities("client_19");
 client_0Entities _client_0Entities = new client_0Entities("client_47");
 client_0Entities _client_0Entities = new client_0Entities("client_68");

ce qui suit est un exemple de code réel qui fonctionne sur mon système. Évidemment, je ne vais pas coder en dur dans "client_19" mais c'est mieux à des fins de démonstration.

voici le code réel avec des noms réels qui fonctionne et ajoute une nouvelle ligne à la table utilisateur sur la base de données client_19

  string _newSchema = "client_19"
  using(client_0Entities _client_0Entities = new client_0Entities(_newSchema))
  {
     user _user = new user();
     _user.UserId = 201;
     _user.Email = "[email protected]"
     _user.FirstName ' "Someone"; 
     _user.LastName  = "New";
     _user.Active = true;

     client_0Entities.users.Add ( _user ) ;
     client_0Entities.SaveChangesAsync ( ) ;
  }

Espérons que cela aide certaines personnes. J'ai passé environ 20 heures à examiner différentes solutions qui ne fonctionnaient tout simplement pas ou qui ne fournissaient pas suffisamment d'informations pour les compléter. Comme je l'ai dit, trouver l'exemple de Franciso m'a permis de le faire fonctionner.

Cordialement,

0
BrownPony