web-dev-qa-db-fra.com

Entity Framework change de connexion au moment de l'exécution

J'ai un projet d'API Web qui référence mon modèle et mes assemblages DAL. L'utilisateur se voit présenter un écran de connexion, dans lequel il peut sélectionner différentes bases de données.

Je construis la chaîne de connexion comme suit:

    public void Connect(Database database)
    {
        //Build an SQL connection string
        SqlConnectionStringBuilder sqlString = new SqlConnectionStringBuilder()
        {
            DataSource = database.Server,
            InitialCatalog = database.Catalog,
            UserID = database.Username,
            Password = database.Password,
        };

        //Build an entity framework connection string
        EntityConnectionStringBuilder entityString = new EntityConnectionStringBuilder()
        {
            Provider = database.Provider,
            Metadata = Settings.Default.Metadata,
            ProviderConnectionString = sqlString.ToString()
        };
    }

Tout d’abord, comment puis-je changer la connexion du contexte de données?

Deuxièmement, s'agissant d'un projet d'API Web, la chaîne de connexion (définie lors de la connexion, comme indiqué ci-dessus) est-elle persistante tout au long de l'interaction de l'utilisateur ou doit-elle être passée à chaque fois à mon contexte de données?

68
Ivan-Mark Debono

Un peu en retard sur cette réponse, mais je pense qu’il existe un moyen possible de le faire avec une méthode d’extension très simple. Nous pouvons tirer parti de la convention EF sur la configuration et de quelques appels au framework.

Quoi qu'il en soit, le code commenté et l'exemple d'utilisation:

classe de méthode d'extension:

public static class ConnectionTools
{
    // all params are optional
    public static void ChangeDatabase(
        this DbContext source,
        string initialCatalog = "",
        string dataSource = "",
        string userId = "",
        string password = "",
        bool integratedSecuity = true,
        string configConnectionStringName = "") 
        /* this would be used if the
        *  connectionString name varied from 
        *  the base EF class name */
    {
        try
        {
            // use the const name if it's not null, otherwise
            // using the convention of connection string = EF contextname
            // grab the type name and we're done
            var configNameEf = string.IsNullOrEmpty(configConnectionStringName)
                ? source.GetType().Name 
                : configConnectionStringName;

            // add a reference to System.Configuration
            var entityCnxStringBuilder = new EntityConnectionStringBuilder
                (System.Configuration.ConfigurationManager
                    .ConnectionStrings[configNameEf].ConnectionString);

            // init the sqlbuilder with the full EF connectionstring cargo
            var sqlCnxStringBuilder = new SqlConnectionStringBuilder
                (entityCnxStringBuilder.ProviderConnectionString);

            // only populate parameters with values if added
            if (!string.IsNullOrEmpty(initialCatalog))
                sqlCnxStringBuilder.InitialCatalog = initialCatalog;
            if (!string.IsNullOrEmpty(dataSource))
                sqlCnxStringBuilder.DataSource = dataSource;
            if (!string.IsNullOrEmpty(userId))
                sqlCnxStringBuilder.UserID = userId;
            if (!string.IsNullOrEmpty(password))
                sqlCnxStringBuilder.Password = password;

            // set the integrated security status
            sqlCnxStringBuilder.IntegratedSecurity = integratedSecuity;

            // now flip the properties that were changed
            source.Database.Connection.ConnectionString 
                = sqlCnxStringBuilder.ConnectionString;
        }
        catch (Exception ex)
        {
            // set log item if required
        }
    }
}

utilisation de base:

// assumes a connectionString name in .config of MyDbEntities
var selectedDb = new MyDbEntities();
// so only reference the changed properties
// using the object parameters by name
selectedDb.ChangeDatabase
    (
        initialCatalog: "name-of-another-initialcatalog",
        userId: "jackthelady",
        password: "nomoresecrets",
        dataSource: @".\sqlexpress" // could be ip address 120.273.435.167 etc
    );

Je sais que vous avez déjà la fonctionnalité de base en place, mais je pense que cela ajouterait un peu de diversité.

100
jim tollan

DbContext a une surcharge de constructeur qui accepte le nom d'une chaîne de connexion ou une chaîne de connexion elle-même. Implémentez votre propre version et transmettez-la au constructeur de base:

public class MyDbContext : DbContext
{
    public MyDbContext( string nameOrConnectionString ) 
        : base( nameOrConnectionString )
    {
    }
}

Ensuite, transmettez simplement le nom d’une chaîne de connexion configurée ou une chaîne de connexion elle-même lorsque vous instanciez votre DbContext

var context = new MyDbContext( "..." );
54
Moho

La réponse de Jim Tollan fonctionne très bien, mais j'ai l'erreur: mot clé non pris en charge "source de données". Pour résoudre ce problème, je devais changer cette partie de son code:

// add a reference to System.Configuration
var entityCnxStringBuilder = new EntityConnectionStringBuilder
    (System.Configuration.ConfigurationManager
            .ConnectionStrings[configNameEf].ConnectionString);

pour ça:

// add a reference to System.Configuration
var entityCnxStringBuilder = new EntityConnectionStringBuilder
{
    ProviderConnectionString = new  SqlConnectionStringBuilder(System.Configuration.ConfigurationManager
               .ConnectionStrings[configNameEf].ConnectionString).ConnectionString
};

Je suis vraiment désolé. Je sais que je ne devrais pas utiliser les réponses pour répondre à d'autres réponses, mais ma réponse est trop longue pour un commentaire :(

11
A.Ima

La classe créée est "partielle"!

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

... et vous l'appelez comme ça:

using (var ctx = new Database1Entities1())
      {
        #if DEBUG
        ctx.Database.Log = Console.Write;
        #endif

vous devez donc uniquement créer un fichier partiel own class pour la classe originale générée automatiquement (avec le même nom de classe!) et ajouter un nouveau constructeur avec un paramètre de chaîne de connexion, comme celui de Moho avant.

Ensuite, vous pouvez utiliser le constructeur paramétré par rapport à l’original. :-)

exemple:

using (var ctx = new Database1Entities1(myOwnConnectionString))
      {
        #if DEBUG
        ctx.Database.Log = Console.Write;
        #endif
6
eMeL

Ajoutez plusieurs chaînes de connexion dans votre web.config ou app.config.

Ensuite, vous pouvez les obtenir sous forme de chaîne comme:

System.Configuration.ConfigurationManager.
    ConnectionStrings["entityFrameworkConnection"].ConnectionString;

Ensuite, utilisez la chaîne pour définir:

Provider
Metadata
ProviderConnectionString

C'est mieux expliqué ici:

Lire la chaîne de connexion depuis web.config

0
Bryan Arbelo

Dans mon cas, j'utilise ObjectContext par opposition à DbContext, j'ai donc modifié le code dans la réponse acceptée à cette fin.

public static class ConnectionTools
{
    public static void ChangeDatabase(
        this ObjectContext source,
        string initialCatalog = "",
        string dataSource = "",
        string userId = "",
        string password = "",
        bool integratedSecuity = true,
        string configConnectionStringName = "")
    {
        try
        {
            // use the const name if it's not null, otherwise
            // using the convention of connection string = EF contextname
            // grab the type name and we're done
            var configNameEf = string.IsNullOrEmpty(configConnectionStringName)
                ? Source.GetType().Name
                : configConnectionStringName;

            // add a reference to System.Configuration
            var entityCnxStringBuilder = new EntityConnectionStringBuilder
                (System.Configuration.ConfigurationManager
                    .ConnectionStrings[configNameEf].ConnectionString);

            // init the sqlbuilder with the full EF connectionstring cargo
            var sqlCnxStringBuilder = new SqlConnectionStringBuilder
                (entityCnxStringBuilder.ProviderConnectionString);

            // only populate parameters with values if added
            if (!string.IsNullOrEmpty(initialCatalog))
                sqlCnxStringBuilder.InitialCatalog = initialCatalog;
            if (!string.IsNullOrEmpty(dataSource))
                sqlCnxStringBuilder.DataSource = dataSource;
            if (!string.IsNullOrEmpty(userId))
                sqlCnxStringBuilder.UserID = userId;
            if (!string.IsNullOrEmpty(password))
                sqlCnxStringBuilder.Password = password;

            // set the integrated security status
            sqlCnxStringBuilder.IntegratedSecurity = integratedSecuity;

            // now flip the properties that were changed
            source.Connection.ConnectionString
                = sqlCnxStringBuilder.ConnectionString;
        }
        catch (Exception ex)
        {
            // set log item if required
        }
    }
}
0
David

Je voulais avoir plusieurs sources de données dans la configuration de l'application. Ainsi, après avoir configuré une section dans le fichier app.config, j'ai échangé la source de données, puis transférée dans le contexte db en tant que chaîne de connexion. 

//Get the key/value connection string from app config  
var sect = (NameValueCollection)ConfigurationManager.GetSection("section");  
var val = sect["New DataSource"].ToString();

//Get the original connection string with the full payload  
var entityCnxStringBuilder = new EntityConnectionStringBuilder(ConfigurationManager.ConnectionStrings["OriginalStringBuiltByADO.Net"].ConnectionString);     

//Swap out the provider specific connection string  
entityCnxStringBuilder.ProviderConnectionString = val;

//Return the payload with the change in connection string.   
return entityCnxStringBuilder.ConnectionString;

Cela m'a pris un peu pour comprendre. J'espère que ça aide quelqu'un. Je le rendais trop compliqué. avant ça. 

0
Jake Porter
string _connString = "metadata=res://*/Model.csdl|res://*/Model.ssdl|res://*/Model.msl;provider=System.Data.SqlClient;provider connection string="data source=localhost;initial catalog=DATABASE;persist security info=True;user id=sa;password=YourPassword;multipleactiveresultsets=True;App=EntityFramework"";

EntityConnectionStringBuilder ecsb = new EntityConnectionStringBuilder(_connString);
ctx = new Entities(_connString);

Vous pouvez obtenir la chaîne de connexion à partir de web.config et la définir simplement dans le constructeur EntityConnectionStringBuilder et utiliser EntityConnectionStringBuilder comme argument dans le constructeur pour le contexte.

Cache la chaîne de connexion par nom d'utilisateur. Exemple simple utilisant deux méthodes génériques pour gérer l’ajout/la récupération dans le cache. 

private static readonly ObjectCache cache = MemoryCache.Default;

// add to cache
AddToCache<string>(username, value);

// get from cache

 string value = GetFromCache<string>(username);
 if (value != null)
 {
     // got item, do something with it.
 }
 else
 {
    // item does not exist in cache.
 }


public void AddToCache<T>(string token, T item)
    {
        cache.Add(token, item, DateTime.Now.AddMinutes(1));
    }

public T GetFromCache<T>(string cacheKey) where T : class
    {
        try
        {
            return (T)cache[cacheKey];
        }
        catch
        {
            return null;
        }
    }
0
scheien

J'ai deux méthodes d'extension pour convertir la chaîne de connexion normale au format Entity Framework. Cette version fonctionne bien avec les projets de bibliothèque de classes sans copier les chaînes de connexion du fichier app.config dans le projet principal. Ceci est VB.Net mais facile à convertir en C #.

Public Module Extensions

    <Extension>
    Public Function ToEntityConnectionString(ByRef sqlClientConnStr As String, ByVal modelFileName As String, Optional ByVal multipleActiceResultSet As Boolean = True)
        Dim sqlb As New SqlConnectionStringBuilder(sqlClientConnStr)
        Return ToEntityConnectionString(sqlb, modelFileName, multipleActiceResultSet)
    End Function

    <Extension>
    Public Function ToEntityConnectionString(ByRef sqlClientConnStrBldr As SqlConnectionStringBuilder, ByVal modelFileName As String, Optional ByVal multipleActiceResultSet As Boolean = True)
        sqlClientConnStrBldr.MultipleActiveResultSets = multipleActiceResultSet
        sqlClientConnStrBldr.ApplicationName = "EntityFramework"

        Dim metaData As String = "metadata=res://*/{0}.csdl|res://*/{0}.ssdl|res://*/{0}.msl;provider=System.Data.SqlClient;provider connection string='{1}'"
        Return String.Format(metaData, modelFileName, sqlClientConnStrBldr.ConnectionString)
    End Function

End Module

Après cela, je crée une classe partielle pour DbContext:

Partial Public Class DlmsDataContext

    Public Shared Property ModelFileName As String = "AvrEntities" ' (AvrEntities.edmx)

    Public Sub New(ByVal avrConnectionString As String)
        MyBase.New(CStr(avrConnectionString.ToEntityConnectionString(ModelFileName, True)))
    End Sub

End Class

Créer une requête:

Dim newConnectionString As String = "Data Source=.\SQLEXPRESS;Initial Catalog=DB;Persist Security Info=True;User ID=sa;Password=pass"

Using ctx As New DlmsDataContext(newConnectionString)
    ' ...
    ctx.SaveChanges()
End Using
0
SZL