Змінення підключення Entity Framework під час виконання


80

У мене є проект веб-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, чи є рядок підключення (встановлений при вході в систему вище) постійним протягом усієї взаємодії користувача або його слід передавати кожного разу в мій контекст даних?


я додав невелику альтернативу на випадок, якщо вона вписується у ваші вимоги до мислення / набору інструментів.
jim tollan

@ Ivan-Mark Як ви вирішили цю частину А по-друге, оскільки це веб-проект api, чи є строка з'єднань (встановлена ​​при вході в систему вище) постійною протягом усієї взаємодії користувача або вона повинна передаватися щоразу до мого контексту даних
Narendra Singh Rathore

@NarendraSinghRathore Рядки підключення зберігаються у конфігураційному файлі, ключовим є ім'я бази даних (або щось інше). Користувач вибирає базу даних при вході, і вона зберігається в кеші, де ключем може бути ім’я користувача. Користувач робить запит, передаючи своє ім'я користувача як заголовок, і рядок з'єднання отримується та передається в контекст даних.
Іван-Марк Дебоно

@ Ivan-MarkDebono Чи можете ви пояснити цей кеш ? Ви використовуєте кеш пам'яті або сеанс у серверній системі або зберігаєте як файл cookie на фронтенді. Дякую!
Нарендра Сінгх Ратхоре,

1
@NarendraSinghRathore MemoryCache в односторонньому порядку
Іван-Марк Дебоно

Відповіді:


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" контролера відповідно до конкретного замовника db. Це також звільняє мене (або будь-яких майбутніх адміністраторів / розробників) від необхідності створювати новий рядок підключення для кожного клієнта, який додається.
LukeP

3
так, я буквально кілька днів намагався знайти ефективне надійне рішення цього питання, і цей простий метод розширення відповів на мої проблеми. з моменту його створення ще у листопаді минулого року, мені не доводилось вносити до нього жодних змін, тому я думаю, що це добре перевірено на дорозі, як є :). так чи інакше, радий, що тикає кілька коробок ... приємно поговорити.
Джим Толлан,

5
Я отримую цю помилку System.ArgumentException: Ключове
слово

2
@ user1234 Я також отримав помилку: Ключове слово не підтримується "джерело даних". Для вирішення цієї проблеми мені довелося змінити цю частину його коду: // add a reference to System.Configuration var entityCnxStringBuilder = new EntityConnectionStringBuilder { ProviderConnectionString = new SqlConnectionStringBuilder(System.Configuration.ConfigurationManager .ConnectionStrings[configNameEf].ConnectionString).ConnectionString };
А.Іма

2
@jimtollan Кожного разу, коли я створюю новий екземпляр, він створюється зі старого рядка підключення, збереженого в app.config !!
Абдулсалам Ельшариф

62

DbContextмає перевантаження конструктора, що приймає ім'я рядка з'єднання або самого рядка з'єднання. Реалізуйте власну версію та передайте її базовому конструктору:

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

Потім просто передайте ім'я налаштованого рядка підключення або самого рядка підключення під час створення вашого екземпляра DbContext

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

Я не розумів, що функція вже існувала у моєму похідному класі DbContext, тому я просто використовував це.
Брайан Лемінг

2
Я думаю, що цю відповідь слід позначити як схвалену відповідь.
нічого

2
Ця відповідь чудова, але як пояснює @eMeL. Цей клас автоматично генерується, тому замість цього вам слід створити інший клас на основі цього, тому не буде перезаписано, якщо ви оновите модель.
Хуан Карлос Оропеза

4
@JuanCarlosOropeza: EF спритно позначає згенеровані класи (бот hcontext та сутності) як часткові, тому ви можете створити власний файл, повторно оголосити свій DbContext в ньому (як частковий) і додати туди свої власні функції.
dotNET

14

Відповідь Джима Толлана чудово працює, але я отримав помилку: ключове слово не підтримується "джерело даних". Для вирішення цієї проблеми мені довелося змінити цю частину його коду:

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

Створений клас є "частковим"!

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-сервера, і список представляється користувачеві.
Іван-Марк Дебоно

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 кожного разу, коли користувач викликає дію контролера?
Іван-Марк Дебоно

Можливо, ви розпоряджаєтесь контекстом після кожного дзвінка, так що так. Контекст повинен діяти лише для одного запиту (одиниці роботи). Пояснення
scheien

Отже, як і де я повинен зберігати рядок підключення користувача на час його сеансу? (Багато користувачів можуть підключитися до веб-проекту api і можуть мати різні
рядки

Як щодо кешування та отримання за іменем користувача чи іншим ключем.
scheien

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

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

У мене є два методи розширення для перетворення звичайного рядка підключення у формат 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

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

спробуйте цей код ...

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.