如何创建初始化器以创建和迁移MySQL数据库?

14

我已经学习了大约一周的时间来使用EF,但是我卡在了创建/更新数据库的问题上。如果数据库不存在,我可以创建一个初始化器来创建数据库:

static class Program
{
    static void Main()
    {
        Database.SetInitializer<GumpDatabase>(new GumpDatabaseInitializer());
....

class GumpDatabaseInitializer : CreateDatabaseIfNotExists<GumpDatabase>
{
    public GumpDatabaseInitializer()
    {
    }
    protected override void Seed(GumpDatabase context)
    {
        context.Database.ExecuteSqlCommand("CREATE UNIQUE INDEX Name ON Stations (Name)");
        // Other stuff
    }
}

或者我可以创建一个配置来迁移数据库

static class Program
{
    static void Main()
    {
        Database.SetInitializer<GumpDatabase>(new MigrateDatabaseToLatestVersion<GumpDatabase, Configuration>());
....

internal sealed class Configuration : DbMigrationsConfiguration<GumpDatabase>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = true;
        SetSqlGenerator("MySql.Data.MySqlClient", new MySql.Data.Entity.MySqlMigrationSqlGenerator()); 
    }

    protected override void Seed(GumpDatabase context)
    {

    }
每个都可以正确运行,但我还没有想出同时实现两者的方法。我可以通过更改SetInitializer调用来在两个初始化程序之间切换,但如果我想在数据库不存在时创建它,并在存在时进行迁移,我该怎么办?我需要创建一个自定义的初始化程序吗?谢谢。
class CreateOrMigrateDatabaseInitializer<TContext, TConfiguration> : CreateDatabaseIfNotExists<TContext>, IDatabaseInitializer<TContext>
    where TContext : DbContext
    where TConfiguration : DbMigrationsConfiguration<TContext>, new()
{
    private readonly DbMigrationsConfiguration _configuration;
    public CreateOrMigrateDatabaseInitializer()
    {
        _configuration = new TConfiguration();
    }
    public CreateOrMigrateDatabaseInitializer(string connection)
    {
        Contract.Requires(!string.IsNullOrEmpty(connection), "connection");

        _configuration = new TConfiguration
        {
            TargetDatabase = new DbConnectionInfo(connection)
        };
    }
    void IDatabaseInitializer<TContext>.InitializeDatabase(TContext context)
    {
        Contract.Requires(context != null, "context");

        if (context.Database.Exists())
        {
            if (!context.Database.CompatibleWithModel(throwIfNoMetadata: false))
            {
                var migrator = new DbMigrator(_configuration);
                migrator.Update();
            }
        }
        else
        {
            context.Database.Create();
            Seed(context);
            context.SaveChanges();
        }


    }
    protected virtual void Seed(TContext context)
    {
    }
}

internal sealed class Configuration : DbMigrationsConfiguration<GumpDatabase>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = true;
        AutomaticMigrationDataLossAllowed = false;
        SetSqlGenerator("MySql.Data.MySqlClient", new MySql.Data.Entity.MySqlMigrationSqlGenerator()); 
    }

    protected override void Seed(GumpDatabase context)
    {
    }
}

class GumpDatabaseInitializer : CreateOrMigrateDatabaseInitializer<GumpDatabase,Gump.Migrations.Configuration>
{
    public GumpDatabaseInitializer()
    {
    }
    protected override void Seed(GumpDatabase context)
    {
        context.Database.ExecuteSqlCommand("CREATE UNIQUE INDEX Name ON Stations (Name)");
        context.Database.ExecuteSqlCommand("CREATE UNIQUE INDEX Name ON Sequences (Name)");
        context.Database.ExecuteSqlCommand("CREATE UNIQUE INDEX StationPartNumber ON StationPartNumbers (StationId,PartNumberId)");
    }
}

最后

static void Main()
{
    Database.SetInitializer<GumpDatabase>(new GumpDatabaseInitializer());
4个回答

16

我认为你已经很接近目标了 - 你可以查找 MigrateDatabaseToLatestVersion 的源代码(它是开源的,链接在这里:http://entityframework.codeplex.com/) - 它相当简单,基本上就是调用 DbMigrator - 据我所见。

你需要做的似乎只是将两者合并 - 使用其中一个作为基础,在其中添加其他功能 - 我认为这应该可以正常工作。

class CreateAndMigrateDatabaseInitializer<TContext, TConfiguration> : CreateDatabaseIfNotExists<TContext>, IDatabaseInitializer<TContext> 
    where TContext : DbContext
    where TConfiguration : DbMigrationsConfiguration<TContext>, new()
{
    private readonly DbMigrationsConfiguration _configuration;
    public CreateAndMigrateDatabaseInitializer()
    {
        _configuration = new TConfiguration();
    }
    public CreateAndMigrateDatabaseInitializer(string connection)
    {
        Contract.Requires(!string.IsNullOrEmpty(connection), "connection");

        _configuration = new TConfiguration
        {
            TargetDatabase = new DbConnectionInfo(connection)
        };
    }
    void IDatabaseInitializer<TContext>.InitializeDatabase(TContext context)
    {
        Contract.Requires(context != null, "context");

        var migrator = new DbMigrator(_configuration);
        migrator.Update();

        // move on with the 'CreateDatabaseIfNotExists' for the 'Seed'
        base.InitializeDatabase(context);
    }
    protected override void Seed(TContext context)
    {
    }
}

这样调用...

Database.SetInitializer(new CreateAndMigrateDatabaseInitializer<GumpDatabase, YourNamespace.Migrations.Configuration>());

实际上,你可以覆盖它(因为它是通用实现),就像你为CreateDatabaseIfNotExists所做的那样(你只需要额外提供Configuration参数) - 然后提供'Seed'即可。

class GumpDatabaseInitializer : CreateAndMigrateDatabaseInitializer<GumpDatabase, YourNamespace.Migrations.Configuration>
{
    protected override void Seed(GumpDatabase context)
    {
        context.Database.ExecuteSqlCommand("CREATE UNIQUE INDEX Name ON Stations (Name)");
    }
}

...然后给它取一个类似于的名字

Database.SetInitializer(new GumpDatabaseInitializer());

编辑:根据评论 - DbMigrator不应运行两次。它始终会检查(花费一点时间)并进行'空白'更新,然后继续执行。但是,以防万一您想要在进入之前“检查”,则应该使用此方法(更改上面的类似部分)...

var migrator = new DbMigrator(_configuration);
if (!context.Database.CompatibleWithModel(throwIfNoMetadata: false))
    if (migrator.GetPendingMigrations().Any())
        migrator.Update();

(这是一个冗余/双重检查 - 如果语句中的其中一个条件满足,应该已经足够。在此处插入一个断点-并查看发生了什么情况,一旦Db被迁移,它就不应该进入。如我所提到的,当我测试时,它可以正常工作。)

编辑:

InitializeDatabase的内部替换为...

var doseed = !context.Database.Exists();
// && new DatabaseTableChecker().AnyModelTableExists(context);
// check to see if to seed - we 'lack' the 'AnyModelTableExists' - could be copied/done otherwise if needed...

var migrator = new DbMigrator(_configuration);
// if (doseed || !context.Database.CompatibleWithModel(throwIfNoMetadata: false))
    if (migrator.GetPendingMigrations().Any())
        migrator.Update();

// move on with the 'CreateDatabaseIfNotExists' for the 'Seed'
base.InitializeDatabase(context);
if (doseed)
{
    Seed(context);
    context.SaveChanges();
}

如果迁移首先运行,这将解决(部分)不需要种子的问题。而且迁移必须在最前面,否则会有问题。

你仍然需要正确执行 - 这只是大意,可能还有其他与 MySQL 等相关的问题需要解决。

注意:如果你有一个空的数据库,仍然不会调用种子。问题在于两个不同的初始化程序的混合。所以你需要解决这个问题 - 要么通过实现 Create... 内部的调用(我们无法调用的那个),要么采用其他方法。


我认为他们已经只针对新的更新(即EF 6)采用了“开源”方式,但至少在这方面源代码应该是相同的。如果你有DbMigrator,那就差不多了。 - NSGaga-mostly-inactive
看起来有一些缺失的部分: using System.Data.Entity.Config; using System.Data.Entity.Internal; using System.Data.Entity.Resources; using System.Data.Entity.Utilities;,在EF 4.3.1中找不到。 - Matt
只需尝试使用DbMigrator...Update——我想就是这样——如果您有MigrateDatabase...initializer,我想您已经拥有了。 - NSGaga-mostly-inactive
当然!看看我刚刚做的修改。它仍然存在我提到的问题:1-删除数据库,2-运行应用程序,新数据库被成功创建,3-修改模型,4-运行应用程序,数据库被成功更新,5-运行应用程序,迁移器更新失败,因为它尝试再次迁移数据库。代码看起来就像是从Microsoft源代码中获取的,但显然某些内容没有被保存或更新。 - Matt
不幸的是,它仍然失败了,只是以不同的方式。从来没有任何待处理的迁移,所以它总是跳过 migrate() 调用。可能是 MySQL 连接器(6.6.5)中的一个 bug,这并不是第一次出现这种情况。 - Matt
显示剩余4条评论

1
要同时进行种子数据和迁移,您只需要使用具有 MigrateDatabaseToLatestVersion 初始化器的迁移。当您为上下文启用迁移时,会创建一个派生自 DbMigrationsConfigurationConfiguration 类,并且您可以重写 Seed 方法以种植您的数据库。请注意,当此方法执行时,数据库可能已经包含种子数据,但 AddOrUpdate 扩展方法方便地帮助您在数据库中进行“upserts”。
这与其他一些数据库初始化程序的 Seed 方法不同,其中仅在初始创建数据库时才种植数据库。但是,当您使用迁移时,可能希望在数据库更改时更改种子数据,使用 MigrateDatabaseToLatestVersion 可以实现这一点。
要将种子数据与迁移结合起来,您需要在新项目中执行以下步骤:
  1. Create a code-first DbContext with associated entities

  2. In the package manager console execute the command Enable-Migrations

  3. In the Migrations folder a Configuration class is generated with a Seed method. You can modify this method to seed your database:

    protected override void Seed(MyContext context) {
      // Add two entities with name "Foo" and "Bar".
      context.MyEntities.AddOrUpdate(
        e => e.Name,
        new MyEntity { Name = "Foo" },
        new MyEntity { Name = "Bar" }
      );
    }
    
  4. You need to create a database initializer that derives from MigrateDatabaseToLatestVersion:

    class MyContextInitializer
      : MigrateDatabaseToLatestVersion<MyContext, Migrations.Configuration> { }
    

    You will also have to configure the initializer either by calling Database.SetInitializer(new MyContextInitializer()) when you application starts or in the App.config file by using the <databaseInitializer/> element.

  5. In the constructor for the generated Configuration class you can enable automatic migrations:

    public Configuration() {
      AutomaticMigrationsEnabled = true
    }
    

    However, in a team you might prefer to not do that. In that case you will have to create an initial migration (unless it was created when you did Enable-Migrations). In the package manager execute the command Add-Migration InitialCreate. This creates the first migration required to create your database.

现在您拥有一个带有迁移和种子方法的 DbContext

总结一下:启用迁移,使用 MigrateDatabaseToLatestVersion 初始化程序,并在启用迁移时生成的 Configuration 类中添加种子数据。


1

Actually it should be:

var migrator = new DbMigrator(_configuration);
if (!context.Database.CompatibleWithModel(false) || migrator.GetPendingMigrations().Any())
    migrator.Update();

因为如果我们进行迁移,与我们的数据库模型无关,例如在任何一个表中插入一行,那么该迁移就不会被执行。


0

虽然 MigrateDatabaseToLatestVersion 实际上会在数据库不存在时创建它,甚至允许您进行种子数据填充,但如果您已经有一个基于 CreateDatabaseIfNotExists 的工作解决方案,并且不想通过测试种子数据的存在来使其复杂化,您可以通过继承以下内容而不是从 CreateDatabaseIfNotExists 继承来使用它:

public class CreateOrMigrateDatabaseInitializer<TContext, TConfiguration> : CreateDatabaseIfNotExists<TContext>, IDatabaseInitializer<TContext>
        where TContext : DbContext
        where TConfiguration : DbMigrationsConfiguration<TContext>, new()
    {

        void IDatabaseInitializer<TContext>.InitializeDatabase(TContext context)
        {
            if (context.Database.Exists())
            {
                if (!context.Database.CompatibleWithModel(throwIfNoMetadata: false))
                {
                    var migrationInitializer = new MigrateDatabaseToLatestVersion<TContext, TConfiguration>(true);
                    migrationInitializer.InitializeDatabase(context);
                }
            }

            base.InitializeDatabase(context);
        }
    }

这是基于之前的答案和 OP 自己的解决方案。这应该也适用于其他提供商,但我只在 SQL Server 上进行了测试。


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