How do I force Entity Framework to mark a particular migration as having been applied?

前端 未结 3 1549
-上瘾入骨i
-上瘾入骨i 2021-02-04 05:01

I\'m using Entity Framework 5 in a project, and I\'ve got Migrations enabled.

Here\'s the scenario:

A new developer (dev1) comes on and builds the project from

相关标签:
3条回答
  • 2021-02-04 05:13

    Entity Framework migrations in a team environment can be tricky. Especially when combined with source control. It sounds like you are running into issues with different code-first migrations being run against different dev databases.

    All the migrations that have been run against a particular database are stored in the __MigrationHistory table. If you have a migration file in your project and EF doesn't see it in the __MigrationHistory table it's going to try and execute it when you run Update-Database. If you trick EF into thinking that it's already applied these migration files by inserting the records into MigrationHistory it will break one of the nicest features of EF code first. You won't be able to role back your migrations using update-database -TargetMigration.

    If each dev has their own set of migrations and it's possible for them to have overlapping changes this can result in all sorts of sql errors when you run update-database.

    My solution to this has been to ignore all migration files having to do with active development (since they tend to be tweeked a lot). When a dev gets a new change set they just create their own local migrations.

    Once I am ready to release a new feature to production I roll all those changes into one big migration and I usually name it after the version number that I am incrementing my app to. I check these migration files into source control. These act as my master migration files to bring any new database up to date with production.

    When I make a new one of these master migrations all devs role back their local changes to the last major version, delete the ones they don't need anymore (because they are covered in master migration), then run update-database.

    0 讨论(0)
  • 2021-02-04 05:19

    I took Doug's technique and created a DatabaseInitializer that automates it.

    Just use

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

    In your DbContext and it will create a new DB if one does not exist and update the migration history table to include all existing migrations.

    Here is the class:

    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");
            }
        }
    }
    
    0 讨论(0)
  • 2021-02-04 05:24

    I figured out a hack.

    Run Update-Database -Script

    Pick out all the migrations that have already been run

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

    Open Sql Server Management Studio, and run those sql statements manually.

    New migrations should work fine.

    0 讨论(0)
提交回复
热议问题