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


80

我有一个引用我的模型和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项目,是在用户交互过程中保持连接字符串(在每次登录时设置)还是应始终传递给我的数据上下文
Narendra Singh Rathore

@NarendraSinghRathore连接字符串存储在配置文件中,其中数据库名称(或其他名称)为键。用户在登录时选择一个数据库,该数据库存储在缓存中,其中的密钥可能是用户名。用户发出一个请求,将其用户名作为标头传递,并检索连接字符串并将其传递给datacontext。
伊万·马克·德波诺

@ Ivan-MarkDebono您能解释这个缓存吗?您是在后端使用memorycache还是会话,还是在前端将其存储为cookie。谢谢!
Narendra Singh Rathore

1
@NarendraSinghRathore单例中的MemoryCache
Ivan-Mark Debono

Answers:


110

这个答案有点晚了,但是我认为有一种潜在的方法可以使用一种简洁的扩展方法来做到这一点。我们可以利用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
    );

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


6
太好了,谢谢!我可以在多租户项目中使用此功能,并在扩展程序Controller中始终将控制器的“ db”设置为其客户特定的数据库。这也使我(或任何未来的管理员/开发人员)从必须为每个要添加的客户端创建新的连接字符串中解放出来。
2014年

3
是的,我花了好几天的时间来努力想出一个可行的,健壮的解决方案,而这种简单的扩展方法却解决了我的问题。自从去年11月创建它以来,我没有对其进行任何更改,因此我认为它是经过充分测试的:)。无论如何,很高兴它能打几个方框...好聊。
2014年

5
我收到此错误System.ArgumentException:不支持关键字:EF 4中的“数据源”
sheshadri 2015年

2
@ user1234我也收到错误:关键字不受支持的“数据源”。为了解决这个问题,我不得不更改他的代码的这一部分: // add a reference to System.Configuration var entityCnxStringBuilder = new EntityConnectionStringBuilder { ProviderConnectionString = new SqlConnectionStringBuilder(System.Configuration.ConfigurationManager .ConnectionStrings[configNameEf].ConnectionString).ConnectionString };
A.Ima

2
@jimtollan每次创建新实例时,它都是使用保存在app.config中的旧连接字符串创建的!
阿卜杜勒萨兰·埃尔沙里夫

61

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

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

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

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

我没有意识到该函数已经存在于我的DbContext派生类中,因此我只是使用了它。
Brian Leeming

2
我认为该答案应标记为已批准答案。
化为乌有

2
这个答案很棒,但是正如@eMeL解释的那样。该类是自动生成的,因此您应该基于该类创建另一个类,这样在更新模型时不会被覆盖。
Juan Carlos Oropeza

3
@JuanCarlosOropeza:EF巧妙地将生成的类(bot hcontext和实体)标记为部分类,因此您可以创建自己的文件,在其中(部分)声明DbContext并在其中添加自定义函数。
dotNET

14

吉姆·托兰(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
};

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


6

创建的类是“ partial”!

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

以上解决方案为我工作。您可以得到更多的信息链接
卡尔蒂克戈亚尔

0

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

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

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

然后使用字符串设置:

Provider
Metadata
ProviderConnectionString

最好在这里解释:

从web.config读取连接字符串


连接字符串存储在单独的sql server数据库中,并向用户显示一个列表。
Ivan-Mark Debono 2013年

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

您可以从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?
Ivan-Mark Debono 2013年

您可能会在每次调用后处理上下文,所以可以。该上下文仅应满足一个请求(工作单元)。说明
scheien 2013年

那么,在会话期间,我将如何以及在何处存储用户的连接字符串?(许多用户可以连接到Web api项目,并且可以具有不同的连接
字符串

如何缓存它,并通过用户名或其他键检索它。
scheien 2013年

0

就我而言,我使用的是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
sheshadri 2015年

0

我想在应用程序配置中有多个数据源。因此,在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;

这花了我一些时间来弄清楚。我希望它可以帮助某人。我让它变得太复杂了。在这之前。


0

我有两种扩展方法,可以将普通的连接字符串转换为实体框架格式。此版本可以很好地与类库项目一起使用,而无需将连接字符串从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

0

对于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);

-6
Linq2SQLDataClassesDataContext db = new Linq2SQLDataClassesDataContext();

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

试试这个代码...

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.