Entity Framework - миграция - первый код - седиментация на миграцию
Я изучаю Migrations, чтобы очистить наши процессы развертывания. Чем меньше ручное вмешательство требуется при нажатии на изменение в производстве, тем лучше.
Я столкнулся с тремя основными корягами с системой миграции. Это шоу-стопперы, если я не могу понять, как их можно обойти.
1. Как добавить данные Seed для каждой миграции:
Я выполняю команду add-migration, которая создает новый файл миграции с функциями Up и Down. Теперь я хочу автоматически вносить изменения в данные с изменениями Up и Down. Я не хочу добавлять данные Seed в метод Configuration.Seed, поскольку он выполняется для всех миграций, который заканчивается всеми видами проблем с дублированием.
2. Если это невозможно, как избежать дублирования?
У меня есть перечисление, которое я прокручиваю, чтобы добавить значения в базу данных.
foreach(var enumValue in Enum.GetValues(typeof(Access.Level)))
{
context.Access.AddOrUpdate(
new Access { AccessId = ((int)enumValue), Name = enumValue.ToString() }
);
}
context.SaveChanges();
Несмотря на то, что я использую AddOrUpdate, я все равно получаю дубликаты в базе данных. Вышеприведенный код приводит меня к моей третьей и последней проблеме:
3. Как я могу засеять первичные ключи?
Мое перечисление с приведенным выше кодом:
public class Access
{
public enum Level
{
None = 10,
Read = 20,
ReadWrite = 30
}
public int AccessId { get; set; }
public string Name { get; set; }
}
Я указываю значения, которые я хочу в качестве своего первичного ключа, но Entity Framework, похоже, игнорирует его. Они все еще в конечном итоге составляют 1,2,3. Как мне получить 10,20,30?
Являются ли эти ограничения EF в данный момент или являются ли они преднамеренными ограничениями для предотвращения какой-либо другой катастрофы, которую я не вижу?
Ответы
Ответ 1
- Когда у меня есть фиксированные данные, которые я хочу вставить с переносом, я помещаю вставки непосредственно в перенастройку Up(), используя вызовы
Sql("Insert ...")
. См. Примечание наполовину вниз по этой странице: как вставить фиксированные данные
- Вы предотвращаете дублирование в методе Seed, вызывая переопределение AddOrUpdate, которое принимает выражение идентификатора, определяющее естественный ключ - см. этот ответ и эта запись в блоге.
- Первичные ключи, которые являются целыми, по умолчанию создаются как поля для идентификации. Чтобы указать иначе, используйте атрибут
[DatabaseGenerated(DatabaseGeneratedOption.None)]
Я думаю, что это хорошее объяснение Методы инициализации и семян
Вот пример использования метода AddOrUpdate:
foreach(var enumValue in Enum.GetValues(typeof(Access.Level)))
{
context.Access.AddOrUpdate(
x => x.Name, //the natural key is "Name"
new Access { AccessId = ((int)enumValue), Name = enumValue.ToString() }
);
}
Ответ 2
В качестве возможного решения пункта 1 я сделал реализацию стратегии IDatabaseInitializer
, которая будет запускать метод Seed для каждой ожидающей миграции, вам нужно будет реализовать пользовательский интерфейс IMigrationSeed
в каждом из ваших DbMigration
, метод Seed
будет реализован сразу после методов Up
и Down
для каждого класса миграции.
Это помогает решить две проблемы для меня:
- Миграция модели групповой базы данных с миграцией данных базы данных (или посещением)
- Проверьте, какая часть кода миграции семян должна быть запущена, а не проверка данных в базе данных, но с использованием уже известных данных, которые были созданы только для модели базы данных.
Интерфейс выглядит следующим образом:
public interface IMigrationSeed<TContext>
{
void Seed(TContext context);
}
Ниже приведена новая реализация, которая будет называть этот метод Seed
public class CheckAndMigrateDatabaseToLatestVersion<TContext, TMigrationsConfiguration>
: IDatabaseInitializer<TContext>
where TContext : DbContext
where TMigrationsConfiguration : DbMigrationsConfiguration<TContext>, new()
{
public virtual void InitializeDatabase(TContext context)
{
var migratorBase = ((MigratorBase)new DbMigrator(Activator.CreateInstance<TMigrationsConfiguration>()));
var pendingMigrations = migratorBase.GetPendingMigrations().ToArray();
if (pendingMigrations.Any()) // Is there anything to migrate?
{
// Applying all migrations
migratorBase.Update();
// Here all migrations are applied
foreach (var pendingMigration in pendingMigrations)
{
var migrationName = pendingMigration.Substring(pendingMigration.IndexOf('_') + 1);
var t = typeof(TMigrationsConfiguration).Assembly.GetType(
typeof(TMigrationsConfiguration).Namespace + "." + migrationName);
if (t != null
&& t.GetInterfaces().Any(x => x.IsGenericType
&& x.GetGenericTypeDefinition() == typeof(IMigrationSeed<>)))
{
// Apply migration seed
var seedMigration = (IMigrationSeed<TContext>)Activator.CreateInstance(t);
seedMigration.Seed(context);
context.SaveChanges();
}
}
}
}
}
Хорошо, что у вас есть реальный контекст EF для манипулирования данными семян, как и стандартная реализация EF Seed. Однако это может стать странным, если, например, вы решили удалить таблицу, которая была выбрана в предыдущей миграции, вам придется соответствующим образом реорганизовать существующий код семян.
EDIT:
В качестве альтернативы реализации метода семян после Up и Down вы можете создать частичный класс того же класса миграции, я нашел это полезным, поскольку он позволяет мне безопасно удалить класс миграции, когда я хочу повторно засеять ту же миграцию.
Ответ 3
Привет, я нашел очень полезную информацию для вашей проблемы в этой ссылке:
Safari Books Online
"1. Как добавить данные Seed для каждой миграции:"
Как вы видите в примере, вам нужно создать новую настройку для посева.
Эта конфигурация семян должна вызываться после миграции.
public sealed class Configuration : DbMigrationsConfiguration
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(SafariCodeFirst.SeminarContext context)
{
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data. E.g.
//
// context.People.AddOrUpdate(
// p => p.FullName,
// new Person { FullName = "Andrew Peters" },
// new Person { FullName = "Brice Lambson" },
// new Person { FullName = "Rowan Miller" }
// );
//
}
}
"2. Если это невозможно, как избежать дублирования?
AddOrUpdate Должен помочь вам точно записать дубликаты, если вы получите ошибку здесь, возможно, у вас возникнет ошибка конфигурации после стека вызовов. См. Пример!
"3. Как я могу засеять первичные ключи?
Здесь также указано ваше ключевое определение. Если ваш ключ DatabaseGenerated(DatabaseGeneratedOption.Identity)
, чем вам не нужно его предоставлять. В некоторых других сенариях вам нужно создать новый, зависящий от типа ключа.
"Являются ли эти ограничения EF на данный момент или являются ли они преднамеренными ограничениями для предотвращения какой-либо другой катастрофы, которую я не вижу?"
Не то, чтобы я знаю!
Ответ 4
ОК, так что, немного сбившись, мне удалось отправить bash EF.
Вот что я сделал:
1. Невозможно найти данные для конкретной миграции. Все это должно войти в общий метод Configuration.Seed.
2. Чтобы избежать дубликатов, мне пришлось сделать 2 вещи.
Для моих перечислений я написал следующий код семени:
foreach (var enumValue in Enum.GetValues(typeof(Access.Level)))
{
var id = (int)enumValue;
var val = enumValue.ToString();
if(!context.Access.Any(e => e.AccessId == id))
context.Access.Add(
new Access { AccessId = id, Name = val }
);
}
context.SaveChanges();
Итак, в основном, просто проверяя, существует ли он и добавляет, если не
3. Чтобы работа над ней работала, вы должны иметь возможность вставлять значения основного ключа. К счастью для меня эта таблица всегда будет иметь одни и те же статические данные, чтобы я мог отключить автоматический приращение. Для этого код выглядит следующим образом:
public class Access
{
public enum Level
{
None = 10,
Read = 20,
ReadWrite = 30
}
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int AccessId { get; set; }
public string Name { get; set; }
}