Подтвердить что ты не робот

Как создать инициализатор для создания и миграции базы данных mysql?

Я изучаю, как использовать 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());
4b9b3361

Ответ 1

Я думаю, что вы там очень много - вы можете найти исходный код для 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());

EDIT: На основе комментариев - DbMigrator не должен запускаться дважды. Он всегда проверяет (тратит немного времени) и делает "пустое" обновление и движется дальше. Однако на всякий случай, если вы хотите удалить это и "проверить" перед входом - это должно сработать (изменить аналогичную фигуру выше)...

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

(это избыточная/двойная проверка - одного из if-s должно быть достаточно. Положите разрыв - и посмотрите, что именно происходит, он не должен входить - как только Db будет перенесен. Как я уже упоминал, работает отлично, когда я его тестирую.

EDIT:

Замените внутри 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 и т.д., вероятно, здесь будет работать еще одна работа.

Примечание.. Сегмент не вызывается, если у вас есть db, но он пуст. Проблема заключается в смешении двух разных инициализаторов. Таким образом, вам нужно будет это решить - либо путем реализации того, что Create... делает внутри (этот вызов мы не можем вызывать), либо что-то еще.

Ответ 2

На самом деле это должно быть:

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

потому что если у нас есть миграция, которая не связана с нашей моделью db, например, вставив строку в любую из наших таблиц, миграция не будет выполнена.

Ответ 3

Для выполнения обоих (семян и миграции) вам действительно нужно использовать миграции с инициализатором MigrateDatabaseToLatestVersion. Когда вы включаете миграции для своего контекста, создается класс Configuration, полученный из DbMigrationsConfiguration, и вы можете переопределить метод Seed, чтобы засеять ваши база данных. Обратите внимание, что база данных может уже содержать данные семени, когда этот метод выполняется, но метод расширения AddOrUpdate удобно помогает вам создавать "upserts" в вашей базе данных.

Это отличается от метода Seed для некоторых других интериализаторов баз данных, где база данных только высевается, когда она изначально создана. Однако, когда вы используете миграции, вы можете захотеть изменить исходные данные при изменении базы данных и использовать MigrateDatabaseToLatestVersion. Это возможно.

Чтобы объединить посев с миграциями, вам нужно будет выполнить следующие шаги в новом проекте:

  • Создайте первый код DbContext со связанными объектами

  • В консоли диспетчера пакетов выполните команду Enable-Migrations

  • В папке 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" }
      );
    }
    
  • Вам нужно создать инициализатор базы данных, полученный из MigrateDatabaseToLatestVersion:

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

    Вам также придется настроить инициализатор либо путем вызова Database.SetInitializer(new MyContextInitializer()) при запуске приложения, либо в файле App.config с помощью <databaseInitializer/>.

  • В конструкторе сгенерированного класса Configuration вы можете включить автоматическую миграцию:

    public Configuration() {
      AutomaticMigrationsEnabled = true
    }
    

    Однако в команде вы можете не делать этого. В этом случае вам придется создать начальную миграцию (если она не была создана, когда вы сделали Enable-Migrations). В диспетчере пакетов выполните команду Add-Migration InitialCreate. Это создает первую миграцию, необходимую для создания вашей базы данных.

В этот момент у вас есть DbContext с миграциями и метод Seed.

Итак, чтобы суммировать его: Включите миграцию, используйте инициализатор MigrateDatabaseToLatestVersion и добавьте данные семени в класс Configuration, который был сгенерирован при включении миграции.

Ответ 4

В то время как 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.