19

我在项目中使用 Entity Framework 5,并且启用了迁移。

这是场景:

新的开发人员 (dev1) 出现并从源代码构建项目。由于以前的开发人员过去一直在从事该项目,因此存在现有的迁移。

当该开发人员第一次运行 ASP.NET MVC 项目时,数据库会自动构建,并且不会出现任何错误。

然而,在那之后,另一个开发人员 (dev2) 添加了一个新的迁移。当 Dev1 尝试运行Update-Database时,之前的所有迁移都会尝试运行。但是它们已经被应用了,因为它们是Dev1 看到的初始模型的一部分。这通常会导致架构错误,因为它试图应用已经存在的架构更改。

因此,最好将本地数据库“快进”到当前迁移。但我不知道有什么方法可以做到这一点。或者,是否有其他方法来初始化数据库,以便我可以在初始化期间应用所有迁移?

4

3 回答 3

25

我想出了一个技巧。

Update-Database -Script

挑选出所有已经运行的迁移

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

打开 Sql Server Management Studio,并手动运行这些 sql 语句。

新的迁移应该可以正常工作。

于 2013-01-11T20:58:02.160 回答
4

团队环境中的实体框架迁移可能很棘手。尤其是与源代码控制结合使用时。听起来您遇到了针对不同开发数据库运行不同代码优先迁移的问题。

针对特定数据库运行的所有迁移都存储在 __MigrationHistory 表中。如果您的项目中有一个迁移文件并且 EF 在 __MigrationHistory 表中没有看到它,它将在您运行 Update-Database 时尝试执行它。如果您通过将记录插入 MigrationHistory 来欺骗 EF 认为它已经应用了这些迁移文件,它将首先破坏 EF 代码的最佳功能之一。您将无法使用 update-database -TargetMigration 对迁移进行角色化。

如果每个开发人员都有自己的一组迁移,并且他们可能有重叠的更改,这可能会在您运行 update-database 时导致各种 sql 错误。

我对此的解决方案是忽略所有与主动开发有关的迁移文件(因为它们往往会被大量修改)。当开发人员获得新的更改集时,他们只需创建自己的本地迁移。

一旦我准备好将一项新功能发布到生产环境中,我会将所有这些更改整合到一个大迁移中,并且我通常以我将应用程序递增到的版本号来命名它。我将这些迁移文件检查到源代码管理中。这些充当我的主迁移文件,以使任何新数据库与生产保持同步。

当我创建一个新的这些主迁移时,所有开发人员都将他们的本地更改恢复到上一个​​主要版本,删除他们不再需要的那些(因为它们已包含在主迁移中),然后运行 ​​update-database。

于 2013-01-11T21:04:56.267 回答
1

我采用了 Doug 的技术并创建了一个自动化它的 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");
        }
    }
}
于 2013-05-14T17:56:14.380 回答