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

谢谢

根据 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

4 回答 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(您只需为配置提供额外的“参数”) - 只需提供“种子”。

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();

(这是一个冗余/双重检查 - 一个 if-s 应该就足够了。在那里休息一下 - 看看到底发生了什么,它不应该进入 - 一旦迁移 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... 在内部执行的操作(我们无法调用的调用)或其他方式。

于 2013-04-03T19:35:13.210 回答
1

其实应该是:

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

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

于 2013-10-10T06:51:12.867 回答
1

要同时执行(种子和迁移),您实际上只需要使用带有MigrateDatabaseToLatestVersion初始化程序的迁移。当您为您的上下文启用迁移时,将创建一个Configuration派生类DbMigrationsConfiguration,您可以覆盖该Seed方法来为您的数据库播种。请注意,当此方法执行时,数据库可能已经包含种子数据,但AddOrUpdate扩展方法可以方便地帮助您在数据库中进行“更新插入”。

这与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())在应用程序启动时调用或在App.config文件中使用<databaseInitializer/>元素来配置初始化程序。

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

    public Configuration() {
      AutomaticMigrationsEnabled = true
    }
    

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

此时你有一个DbContextwith migrations 和一个Seed方法。

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

于 2015-09-23T14:16:44.423 回答
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 进行了测试。

于 2017-03-09T09:01:56.810 回答