Как создать инициализатор для создания и переноса базы данных 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
}
}
Или я могу создать конфигурацию для переноса БД
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 ответа
Я думаю, что вы в значительной степени там - вы можете посмотреть исходный код для 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-ов должно быть достаточно. Поставьте перерыв там - и посмотрите, что именно происходит, он не должен входить - после переноса 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... делает внутри (этот вызов мы не можем вызвать), либо что-то еще.
На самом деле это должно быть:
var migrator = new DbMigrator(_configuration);
if (!context.Database.CompatibleWithModel(false) || migrator.GetPendingMigrations().Any())
migrator.Update();
потому что если у нас есть миграция, которая не связана с нашей моделью базы данных, например, вставка строки в любую из наших таблиц, миграция не будет выполнена.
Для того, чтобы сделать и то, и другое (семя и миграция), вам действительно нужно использовать миграцию только с 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
класс, сгенерированный при включении миграции.
Хотя 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);
}
}
Это основано на предыдущих ответах и собственном решении ОП. Это должно работать и с другими провайдерами, но я тестировал только с SQL Server.