Entity Framework ändert die Verbindung zur Laufzeit

80

Ich habe ein Web-API-Projekt, das auf mein Modell und meine DAL-Assemblys verweist. Dem Benutzer wird ein Anmeldebildschirm angezeigt, auf dem er verschiedene Datenbanken auswählen kann.

Ich baue die Verbindungszeichenfolge wie folgt auf:

    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()
        };
    }

Wie ändere ich eigentlich die Verbindung des Datenkontexts?

Und zweitens, da es sich um ein Web-API-Projekt handelt, bleibt die Verbindungszeichenfolge (festgelegt bei Anmeldung gemäß oben) während der gesamten Benutzerinteraktion bestehen oder sollte sie jedes Mal an meinen Datenkontext übergeben werden?

Ivan-Mark Debono
quelle
Ich habe eine kleine Alternative hinzugefügt, falls sie in Ihre Denkweise / Toolbox-Anforderungen passt.
Jim Tollan
@ Ivan-Mark Wie haben Sie diesen Teil gelöst ? Und zweitens, da dies ein Web-API-Projekt ist, bleibt der Verbindungsstring (wie oben angegeben) während der gesamten Interaktion des Benutzers bestehen oder sollte er jedes Mal an meinen Datenkontext übergeben werden
Narendra Singh Rathore
@NarendraSinghRathore Die Verbindungszeichenfolgen werden in einer Konfigurationsdatei gespeichert, wobei der Datenbankname (oder etwas anderes) der Schlüssel ist. Der Benutzer wählt beim Anmelden eine Datenbank aus und diese wird in einem Cache gespeichert, in dem der Schlüssel möglicherweise der Benutzername ist. Der Benutzer stellt eine Anfrage, indem er seinen Benutzernamen als Header übergibt, und der Verbindungsstring wird abgerufen und an den Datenkontext übergeben.
Ivan-Mark Debono
@ Ivan-MarkDebono Kannst du diesen Cache erklären ? Verwenden Sie Memorycache oder Sitzung im Backend oder speichern Sie sie als Cookie im Frontend? Vielen Dank!
Narendra Singh Rathore
1
@ NarendraSinghRathore MemoryCache in einem Singleton
Ivan-Mark Debono

Antworten:

110

Ein bisschen spät bei dieser Antwort, aber ich denke, es gibt einen möglichen Weg, dies mit einer netten kleinen Erweiterungsmethode zu tun. Wir können die EF-Konvention gegenüber der Konfiguration sowie einige kleine Framework-Aufrufe nutzen.

Wie auch immer, der kommentierte Code und die Beispielverwendung:

Erweiterungsmethodenklasse:

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
        }
    }
}

Grundlegende Verwendung:

// 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
    );

Ich weiß, dass Sie bereits über die grundlegenden Funktionen verfügen, dachte aber, dies würde ein wenig Abwechslung bringen.

Jim Tollan
quelle
6
Das ist großartig, danke! Ich kann dies für ein mandantenfähiges Projekt zusammen mit einem erweiterten Projekt verwenden, bei dem Controllerdie 'Datenbank' des Controllers immer auf die kundenspezifische Datenbank eingestellt wird. Dies befreit mich (oder zukünftige Administratoren / Entwickler) auch davon, für jeden hinzugefügten Client eine neue Verbindungszeichenfolge erstellen zu müssen.
LukeP
3
Ja, ich hatte buchstäblich tagelang Probleme, eine tragfähige, robuste Lösung für dieses Problem zu finden, und diese einfache Erweiterungsmethode beantwortete meine Probleme. Seit ich es im November letzten Jahres erstellt habe, musste ich keine Änderungen daran vornehmen, daher denke ich, dass es so wie es ist gut getestet wurde :). Trotzdem froh, dass es ein paar Kästchen ankreuzt ... gut zu reden.
Jim Tollan
5
Ich erhalte diesen Fehler System.ArgumentException: Schlüsselwort nicht unterstützt: 'Datenquelle' in EF 4
Sheshadri
2
@ user1234 Ich habe auch den Fehler: Schlüsselwort nicht unterstützt 'Datenquelle'. Um dieses Problem zu lösen, musste ich diesen Teil seines Codes ändern: // add a reference to System.Configuration var entityCnxStringBuilder = new EntityConnectionStringBuilder { ProviderConnectionString = new SqlConnectionStringBuilder(System.Configuration.ConfigurationManager .ConnectionStrings[configNameEf].ConnectionString).ConnectionString };
A.Ima
2
@jimtollan Jedes Mal, wenn ich eine neue Instanz erstelle, wird diese aus der alten Verbindungszeichenfolge erstellt, die in app.config gespeichert wurde !!
Abdulsalam Elsharif
61

DbContexthat eine Konstruktorüberladung, die den Namen einer Verbindungszeichenfolge oder einer Verbindungszeichenfolge selbst akzeptiert. Implementieren Sie Ihre eigene Version und übergeben Sie sie an den Basiskonstruktor:

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

Übergeben Sie dann einfach den Namen einer konfigurierten Verbindungszeichenfolge oder einer Verbindungszeichenfolge selbst, wenn Sie Ihre instanziieren DbContext

var context = new MyDbContext( "..." );
Moho
quelle
Ich wusste nicht, dass die Funktion in meiner von DbContext abgeleiteten Klasse bereits vorhanden ist, also habe ich sie einfach verwendet.
Brian Leeming
2
Ich denke, diese Antwort sollte als genehmigte Antwort markiert werden.
nichts
2
Diese Antwort ist großartig, aber wie @eMeL erklären. Diese Klasse wird automatisch generiert. Stattdessen sollten Sie eine andere Klasse basierend auf dieser Klasse erstellen, damit sie beim Aktualisieren des Modells nicht überschrieben wird.
Juan Carlos Oropeza
3
@JuanCarlosOropeza: EF markiert generierte Klassen (Bot-Kontext und Entitäten) geschickt als partiell, sodass Sie Ihre eigene Datei erstellen, Ihren DbContext darin (als partiell) neu deklarieren und dort Ihre benutzerdefinierten Funktionen hinzufügen können.
dotNET
14

Die Antwort von Jim Tollan funktioniert hervorragend, aber ich habe den Fehler: Das Schlüsselwort "Datenquelle" wird nicht unterstützt. Um dieses Problem zu lösen, musste ich diesen Teil seines Codes ändern:

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

dazu:

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

Es tut mir wirklich leid. Ich weiß, dass ich keine Antworten verwenden sollte, um auf andere Antworten zu antworten, aber meine Antwort ist zu lang für einen Kommentar :(

A. Ima
quelle
6

Die erstellte Klasse ist 'partiell'!

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

... und du nennst es so:

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

Sie müssen also nur eine teilweise eigene Klassendatei für die ursprünglich automatisch generierte Klasse (mit demselben Klassennamen!) erstellen und einen neuen Konstruktor mit Verbindungszeichenfolgenparametern hinzufügen, wie zuvor die Antwort von Moho.

Danach können Sie den parametrisierten Konstruktor gegen das Original verwenden. :-)

Beispiel:

using (var ctx = new Database1Entities1(myOwnConnectionString))
      {
        #if DEBUG
        ctx.Database.Log = Console.Write;
        #endif
eMeL
quelle
Die obige Lösung funktioniert für mich. Sie können mehr Details von Link
Kartik Goyal
0

Fügen Sie Ihrer web.config oder app.config mehrere Verbindungszeichenfolgen hinzu.

Dann können Sie sie als Zeichenfolge erhalten wie:

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

Verwenden Sie dann die Zeichenfolge, um Folgendes festzulegen:

Provider
Metadata
ProviderConnectionString

Es wird hier besser erklärt:

Lesen Sie die Verbindungszeichenfolge aus web.config

Bryan Arbelo
quelle
Die Verbindungszeichenfolgen werden in einer separaten SQL Server-Datenbank gespeichert und dem Benutzer wird eine Liste angezeigt.
Ivan-Mark Debono
0
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);

Sie können die Verbindungszeichenfolge aus der Datei web.config abrufen, diese einfach im EntityConnectionStringBuilder-Konstruktor festlegen und den EntityConnectionStringBuilder als Argument im Konstruktor für den Kontext verwenden.

Zwischenspeichern Sie die Verbindungszeichenfolge nach Benutzername. Einfaches Beispiel mit einigen generischen Methoden zum Hinzufügen / Abrufen aus dem 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;
        }
    }
scheien
quelle
Ja, aber muss der neue Verbindungsstring jedes Mal an den Datenbankkontext übergeben werden, wenn der Benutzer die Aktion eines Controllers aufruft?
Ivan-Mark Debono
Sie würden den Kontext wahrscheinlich nach jedem Aufruf entsorgen, also ja. Der Kontext sollte nur für eine Anfrage (Arbeitseinheit) leben. Erklärung
scheien
Wie und wo würde ich den Verbindungsstring des Benutzers für die Dauer seiner Sitzung speichern? (Viele Benutzer können eine Verbindung zum Web-API-Projekt herstellen und unterschiedliche Verbindungszeichenfolgen haben.)
Ivan-Mark Debono
Wie wäre es, wenn Sie es zwischenspeichern und über den Benutzernamen oder einen anderen Schlüssel abrufen.
Scheien
0

In meinem Fall verwende ich den ObjectContext im Gegensatz zum DbContext, daher habe ich den Code in der akzeptierten Antwort zu diesem Zweck optimiert.

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
        }
    }
}
David
quelle
Ich habe diesen Fehler erhalten. Schlüsselwort wird nicht unterstützt: 'Datenquelle'. Ich benutze EF 4
Sheshadri
0

Ich wollte mehrere Datenquellen in der App-Konfiguration haben. Nachdem ich einen Abschnitt in der app.config eingerichtet hatte, tauschte ich die Datenquelle aus und übergab sie dann als Verbindungszeichenfolge an den dbcontext.

//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;

Ich brauchte ein bisschen, um das herauszufinden. Ich hoffe es hilft jemandem. Ich habe es viel zu kompliziert gemacht. vor dem.

Jake Porter
quelle
0

Ich habe zwei Erweiterungsmethoden, um die normale Verbindungszeichenfolge in das Entity Framework-Format zu konvertieren. Diese Version funktioniert gut mit Klassenbibliotheksprojekten, ohne die Verbindungszeichenfolgen aus der Datei app.config in das primäre Projekt zu kopieren. Dies ist VB.Net, aber einfach in C # zu konvertieren.

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

Danach erstelle ich eine Teilklasse für 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

Abfrage erstellen:

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
SZL
quelle
0

Verwenden Sie für SQL Server- und SQLite-Datenbanken:

_sqlServerDBsContext = new SqlServerDBsContext(new DbContextOptionsBuilder<SqlServerDBsContext>().UseSqlServer("Connection String to SQL DB").Options);

Stellen Sie für SQLite sicher, dass Microsoft.EntityFrameworkCore.Sqliteinstalliert ist. Die Verbindungszeichenfolge lautet dann einfach "'DataSource =' + Dateiname".

_sqliteDBsContext = new SqliteDBsContext(new DbContextOptionsBuilder<SqliteDBsContext>().UseSqlite("Connection String to SQLite DB").Options);
TiyebM
quelle
-6
Linq2SQLDataClassesDataContext db = new Linq2SQLDataClassesDataContext();

var query = from p in db.SyncAudits orderby p.SyncTime descending select p;
Console.WriteLine(query.ToString());

versuchen Sie diesen Code ...

Saad Mehmood Khan
quelle