Как заставить Entity Framework отмечать конкретную миграцию как применяемую? - программирование
Подтвердить что ты не робот

Как заставить Entity Framework отмечать конкретную миграцию как применяемую?

Я использую Entity Framework 5 в проекте, и у меня включен Migrations.

Здесь сценарий:

Появляется новый разработчик (dev1) и строит проект из источника. Существуют существующие миграции, поскольку предыдущие разработчики работали над проектом в прошлом.

Когда этот разработчик впервые запускает проект ASP.NET MVC, база данных создается автоматически, и ошибок не возникает.

После этого другой разработчик (dev2) добавляет новую миграцию. Когда Dev1 пытается запустить Update-Database, все попытки предыдущих выполняются. Но они уже применялись, поскольку они были частью исходной модели, поскольку Dev1 видел ее. Это часто приводит к ошибке схемы, поскольку она пытается применить изменение схемы, которое уже существует.

Таким образом, оптимально было бы просто просто "ускорить" локальную базу данных для текущей миграции. Но я не знаю, как это сделать. Альтернативно, есть ли другой способ инициализации базы данных, чтобы я мог применять все миграции во время инициализации?

4b9b3361

Ответ 1

Я понял, взломать.

Выполнить Update-Database -Script

Выберите все миграции, которые уже были запущены

INSERT INTO [__MigrationHistory] ([MigrationId], [Model], [ProductVersion]) VALUES

Откройте Sql Server Management Studio и запустите эти операторы sql вручную.

Новые миграции должны работать нормально.

Ответ 2

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

Все миграции, которые были выполнены против конкретной базы данных, хранятся в таблице __MigrationHistory. Если у вас есть файл миграции в вашем проекте, и EF не видит его в таблице __MigrationHistory, он попытается выполнить его при запуске Update-Database. Если вы обманете EF, подумав, что он уже применил эти файлы миграции, вставив записи в MigrationHistory, он сначала сломает одну из лучших функций EF-кода. Вы не сможете воспроизвести свои миграции с помощью update-database -TargetMigration.

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

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

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

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

Ответ 3

Я взял технику Дуга и создал DatabaseInitializer, который его автоматизирует.

Просто используйте

 Database.SetInitializer(new CreateDbWithMigrationHistoryIfNotExists<EntityContext, Configuration>());

В вашем DbContext и создаст новую БД, если ее не существует, и обновите таблицу истории миграции, чтобы включить все существующие миграции.

Вот класс:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Infrastructure;
using System.Data.Entity.Migrations.Model;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml.Linq;

namespace EfStuff
{
    public class CreateDbWithMigrationHistoryIfNotExists<TContext, TMigrationsConfiguration> :
        IDatabaseInitializer<TContext>
        where TContext : DbContext
        where TMigrationsConfiguration : DbMigrationsConfiguration<TContext>
    {
        private readonly Regex _pattern = new Regex("ProviderManifestToken=\"([^\"]*)\"");
        private readonly TMigrationsConfiguration _config;

        public CreateDbWithMigrationHistoryIfNotExists()
        {
            _config = Activator.CreateInstance<TMigrationsConfiguration>();
        }

        public void InitializeDatabase(TContext context)
        {
            if (context.Database.Exists()) return;
            context.Database.Create();

            var operations = GetInsertHistoryOperations();
            if (!operations.Any()) return;
            var providerManifestToken = GetProviderManifestToken(operations.First().Model);
            var sqlGenerator = _config.GetSqlGenerator(GetProviderInvariantName(context.Database.Connection));
            var statements = sqlGenerator.Generate(operations, providerManifestToken);
            statements.ToList().ForEach(x => context.Database.ExecuteSqlCommand(x.Sql));
        }

        private IList<InsertHistoryOperation> GetInsertHistoryOperations()
        {
            return
                _config.MigrationsAssembly.GetTypes()
                       .Where(x => typeof (DbMigration).IsAssignableFrom(x))
                       .Select(migration => (IMigrationMetadata) Activator.CreateInstance(migration))
                       .Select(metadata => new InsertHistoryOperation("__MigrationHistory", metadata.Id,
                                                                      Convert.FromBase64String(metadata.Target)))
                       .ToList();
        }

        private string GetProviderManifestToken(byte[] model)
        {
            var targetDoc = Decompress(model);
            return _pattern.Match(targetDoc.ToString()).Groups[1].Value;
        }

        private static XDocument Decompress(byte[] bytes)
        {
            using (var memoryStream = new MemoryStream(bytes))
            {
                using (var gzipStream = new GZipStream(memoryStream, CompressionMode.Decompress))
                {
                    return XDocument.Load(gzipStream);
                }
            }
        }

        private static string GetProviderInvariantName(DbConnection connection)
        {
            var type = DbProviderServices.GetProviderFactory(connection).GetType();
            var assemblyName = new AssemblyName(type.Assembly.FullName);
            foreach (DataRow providerRow in (InternalDataCollectionBase) DbProviderFactories.GetFactoryClasses().Rows)
            {
                var typeName = (string) providerRow[3];
                var rowProviderFactoryAssemblyName = (AssemblyName) null;
                Type.GetType(typeName, (a =>
                                            {
                                                rowProviderFactoryAssemblyName = a;
                                                return (Assembly) null;
                                            }), ((_, __, ___) => (Type) null));
                if (rowProviderFactoryAssemblyName != null)
                {
                    if (string.Equals(assemblyName.Name, rowProviderFactoryAssemblyName.Name,
                                      StringComparison.OrdinalIgnoreCase))
                    {
                        if (DbProviderFactories.GetFactory(providerRow).GetType().Equals(type))
                            return (string) providerRow[2];
                    }
                }
            }
            throw new Exception("couldn't get the provider invariant name");
        }
    }
}