关于c#:实体框架在运行时更改连接

Entity Framework change connection at runtime

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

我构建连接字符串如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    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项目,连接字符串(设置为上面的登录)是在整个用户交互过程中持续存在的,还是应该每次传递到我的数据上下文?


这个答案有点晚了,但我认为有一种可能的方法可以用一个简洁的小扩展方法来实现这一点。我们可以利用ef约定胜过配置,再加上一些框架调用。

总之,注释代码和示例用法:

扩展方法类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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
        }
    }
}

基本用法:

1
2
3
4
5
6
7
8
9
10
11
// 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
    );

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


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

1
2
3
4
5
6
7
public class MyDbContext : DbContext
{
    public MyDbContext( string nameOrConnectionString )
        : base( nameOrConnectionString )
    {
    }
}

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

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


吉姆·托兰的回答很好,但我得到了一个错误:关键字不支持"数据源"。为了解决这个问题,我必须更改他的代码的这一部分:

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

对此:

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

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


创建的类是"部分"!

1
2
3
4
5
6
public partial class Database1Entities1 : DbContext
{
    public Database1Entities1()
        : base("name=Database1Entities1")
    {
    }

…你这样称呼它:

1
2
3
4
5
using (var ctx = new Database1Entities1())
      {
        #if DEBUG
        ctx.Database.Log = Console.Write;
        #endif

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

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

例子:

1
2
3
4
5
using (var ctx = new Database1Entities1(myOwnConnectionString))
      {
        #if DEBUG
        ctx.Database.Log = Console.Write;
        #endif


我有两种扩展方法可以将普通连接字符串转换为实体框架格式。这个版本可以很好地处理类库项目,而无需将连接字符串从app.config文件复制到主项目。这是vb.net,但很容易转换为c。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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创建一个分部类:

1
2
3
4
5
6
7
8
9
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

创建查询:

1
2
3
4
5
6
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

我想在应用程序配置中有多个数据源。因此,在app.config中设置了一个节之后,我提取了数据源,然后将其作为连接字符串传递到dbcontext中。

1
2
3
4
5
6
7
8
9
10
11
12
//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;

这让我有点费解。我希望它能帮助别人。我把事情弄得太复杂了。在此之前。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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
        }
    }
}


1
2
3
4
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用作上下文的构造函数中的参数。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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;
        }
    }


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

然后你可以得到一个字符串,比如:

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

然后使用字符串设置:

1
2
3
Provider
Metadata
ProviderConnectionString

这里最好解释一下:

从web.config读取连接字符串


1
2
3
4
Linq2SQLDataClassesDataContext db = new Linq2SQLDataClassesDataContext();

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

尝试此代码…