Динамическое подключение базы данных 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=&quot;
      server=localhost;user id=xxxx;password=yyyyy;
     persistsecurityinfo=True;database=myclient_0&quot;" 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=&quot;
      server=localhost;user id=xxxx;password=yyyyy;
     persistsecurityinfo=True;database=myclient_0&quot;" 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=&quot;
      server=localhost;user id=xxxx;password=yyyyy;
     persistsecurityinfo=True;database={0}&quot;" 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=&quot;
      server=localhost;user id=xxxx;password=yyyyy;
     persistsecurityinfo=True;database={0}&quot;" 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 часов, рассматривая различные решения, которые просто не работали или предоставляли достаточно информации для их завершения. Как я уже сказал, нахождение примера Франциска позволило мне заставить его работать.

С Уважением,