实体框架在运行时更改连接

Posted

技术标签:

【中文标题】实体框架在运行时更改连接【英文标题】:Entity Framework change connection at runtime 【发布时间】:2013-12-11 12:59:18 【问题描述】:

我有一个引用我的模型和 DAL 程序集的 Web API 项目。用户会看到一个登录屏幕,他可以在其中选择不同的数据库。

我构建连接字符串如下:

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

首先,我如何真正改变数据上下文的连接?

其次,由于这是一个 Web API 项目,连接字符串(根据上述在登录时设置)是在整个用户交互过程中保持不变,还是应该每次都传递给我的数据上下文?

【问题讨论】:

我添加了一个小替代品,以防它符合您的心态/工具箱要求。 @Ivan-Mark 你是如何解决这部分问题的 其次,由于这是一个 web api 项目,连接字符串(在上面的登录时设置)是在整个用户交互过程中保持不变还是应该每次都传递给我的数据上下文 @NarendraSinghRathore 连接字符串存储在配置文件中,数据库名称(或其他名称)是关键。用户在登录时选择一个数据库,并将其存储在缓存中,其中键可能是用户名。用户发出请求,将其用户名作为标头传递,连接字符串被检索并传递给数据上下文。 @Ivan-MarkDebono 你能解释一下这个 cache 吗?您是在后端使用内存缓存或会话,还是在前端存储为 cookie。谢谢! @NarendraSinghRathore MemoryCache in a singleton 【参考方案1】:

这个答案有点晚了,但我认为有一种潜在的方法可以通过一个简洁的小扩展方法来做到这一点。我们可以利用 EF 约定而不是配置以及一些小的框架调用。

不管怎样,注释代码和示例用法:

扩展方法类:

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
        
    

基本用法:

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

我知道您已经具备基本功能,但认为这会增加一点多样性。

【讨论】:

这太好了,谢谢!我可以在多租户项目中使用它以及扩展的Controller,它将始终将控制器的“db”设置为客户特定的数据库。这也使我(或任何未来的管理员/开发人员)不必为每个添加的客户端创建一个新的连接字符串。 是的,我挣扎了好几天,试图为这个问题提出一个可行的健壮解决方案,这个简单的扩展方法回答了我的问题。自从去年 11 月创建它以来,我不必对其进行任何更改,所以我认为它已经通过了良好的道路测试:)。无论如何,很高兴它在几个盒子里打勾……很高兴。 我收到此错误 System.ArgumentException: Keyword not supported: 'data source' in EF 4 @user1234 我也收到错误:关键字不支持“数据源”。为了解决这个问题,我不得不更改他的这部分代码:// add a reference to System.Configuration var entityCnxStringBuilder = new EntityConnectionStringBuilder ProviderConnectionString = new SqlConnectionStringBuilder(System.Configuration.ConfigurationManager .ConnectionStrings[configNameEf].ConnectionString).ConnectionString ; @jimtollan 每次我创建新实例时,它都是从保存在 app.config 中的旧连接字符串创建的!!【参考方案2】:

DbContext 有一个构造函数重载,它接受连接字符串的名称或连接字符串本身。实现您自己的版本并将其传递给基本构造函数:

public class MyDbContext : DbContext

    public MyDbContext( string nameOrConnectionString ) 
        : base( nameOrConnectionString )
    
    

然后在您实例化 DbContext 时,只需传递已配置连接字符串的名称或连接字符串本身即可

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

【讨论】:

我没有意识到我的 DbContext 派生类中已经存在该函数,所以我只是使用了它。 我认为这个答案应该标记为已批准的答案。 这个答案很好,但正如@eMeL 解释的那样。这个类是自动生成的,所以你应该基于这个创建另一个类,这样如果你更新模型就不会被覆盖。 @JuanCarlosOropeza:EF 巧妙地将生成的类(包括 hcontext 和实体)标记为部分,因此您可以创建自己的文件,在其中重新声明 DbContext(作为部分)并在其中添加自定义函数.【参考方案3】:

Jim Tollan 的回答效果很好,但我得到了错误:关键字不支持“数据源”。 为了解决这个问题,我不得不更改他的这部分代码:

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

到这里:

// add a reference to System.Configuration
var entityCnxStringBuilder = new EntityConnectionStringBuilder

    ProviderConnectionString = new  SqlConnectionStringBuilder(System.Configuration.ConfigurationManager
               .ConnectionStrings[configNameEf].ConnectionString).ConnectionString
;

真的很抱歉。我知道我不应该使用答案来回复其他答案,但是我的答案太长了,无法评论:(

【讨论】:

【参考方案4】:

创建的类是“部分的”!

public partial class Database1Entities1 : DbContext

    public Database1Entities1()
        : base("name=Database1Entities1")
    
    

...你这样称呼它:

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

因此,您只需为原始自动生成的类(具有相同的类名!)创建部分自己的类文件,并添加一个带有连接字符串参数的新构造函数,就像 Moho 之前的回答一样。

之后,您可以使用参数化构造函数来对抗原始构造函数。 :-)

示例:

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

【讨论】:

以上解决方案对我有用。您可以从link获取更多详细信息【参考方案5】:

您可以使用IDbConnectionInterceptor 即时执行此操作。这样做的好处是允许您使用标准连接字符串而不是实体客户端版本,并且不必修改 EDMX 模型中自动生成的上下文类,或使用重载的构造函数。它只是工作!

我们使用它,例如,用秘密保险库中的密码替换标记化的连接字符串。

首先,实现接口。我只展示了需要实现的众多接口方法之一。在这种情况下,我正在实现ConnectionStringGetting,并将所有其他方法主体留空:

public class SecretsDbConnectionInterceptor : IDbConnectionInterceptor

    public void ConnectionStringGetting(DbConnection connection, DbConnectionInterceptionContext<string> interceptionContext)
    
        var originalConnectionString = connection.ConnectionString;
        try
        
            connection.ConnectionString = /* Build your new connection string */;
        
        catch (Exception e)
        
            connection.ConnectionString = originalConnectionString;
            Trace.WriteLine(e.Message);
        
    
    
    // ... Many other methods here; leave them empty

你可以通过你的 .config 文件把它连接起来;只需将 &lt;interceptor /&gt; 添加到现有的 &lt;entityFramework /&gt; 节点,并使用您的新拦截器的完全限定类型名称:

  <entityFramework>
    <interceptors>
      <interceptor type="Foo.Bar.SecretsDbConnectionInterceptor, Foo.Bar" />
    </interceptors>
    ...
  </entityFramework>

或者,根据我的个人喜好,您可以通过代码将其连接起来。它相当于配置版本。理想情况下,这将放在服务/UI 项目中的 Application_Startup 中,或者放在控制台应用程序中 Main 的顶部,因为它必须在您尝试建立任何新的 DbContexts 之前运行:

DbInterception.Add(new Foo.Bar.SecretsDbConnectionInterceptor());

当你通过代码进行配置时,你可以将参数传递给你的拦截器构造函数,或者使用 DI。

注意:每次您在应用程序中创建任何DbContext 的新实例时,拦截器代码都会运行,因此请注意性能影响。你可以在你的拦截器中实现一些缓存策略,或者让它成为一个带有上下文名称/连接字符串映射的单例实例,或者类似的东西。

【讨论】:

【参考方案6】:

在您的 web.config 或 app.config 中添加多个连接字符串。

然后您可以将它们作为字符串获取:

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

然后用字符串来设置:

Provider
Metadata
ProviderConnectionString

这里解释得更好:

Read connection string from web.config

【讨论】:

连接字符串存储在单独的 sql server 数据库中,并向用户呈现一个列表。【参考方案7】:
string _connString = "metadata=res://*/Model.csdl|res://*/Model.ssdl|res://*/Model.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=localhost;initial catalog=DATABASE;persist security info=True;user id=sa;password=YourPassword;multipleactiveresultsets=True;App=EntityFramework&quot;";

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

您可以从 web.config 中获取连接字符串,只需在 EntityConnectionStringBuilder 构造函数中设置它,然后将 EntityConnectionStringBuilder 用作上下文构造函数中的参数。

按用户名缓存连接字符串。使用几个通用方法来处理从缓存中添加/检索的简单示例。

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;
        
    

【讨论】:

是的,但是每次用户调用控制器的操作时是否需要将新的连接字符串传递给 dbcontext? 您可能会在每次调用后处理上下文,所以是的。上下文应该只适用于一个请求(工作单元)。 Explanation 那么在用户会话期间,我将如何以及在哪里存储用户的连接字符串? (很多用户可以连接到web api项目,可以有不同的连接字符串) 如何缓存它,然后通过用户名或其他键检索它。【参考方案8】:

在我的例子中,我使用的是 ObjectContext 而不是 DbContext,因此我为此目的调整了接受答案中的代码。

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
        
    

【讨论】:

我收到此错误关键字不支持:'数据源'。我正在使用 EF 4【参考方案9】:

我想在应用配置中有多个数据源。因此,在 app.config 中设置了一个部分后,我换出了数据源,然后将其作为连接字符串传递给 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;

这花了我一点时间才弄清楚。我希望它可以帮助某人。我把它弄得太复杂了。在这之前。

【讨论】:

【参考方案10】:

我有两种扩展方法可以将普通连接字符串转换为实体框架格式。此版本适用于类库项目,无需将连接字符串从 app.config 文件复制到主项目。这是 VB.Net,但很容易转换为 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

之后我为 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

创建查询:

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

【讨论】:

【参考方案11】:

对于 SQL Server 和 SQLite 数据库,请使用:

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

对于 SQLite,确保 Microsoft.EntityFrameworkCore.Sqlite 是 安装,那么连接字符串就是“'DataSource='+文件名”。

_sqliteDBsContext = new SqliteDBsContext(new DbContextOptionsBuilder<SqliteDBsContext>().UseSqlite("Connection String to SQLite DB").Options);

【讨论】:

【参考方案12】:

如果您正在使用 EFCore,那么您可以执行类似创建新连接字符串的操作: 在您的上下文文件中(对于 Sqlite)

public biorevContext(string connectionString) : base(GetOptions(connectionString))
    
       this.Database.EnsureCreated();
    
    private static DbContextOptions GetOptions(string connectionString)
    
        return SqliteDbContextOptionsBuilderExtensions.UseSqlite(new DbContextOptionsBuilder(), connectionString).Options;
    

对于 mysql

 public biorevContext(string connectionString) : base(GetOptions(connectionString))
    
       this.Database.EnsureCreated();
    
    private static DbContextOptions GetOptions(string connectionString)
    
        return MySQLDbContextOptionsExtensions.UseMySQL(new DbContextOptionsBuilder(), connectionString).Options;
    

对于 Sql:

    public biorevContext(string connectionString) : base(GetOptions(connectionString))
    
       this.Database.EnsureCreated();
     
    private static DbContextOptions GetOptions(string connectionString)
    
        return SqlServerDbContextOptionsExtensions.UseSqlServer(new DbContextOptionsBuilder(), connectionString).Options;
    

然后你可以这样使用它:

        var context = new biorevContext("connectionString");

【讨论】:

【参考方案13】:
Linq2SQLDataClassesDataContext db = new Linq2SQLDataClassesDataContext();

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

试试这个代码...

【讨论】:

以上是关于实体框架在运行时更改连接的主要内容,如果未能解决你的问题,请参考以下文章

c# - 在运行时更改 App.Config 后,实体框架 ConnectionString 不会更新

IIS服务器SSL证书安装 (pfx文件不能直接运行时)

如何使用实体框架执行原始 SQL 查询而无需使用模型?

为啥实体框架在不同的 AppDomain 中运行时会明显变慢?

Spring Application在同一集群内运行时无法连接到Kafka,但在从集群外运行时可以工作[重复]

在 DbContext 中看不到实体框架数据库更改