Entity Framework 6 的动态 MySQL 数据库连接

22

我希望能够向实体框架上下文传递动态连接字符串。我有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,并因连接字符串无效而失败。

问题是,如果我正在动态创建连接字符串,该如何将提供程序名称传递给连接字符串呢?

2个回答

54

Entity Framework 6提供了一些便利的微小改变,有助于使MySQL正常工作并创建动态数据库连接。

如何使用Entity Framework 6使MySQL正常工作

首先,在我回答这个问题时,与EF6兼容的唯一.Net连接器驱动程序是MySQL .Net Connector 6.8.1(Beta开发版本),可以在官方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 />节点:

<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>
如果你更改了默认的 sql server 连接,不要忘记删除嵌套在 defaultConnectionFactory 节点中的 <parameter> 节点。MysqlConnectionFactory 的构造函数不需要任何参数,如果这些参数仍然存在,则会导致连接失败。

当前阶段使用 Entity 连接 MySQL 很容易,只需按名称引用上面的 connectionString 即可。请注意,如果按名称连接,则即使 defaultConnectionFactory 节点仍指向 SQL Server(默认情况下是这样),也可以工作。

public class ApplicationDbContext: DbContext
{
    public ApplicationDbContext() : base("mysqlCon")
    {
    }
}

现在只需要正常连接即可:

ApplicationDbContext db = ApplicationDbContext();

动态选择数据库名称进行连接

现在,我们可以通过参数轻松地连接到一个数据库,但还有一些需要做的事情。

重要提示

如果您希望动态连接到 MySQL,则必须在 Web.config 中更改 defaultConnectionFactory。因为我们将直接向上下文构造函数传递连接字符串,它不会知道使用哪个提供者,除非在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);
    }
}

如果您正在使用数据库迁移,则以下步骤非常重要

如果您使用迁移,您会发现应用程序DbContext将由框架传递到种子方法中,但它会失败,因为它不会传递我们为数据库名称设置的参数。

请在上下文类底部(或任何位置)添加以下类以解决该问题。

public class MigrationsContextFactory : IDbContextFactory<ApplicationDbContext>
{
    public ApplicationDbContext Create()
    {
        return new ApplicationDbContext("developmentdb");
    }
}

您的Code First迁移和种子方法现在将针对您的MySQL数据库中的developmentdb模式进行操作。

希望这能帮助某些人 :)


你将连接字符串命名为“mysqlCon”,但在代码中却将“MyDatabase”作为参数传递。这是一个错误吗? - Davor
没问题,你的回答也帮了我很多。 - Davor
我只是想留下比点赞更多的东西。感谢你把它分解成最简单的核心部分! - Benjamin Trent
嗨@francisco.preller,您已将默认连接设置为mysql,但如果我们需要动态地将providerName设置为mssql/任何其他内容,该怎么办? - Narendra Singh Rathore

1
现在已经是2019年了,当然有些事情已经改变了,但Franciso的例子确实帮助了我。这是我能找到的最简单的解决方案,也是唯一一个真正起作用的方案。我从他展示的内容中做了一些修改。按照这个完成,你应该会得到一个可行的解决方案。
我必须做一些更改。我将非常明确地说明必须做什么,我将使用我的实际文件名等,以便您不必猜测替换。许多示例也缺乏如何使其最终工作的信息。这个例子包含了你需要知道的所有内容。 这是在Visual Studio 2015 Entityframework 6上构建的,使用MySql Server 8.0.16.0。
不幸的是,MySql连接器和库是一团糟。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的,但我真的不知道为什么它不能适用于任何数据库。

情景

我有一个多租户的情况,其中我有一个共同的数据库和多个租户数据库,每个客户一个。客户ID保存在共同的数据库中,用于登录和授权,客户ID指示使用哪个数据库。客户端数据库都称为myclientdb_x,其中x是客户端号码。例如myclientdb_1、myclientdb_2、myclientdb_35等。

我需要动态地切换到当前服务代码所使用的任何clientdb_x。有一个名为myclient_0的初始数据库客户端,它是所有其他myclient_x数据库的模板。

步骤1

我在Web.config中创建了一个特定的连接字符串,它看起来像这样。它允许连接到clientdb_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: strings” 是数据实体所需的,这就是为什么您不能只将标准连接字符串发送到数据实体中。如果您尝试发送初始连接字符串,则会失败。
 <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();
        }

步骤三

这个新的连接字符串是您需要修改的。我没有测试过,但我非常确定如果您使用向导更改数据实体模型,则需要再次进行此更改。 获取字符串:

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

请注意,唯一更改的部分是将database=myclient_0更改为database={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
现在你可以让它运行。为了更好地理解这个例子,知道这个实体的模型是什么样子很好。它非常简单,因为我只是在测试和尝试让它运行。
通过Drilling down ClientDbUserUpdater.edmx然后进入ClientDbUserUpdater.tt,您将在modelname.cs中找到您的模型。我的模型称为"user",因此我的文件名称为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”,但这对于演示目的更好。
以下是具有真实名称的实际代码,可在数据库客户端_19上向用户表添加新行。
  string _newSchema = "client_19"
  using(client_0Entities _client_0Entities = new client_0Entities(_newSchema))
  {
     user _user = new user();
     _user.UserId = 201;
     _user.Email = "someone@someplace.com"
     _user.FirstName ' "Someone"; 
     _user.LastName  = "New";
     _user.Active = true;

     client_0Entities.users.Add ( _user ) ;
     client_0Entities.SaveChangesAsync ( ) ;
  }

希望这能帮助到一些人。我花了大约20小时查看不同的解决方案,但它们都不能正常工作或提供足够的信息来完成它们。正如我所说,找到Franciso的示例让我成功了。 问候,

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接