如何创建初始化程序来创建和迁移mysql数据库?

时间:2013-04-03 19:20:08

标签: c# mysql migration entity-framework-4.3 initializer

我一直在学习如何使用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
    }
}

或者我可以创建一个配置来迁移db

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调用在两个初始化程序之间切换,但是如果我想创建数据库,如果它不存在,并且如果它是我该怎么做的话还要迁移它?我是否需要创建自定义初始化程序?

由于

根据NSGaga回答编辑

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 个答案:

答案 0 :(得分:16)

我认为你几乎就在那里 - 你可以查找MigrateDatabaseToLatestVersion的源代码(它的开源http://entityframework.codeplex.com/) - 它非常简单,它的作用几乎就是调用{{1 - 据我所见。

所有你需要做的似乎是合并两个 - 使用一个或另一个作为基础,在那里添加其他功能 - 这应该工作正常我认为。

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>()); 所做的那样(你只需要配置额外的'param') - 并且只提供'种子'。

CreateDatabaseIfNotExists

...并称之为

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

修改 根据评论 - DbMigrator不应该运行两次。它总是检查(花费一点时间)并进行“空白”更新并继续前进。但是,如果你想在进入之前删除它并“检查” - 这应该有效(改变上面的类似部分)......

Database.SetInitializer(new GumpDatabaseInitializer());

(这是一个冗余/双重检查 - 其中一个if-s应该足够了。在那里休息一下 - 看看究竟发生了什么,它不应该进入 - 一旦Db被迁移。正如我所提到的,有效我测试时很好。

修改

var migrator = new DbMigrator(_configuration); if (!context.Database.CompatibleWithModel(throwIfNoMetadata: false)) if (migrator.GetPendingMigrations().Any()) migrator.Update(); 的内部替换为...

InitializeDatabase

如果首先迁移,这可以解决(中途)非播种问题。迁移必须是第一个,否则你就会遇到问题。

你仍然需要正确地做到这一点 - 如果不是你可能需要的话,这是要点 - 但如果有任何问题与MySQL等,可能还有一些更多的工作在这里。

注意:如果你有一个数据库,仍然没有调用播种,但它是空的。问题是混合两种不同的初始化器。所以你必须解决这个问题 - 通过实现内部的Create ...(我们不能调用的那个)或其他东西。

答案 1 :(得分:1)

实际上它应该是:

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

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

答案 2 :(得分:1)

要同时执行这两项操作(种子和迁移),您只需使用MigrateDatabaseToLatestVersion初始化程序进行迁移。为上下文启用迁移时,会创建从Configuration派生的DbMigrationsConfiguration类,您可以覆盖Seed方法来为数据库设定种子。请注意,在执行此方法时,数据库可能已包含种子数据,但AddOrUpdate扩展方法可以方便地帮助您在数据库中进行“upserts”。

与某些其他数据库初始化器的Seed方法相比,这是不同的,其中数据库仅在最初创建时播种。但是,当您使用迁移时,您可能希望在数据库更改时更改种子数据,并使用MigrateDatabaseToLatestVersion使其成为可能。

要将种子与迁移相结合,您必须在新项目中执行以下步骤:

  1. 使用关联实体创建代码优先DbContext

  2. 在包管理器控制台中,执行命令Enable-Migrations

  3. Migrations文件夹中,使用Configuration方法生成Seed类。您可以修改此方法以为数据库设定种子:

    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. 您需要创建一个派生自MigrateDatabaseToLatestVersion的数据库初始化程序:

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

    您还必须通过在应用程序启动时调用Database.SetInitializer(new MyContextInitializer())或使用<databaseInitializer/>元素调用App.config文件来配置初始值设定项。

  5. 在生成的Configuration类的构造函数中,您可以启用自动迁移:

    public Configuration() {
      AutomaticMigrationsEnabled = true
    }
    

    但是,在团队中,您可能不愿意这样做。在这种情况下,您将必须创建初始迁移(除非它是在您Enable-Migrations时创建的)。在包管理器中执行命令Add-Migration InitialCreate。这将创建创建数据库所需的第一次迁移。

  6. 此时,您有DbContext迁移和Seed方法。

    总而言之:启用迁移,使用MigrateDatabaseToLatestVersion初始值设定项,并在启用迁移时生成的Configuration类中添加种子数据。

答案 3 :(得分: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。