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

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 в данный момент или являются ли они преднамеренными ограничениями для предотвращения какой-либо другой катастрофы, которую я не вижу?

4b9b3361

Ответ 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; }
}