Entity Framework меняет соединение во время выполнения
У меня есть проект веб-API, который ссылается на мою модель и сборки DAL. Пользователю предоставляется экран входа в систему, где он может выбирать разные базы данных.
Я строю строку подключения следующим образом:
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()
};
}
Прежде всего, как мне на самом деле изменить соединение контекста данных?
И во-вторых, поскольку это проект веб-API, является ли строка подключения (настроенная для входа в систему выше) постоянной в течение всего взаимодействия с пользователем, или она должна передаваться каждый раз в мой контекст данных?
13 ответов
Немного опоздал с этим ответом, но я думаю, что есть потенциальный способ сделать это с помощью аккуратного метода расширения. Мы можем воспользоваться соглашением 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
);
Я знаю, что у вас уже есть базовая функциональность, но я подумал, что это добавит немного разнообразия.
DbContext
имеет перегрузку конструктора, которая принимает имя строки подключения или самой строки подключения. Реализуйте свою собственную версию и передайте ее базовому конструктору:
public class MyDbContext : DbContext
{
public MyDbContext( string nameOrConnectionString )
: base( nameOrConnectionString )
{
}
}
Затем просто передайте имя настроенной строки подключения или самой строки подключения, когда вы создаете DbContext
var context = new MyDbContext( "..." );
Ответ Джима Толлана отлично работает, но я получил сообщение об ошибке: ключевое слово не поддерживает "источник данных". Чтобы решить эту проблему, мне пришлось изменить эту часть своего кода:
// 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
};
Мне очень жаль. Я знаю, что не должен использовать ответы, чтобы отвечать на другие ответы, но мой ответ слишком длинный для комментария:(
Созданный класс является "частичным"!
public partial class Database1Entities1 : DbContext
{
public Database1Entities1()
: base("name=Database1Entities1")
{
}
... и вы называете это так:
using (var ctx = new Database1Entities1())
{
#if DEBUG
ctx.Database.Log = Console.Write;
#endif
Таким образом, вам нужно только создать частичный собственный файл класса для исходного автоматически сгенерированного класса (с тем же именем класса!) и добавить новый конструктор с параметром строки подключения, как ответ Мохо ранее.
После этого вы можете использовать параметризованный конструктор против оригинала.:-)
пример:
using (var ctx = new Database1Entities1(myOwnConnectionString))
{
#if DEBUG
ctx.Database.Log = Console.Write;
#endif
Вы можете сделать это на лету сIDbConnectionInterceptor
. Преимущество этого заключается в том, что вы можете работать со стандартной строкой подключения, а не с версией Entity Client, а также не нужно изменять автоматически сгенерированные классы контекста в модели 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; просто добавьте
<interceptor />
к существующему
<entityFramework />
node с полным именем типа вашего нового перехватчика:
<entityFramework>
<interceptors>
<interceptor type="Foo.Bar.SecretsDbConnectionInterceptor, Foo.Bar" />
</interceptors>
...
</entityFramework>
Или, мое личное предпочтение, вы можете подключить его с помощью кода. Это эквивалентно версии конфигурации. В идеале это должно происходить в
Application_Startup
в сервисе/проекте пользовательского интерфейса или ближе к началу
Main
в консольном приложении, потому что оно должно запускаться до того, как вы попытаетесь установить какие-либо новые s:
DbInterception.Add(new Foo.Bar.SecretsDbConnectionInterceptor());
При настройке с помощью кода вы можете передать параметры конструктору перехватчика или использовать DI.
Примечание: код перехватчика запускается каждый раз, когда вы создаете новый экземпляр любого
DbContext
в вашем приложении, поэтому остерегайтесь влияния на производительность. Вы можете реализовать некоторую стратегию кэширования в своем перехватчике или сделать его одноэлементным экземпляром с отображением имени контекста/строки подключения или что-то в этом роде.
Добавьте несколько строк подключения в ваш web.config или app.config.
Затем вы можете получить их в виде строки:
System.Configuration.ConfigurationManager.
ConnectionStrings["entityFrameworkConnection"].ConnectionString;
Затем используйте строку для установки:
Provider
Metadata
ProviderConnectionString
Это лучше объяснить здесь:
Для баз данных 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);
В моем случае я использую 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
}
}
}
хорошо, если вы работаете с 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");
У меня есть два метода расширения для преобразования обычной строки подключения в формат Entity Framework. Эта версия хорошо работает с проектами библиотеки классов без копирования строк подключения из файла 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
Я хотел, чтобы в конфигурации приложения было несколько источников данных. Поэтому после настройки раздела в 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;
Это заняло у меня немного времени, чтобы понять. Я надеюсь, что это поможет кому-то. Я делал это слишком сложно. до этого.
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;
}
}
Linq2SQLDataClassesDataContext db = new Linq2SQLDataClassesDataContext();
var query = from p in db.SyncAudits orderby p.SyncTime descending select p;
Console.WriteLine(query.ToString());
попробуйте этот код...