Динамическое подключение базы данных MySQL для Entity Framework 6
Я хочу передать динамическую строку соединения в контекст инфраструктуры сущности. У меня более 150 схем, которые идентичны (по одному на одну учетную запись), и я хотел бы выбрать соединение как таковое:
ApplicationDbContext db = new ApplicationDbContext("dbName");
В теории это было бы довольно легко, так как я могу создать connectionString и передать его как аргумент для конструктора, например:
public ApplicationDbContext(string dbName) : base(GetConnectionString(dbName))
{
}
public static string GetConnectionString(string dbName)
{
// The connectionString passed is something like:
// Server=localhost;Database={0};Uid=username;Pwd=password
var connString = ConfigurationManager
.ConnectionStrings["MyDatabase"]
.ConnectionString
.ToString();
return String.Format(connString, dbName);
}
Я могу подключиться успешно, когда просто передаю имя строки подключения, но не тогда, когда я генерирую его динамически, как показано ниже. Теперь я понимаю, что это потому, что строка соединения в web.config имеет в ней атрибут providerName="MySql.Data.MySqlClient"
.
Когда я передаю фактическую строку подключения динамически к соединению, он предполагает, что ему необходимо подключиться к SQL Server, а не к MySQL, и не удается из-за неправильной строки подключения.
Вопрос в том, как передать имя поставщика в строку соединения, если я создаю его динамически?
Ответы
Ответ 1
Entity Framework 6 предлагает некоторые удобные тонкие изменения, которые помогают как в работе MySQL, так и при создании динамических подключений к базе данных.
Получение MySQL работы с Entity Framework 6
Во-первых, в момент моего ответа на этот вопрос единственными драйверами .Net-соединителей, совместимыми с EF6, является MySQL.Net Connectior 6.8.1 (версия для разработки бета-версии), которую можно найти на официальном сайте MySQL здесь.
После установки обратитесь к следующим файлам из решения Visual Studio:
- Mysql.Data.dll
- Mysql.Data.Entity.EF6.dll
Вам также нужно будет скопировать эти файлы где-нибудь, где они будут доступны для проекта во время сборки, например, в каталоге bin.
Затем вам нужно добавить некоторые элементы в файл Web.config(или App.config, если на рабочем столе).
Строка подключения:
<connectionStrings>
<add name="mysqlCon"
connectionString="Server=localhost;Database=dbName;Uid=username;Pwd=password"
providerName="MySql.Data.MySqlClient" />
</connectionStrings>
Также добавьте провайдера внутри узлов <entityFramework />
и <providers />
, необязательно (это абсолютное обязательство во второй части моего ответа при работе с динамически определенными базами данных), вы можете изменить <defaultConnectionFactory />
node:
<entityFramework>
<defaultConnectionFactory type="MySql.Data.Entity.MySqlConnectionFactory, MySql.Data.Entity.EF6" />
<providers>
<provider invariantName="MySql.Data.MySqlClient" type="MySql.Data.MySqlClient.MySqlProviderServices, MySql.Data.Entity.EF6" />
</providers>
</entityFramework>
Если вы изменили defaultConnectionFactory из соединения SQL-сервера по умолчанию, не забудьте удалить узлы <parameter>
, которые вложены в defaultConnectionFactory node. MysqlConnectionFactory не принимает никаких параметров для своего конструктора и не будет работать, если параметры все еще существуют.
На этом этапе довольно легко подключиться к MySQL с Entity, вы можете просто ссылаться на connectionString выше по имени. Обратите внимание, что при подключении по имени это будет работать, даже если defaultConnectionFactory
node по-прежнему указывает на SQL Server (по умолчанию это делается).
public class ApplicationDbContext: DbContext
{
public ApplicationDbContext() : base("mysqlCon")
{
}
}
Это просто вопрос подключения нормально:
ApplicationDbContext db = ApplicationDbContext();
Подключение к динамически выбранному имени базы данных
В этот момент легко подключиться к базе данных, которую мы можем передать как параметр, но нам нужно сделать несколько вещей.
Важное примечание
Если вы еще этого не сделали, вы должны изменить значение defaultConnectionFactory в Web.config, если вы хотите подключиться к MySQL динамически. Поскольку мы будем передавать строку подключения непосредственно конструктор контекста, он не будет знать, какой поставщик использовать и обратится к соединению по умолчанию factory, если не указано в web.config. См. Выше, как это сделать.
Вы можете передать строку соединения вручную в контексте следующим образом:
public ApplicationDbContext() : base("Server:localhost;...")
{
}
Но чтобы сделать это немного проще, мы можем внести небольшое изменение в строку соединения, которую мы сделали выше, при настройке mySQL. Просто добавьте заполнитель, как показано ниже:
<add name="mysqlCon" connectionString="Server=localhost;Database={0};Uid=username;Pwd=password" providerName="MySql.Data.MySqlClient" />
Теперь мы можем создать вспомогательный метод и изменить класс ApplicationDbContext, как показано ниже:
public class ApplicationDbContext: DbContext
{
public ApplicationDbContext(string dbName) : base(GetConnectionString(dbName))
{
}
public static string GetConnectionString(string dbName)
{
// Server=localhost;Database={0};Uid=username;Pwd=password
var connString =
ConfigurationManager.ConnectionStrings["mysqlCon"].ConnectionString.ToString();
return String.Format(connString, dbName);
}
}
Если вы используете миграции баз данных, важно сделать следующий шаг
Если вы используете миграции, вы обнаружите, что ApplicationDbContext будет передан вашему методу Seed с помощью фреймворка, и он потерпит неудачу, потому что он не будет передаваться в параметре, который мы вводим для имени базы данных.
Добавьте следующий класс в конец вашего контекстного класса (или где-нибудь действительно), чтобы решить эту проблему.
public class MigrationsContextFactory : IDbContextFactory<ApplicationDbContext>
{
public ApplicationDbContext Create()
{
return new ApplicationDbContext("developmentdb");
}
}
Сначала ваши кодовые миграции и методы семени будут нацелены на схему developmentdb
в вашей базе данных MySQL.
Надеюсь, это поможет кому-то:)
Ответ 2
Сейчас 2019 год, конечно, все немного изменилось, но пример Фрэнсисо действительно помог мне в этом. Это самое простое решение, которое я смог найти, и единственное, которое действительно сработало. Я немного изменил то, что он показал. Следуйте этому до завершения, вы должны получить рабочее решение.
Я должен был изменить несколько вещей. Я собираюсь быть очень точным в том, что должно быть сделано, и я собираюсь использовать мои настоящие имена файлов и т.д., Чтобы вам не приходилось догадываться о подстановках. Многие примеры также коротки о том, как заставить это работать в конце. В этом примере есть все, что вам нужно знать.
Это было построено на Visual Studio 2015 Entityframework 6 с использованием сервера MySql 8.0.16.0.
К сожалению, коннекторы и библиотеки MySql - полный беспорядок. Соединитель/net 8.0.xx.0 и MySql.Data.Entity.EF6 и MySql.Data совершенно бесполезны. Я установил Connector Net 6.10.7.0, MySql.Data.Entity.EF6 6.10.7.0 и MySql.Data 6.10.7.0. Это работает для меня, и я буду решительно выступать против изменения этого.
Это для MySql, но я действительно не знаю, почему он не может работать для любой БД.
сценарий
У меня мультитенантная ситуация, когда у меня есть общая база данных и несколько баз данных для палатки, по одной на каждого клиента. Идентификатор клиента хранится в общей базе данных для входа в систему и авторизации, а идентификатор клиента указывает, какую базу данных использовать. Все клиентские базы данных называются myclientdb_x, где x - это номер клиента. myclientdb_1, myclientdb_2, myclientdb_35 и так далее.
Мне нужно динамически переключаться на любой clientdb_x, который в данный момент обслуживает код. Существует исходный клиент базы данных с именем myclient_0, который является шаблоном для всех других баз данных myclient_x.
Шаг 1
Я создал определенную строку подключения в моем Web.config для этого, он выглядит следующим образом. Это позволяет подключаться к клиенту db_0
<add name="DefaultClientConnection" providerName="MySql.Data.MySqlClient"
connectionString="server=localhost;user id=xxx;
password=xxxx; persistsecurityinfo=True;database=clientdb_0" />
Шаг 2
Я создал новый объект под названием ClientDbUserUpdater с помощью мастера. Объект данных называется
ClientDbUserUpdater.edmx
Я сказал ему использовать DefaultClientConnection в качестве соединения с БД. Я сказал ему сохранить эту новую строку соединения в файле Web.config.
Это создало новую строку подключения объекта в файле Web.config, и она будет выглядеть следующим образом
<add name="myclient_0Entities" connectionString="metadata=
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.csdl|
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.ssdl|
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.msl;
provider=MySql.Data.MySqlClient;provider connection string="
server=localhost;user id=xxxx;password=yyyyy;
persistsecurityinfo=True;database=myclient_0"" providerName="System.Data.EntityClient" />
Возможно, вам придется немного покопаться, потому что мастер не очень хорошо вкладывает \n в соответствующие места.
Обратите внимание, что эта строка подключения в основном совпадает с исходной строкой подключения, за исключением ее имени и того факта, что она имеет
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.csdl|
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.ssdl|
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.msl;
Строки res: необходимы объекту данных, и поэтому вы не можете просто отправить стандартную строку подключения в объект данных.
Если вы попытаетесь отправить в исходной строке подключения
<add name="DefaultClientConnection" providerName="MySql.Data.MySqlClient"
connectionString="server=localhost;user id=xxx;
password=xxxx; persistsecurityinfo=True;database=clientdb_0" />
вы получите исключение из
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
Шаг 3
Эта новая строка подключения - это то, что вам нужно изменить. Я не проверял это, но я уверен, что если вы измените модель объекта данных с помощью мастера, вам нужно будет снова сделать это изменение. Взять строку:
<add name="myclient_0Entities" connectionString="metadata=
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.csdl|
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.ssdl|
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.msl;
provider=MySql.Data.MySqlClient;provider connection string="
server=localhost;user id=xxxx;password=yyyyy;
persistsecurityinfo=True;database=myclient_0"" providerName="System.Data.EntityClient" />
и измените его на:
<add name="myclient_0Entities" connectionString="metadata=
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.csdl|
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.ssdl|
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.msl;
provider=MySql.Data.MySqlClient;provider connection string="
server=localhost;user id=xxxx;password=yyyyy;
persistsecurityinfo=True;database={0}"" providerName="System.Data.EntityClient" />
Обратите внимание, что изменилась только часть базы данных = myclient_0 в базу данных = {0}
Шаг 4
Объект данных создал некоторый код за ClientDbUserUpdater.edmx. Файл называется ClientDbUserUpdater.Context.cs.
Код...
namespace what.ever.your.namespace.is
{
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
public partial class client_0Entities : DbContext
{
public client_0Entities()
: base("name=client_0Entities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<user> users { get; set; }
}
}
Обратите внимание, что это частичный класс. Это означает, что вы можете расширить этот класс и добавить новый конструктор.
Добавьте следующий класс.
using System;
using System.Configuration ;
using System.Data.Entity ;
namespace what.ever.your.namespace.is
{
public partial class client_0Entities : DbContext
{
public client_0Entities(string dbName) : base(GetConnectionString(dbName))
{
}
public static string GetConnectionString(string dbName)
{
var connString = ConfigurationManager.ConnectionStrings["client_0Entities"].ConnectionString.ToString();
// obviously the next 2 lines could be done as one but creating and
// filling a string is better for debugging. You can see what happened
// by looking a conn
// return String.Format(connString, dbName);
string conn = String.Format(connString, dbName);
return conn ;
}
}
}
Класс добавляет новый конструктор, который позволяет вам получить строку базового соединения для модели объекта данных, которая сверху выглядит следующим образом:
<add name="myclient_0Entities" connectionString="metadata=
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.csdl|
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.ssdl|
res://*/Areas.Authorizations.Models.ClientDbUserUpdater.msl;
provider=MySql.Data.MySqlClient;provider connection string="
server=localhost;user id=xxxx;password=yyyyy;
persistsecurityinfo=True;database={0}"" providerName="System.Data.EntityClient" />
и модифицируйте его во время выполнения, чтобы изменить схему.
Вызов String.Format() в новом частичном классе заменяет имя схемы базы данных в этой строке соединения во время выполнения.
На данный момент все настройки сделаны.
Шаг 5
Теперь вы можете сделать это. Для лучшего понимания этого примера полезно знать, как выглядит модель для этой сущности. Это очень просто, потому что я просто тестировал и пытался сделать это.
Развернув файл ClientDbUserUpdater.edmx и перейдя в ClientDbUserUpdater.tt, вы найдете вашу модель в modelname.cs. Моя модель называется "пользователь", поэтому мое имя файла называется user.cs
namespace what.ever.your.namespace.is
{
using System;
using System.Collections.Generic;
public partial class user
{
public int UserId { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Nullable<bool> Active { get; set; }
}
}
Теперь вы можете получить доступ к своей модели, как это.
client_0Entities _client_0Entities = new client_0Entities("schemaName");
и этот код может быть в любом месте вашего решения, который может видеть класс client_0Entities.
которая на практике представляет собой строку, аналогичную любой из 3 ниже, которые являются подключением к базам данных client_19, client_47 и client_68 соответственно.
client_0Entities _client_0Entities = new client_0Entities("client_19");
client_0Entities _client_0Entities = new client_0Entities("client_47");
client_0Entities _client_0Entities = new client_0Entities("client_68");
Ниже приведен пример кода, который работает в моей системе. Очевидно, я собираюсь не жестко кодировать в "client_19", но лучше для демонстрационных целей.
Вот фактический код с реальными именами, который работает и добавляет новую строку в пользовательскую таблицу в базе данных client_19
string _newSchema = "client_19"
using(client_0Entities _client_0Entities = new client_0Entities(_newSchema))
{
user _user = new user();
_user.UserId = 201;
_user.Email = "[email protected]"
_user.FirstName ' "Someone";
_user.LastName = "New";
_user.Active = true;
client_0Entities.users.Add ( _user ) ;
client_0Entities.SaveChangesAsync ( ) ;
}
Надеюсь, это поможет некоторым людям. Я потратил около 20 часов, рассматривая различные решения, которые просто не работали или предоставляли достаточно информации для их завершения. Как я уже сказал, нахождение примера Франциска позволило мне заставить его работать.
С Уважением,