Code first DbMigrator在使用不同的计算机构建时出现错误

44
我们有一个在 SCM 下的项目。当我从我的机器构建并通过 msdeploy 发布到远程服务器时,一切正常。
当我的同事尝试使用从 SCM 新拉取的相同项目在远程服务器上进行相同操作时,实体框架 4.3.1 的 DbMigrator 抛出以下异常:
自动迁移未应用,因为它将导致数据丢失。
事实证明,最初发布到远程服务器的人将成为“赢家”。如果我们删除远程服务器上的数据库,则我的同事可以发布,而我会被锁定。我的发布结果会产生以上相同的错误。 DbMigrator 的配置如下:
        var dbMgConfig = new DbMigrationsConfiguration()
        {
            AutomaticMigrationsEnabled = true,
            //***DO NOT REMOVE THIS LINE, 
            //DATA WILL BE LOST ON A BREAKING SCHEMA CHANGE,
            //TALK TO OTHER PARTIES INVOLVED IF THIS LINE IS CAUSING PROBLEMS    
            AutomaticMigrationDataLossAllowed=false,
            //***DO NOT REMOVE THIS LINE,
            ContextType = typeof(TPSContext),
            MigrationsNamespace = "TPS.Migrations",
            MigrationsAssembly = Assembly.GetExecutingAssembly()
        };

我猜这与新表__MigrationHistory和存储在其行中的长十六进制字符串有关。

我不想对发布到生产环境负全部责任。我应该注意哪些方面?


1
你们能否各自运行 Update-Database -Script -SourceMigration $InitialDatabase -Force 并比较两个输出结果? - bricelam
2
非常感谢你的彻底调查,@spender。你的解决方法正是我所建议的。我会确保我们(Entity Framework团队)为此问题提交一个错误报告。 - bricelam
我们也做软件配置管理。从未遇到过这个问题。但是我们只从构建服务器部署到集成测试服务器。在我的看法中,本地在开发实例上构建,然后部署到服务器上是没有意义的... - Yves M.
啊,没错...一个构建服务器就可以解决所有问题。又是新的一天,新的项目! - spender
1
既然它解决了你最初的问题,你应该将你的工作转化为一个答案并标记为已接受。为什么它在不同的机器上表现不同的问题,从技术上讲是一个单独的问题。 - Kenneth Cochran
显示剩余7条评论
5个回答

8
我们将我们的代码从以下内容进行了更改:

        dbMgConfig.AutomaticMigrationDataLossAllowed = false;
        var mg = new DbMigrator(dbMgConfig);
        mg.Update(null);

to

        dbMgConfig.AutomaticMigrationDataLossAllowed = true;
        var mg = new DbMigrator(dbMgConfig);
        var scriptor = new MigratorScriptingDecorator(mg);
        string script = scriptor.ScriptUpdate(sourceMigration: null, targetMigration: null);
        throw new Exception(script);

这样我们就能观察DbMigrator在远程服务器上所做的更改。

在本问题开头概述的情况下(即同事上传创建数据库,然后我在另一台机器上生成的上传文件),会生成以下SQL语句:

ALTER TABLE [GalleryImages] DROP CONSTRAINT [FK_GalleryImages_Galleries_Gallery_Id]
ALTER TABLE [GalleryImages] DROP CONSTRAINT [FK_GalleryImages_Images_Image_Id]
ALTER TABLE [UserLightboxes] DROP CONSTRAINT [FK_UserLightboxes_Users_User_Id]
ALTER TABLE [UserLightboxes] DROP CONSTRAINT [FK_UserLightboxes_Lightboxes_Lightbox_Id]
ALTER TABLE [ImageLightboxes] DROP CONSTRAINT [FK_ImageLightboxes_Images_Image_Id]
ALTER TABLE [ImageLightboxes] DROP CONSTRAINT [FK_ImageLightboxes_Lightboxes_Lightbox_Id]
DROP INDEX [IX_Gallery_Id] ON [GalleryImages]
DROP INDEX [IX_Image_Id] ON [GalleryImages]
DROP INDEX [IX_User_Id] ON [UserLightboxes]
DROP INDEX [IX_Lightbox_Id] ON [UserLightboxes]
DROP INDEX [IX_Image_Id] ON [ImageLightboxes]
DROP INDEX [IX_Lightbox_Id] ON [ImageLightboxes]
CREATE TABLE [ImageGalleries] (
   [Image_Id] [int] NOT NULL,
   [Gallery_Id] [int] NOT NULL,
   CONSTRAINT [PK_ImageGalleries] PRIMARY KEY ([Image_Id], [Gallery_Id])
)
CREATE TABLE [LightboxImages] (
   [Lightbox_Id] [int] NOT NULL,
   [Image_Id] [int] NOT NULL,
   CONSTRAINT [PK_LightboxImages] PRIMARY KEY ([Lightbox_Id], [Image_Id])
)
CREATE TABLE [LightboxUsers] (
   [Lightbox_Id] [int] NOT NULL,
   [User_Id] [int] NOT NULL,
   CONSTRAINT [PK_LightboxUsers] PRIMARY KEY ([Lightbox_Id], [User_Id])
)
CREATE INDEX [IX_Image_Id] ON [ImageGalleries]([Image_Id])
CREATE INDEX [IX_Gallery_Id] ON [ImageGalleries]([Gallery_Id])
CREATE INDEX [IX_Lightbox_Id] ON [LightboxImages]([Lightbox_Id])
CREATE INDEX [IX_Image_Id] ON [LightboxImages]([Image_Id])
CREATE INDEX [IX_Lightbox_Id] ON [LightboxUsers]([Lightbox_Id])
CREATE INDEX [IX_User_Id] ON [LightboxUsers]([User_Id])
DROP TABLE [GalleryImages]
DROP TABLE [UserLightboxes]
DROP TABLE [ImageLightboxes]
ALTER TABLE [ImageGalleries] ADD CONSTRAINT [FK_ImageGalleries_Images_Image_Id] FOREIGN KEY ([Image_Id]) REFERENCES [Images] ([Id]) ON DELETE CASCADE
ALTER TABLE [ImageGalleries] ADD CONSTRAINT [FK_ImageGalleries_Galleries_Gallery_Id] FOREIGN KEY ([Gallery_Id]) REFERENCES [Galleries] ([Id]) ON DELETE CASCADE
ALTER TABLE [LightboxImages] ADD CONSTRAINT [FK_LightboxImages_Lightboxes_Lightbox_Id] FOREIGN KEY ([Lightbox_Id]) REFERENCES [Lightboxes] ([Id]) ON DELETE CASCADE
ALTER TABLE [LightboxImages] ADD CONSTRAINT [FK_LightboxImages_Images_Image_Id] FOREIGN KEY ([Image_Id]) REFERENCES [Images] ([Id]) ON DELETE CASCADE
ALTER TABLE [LightboxUsers] ADD CONSTRAINT [FK_LightboxUsers_Lightboxes_Lightbox_Id] FOREIGN KEY ([Lightbox_Id]) REFERENCES [Lightboxes] ([Id]) ON DELETE CASCADE
ALTER TABLE [LightboxUsers] ADD CONSTRAINT [FK_LightboxUsers_Users_User_Id] FOREIGN KEY ([User_Id]) REFERENCES [Users] ([Id]) ON DELETE CASCADE
CREATE TABLE [__MigrationHistory] (
   [MigrationId] [nvarchar](255) NOT NULL,
   [CreatedOn] [datetime] NOT NULL,
   [Model] [varbinary](max) NOT NULL,
   [ProductVersion] [nvarchar](32) NOT NULL,
   CONSTRAINT [PK___MigrationHistory] PRIMARY KEY ([MigrationId])
)
BEGIN TRY
   EXEC sp_MS_marksystemobject '__MigrationHistory'
END TRY
BEGIN CATCH
END CATCH
INSERT INTO [__MigrationHistory] ([MigrationId], [CreatedOn], [Model], [ProductVersion]) VALUES ('201203030113082_AutomaticMigration', '2012-03-03T01:13:08.986Z', 0x[removedToShortenPost], '4.3.1')

可以看出,DbMigrator抛出异常的原因是它正试图重命名用于连接多对多关系的3个表,通过反转它们所连接的表的名称,例如将GalleryImages改为ImageGalleriesUserLightboxes改为LightboxUsers

解决方法

这似乎是EF 4.3中的一个错误,其中“association”表的命名似乎是不确定的。鉴于这些类型的表格的名称排序似乎未定义/不确定,我们从不同的角度来解决这个问题,使用流畅的API来强制EF在不同机器上使用一致的命名:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder
            .Entity<Gallery>()
            .HasMany(p => p.Images)
            .WithMany(p => p.Galleries)
            .Map(c =>
            {
                c.MapLeftKey("Gallery_Id");
                c.MapRightKey("Image_Id");
                c.ToTable("GalleryImages");
            });
        modelBuilder
            .Entity<User>()
            .HasMany(p => p.Lightboxes)
            .WithMany(p => p.Users)
            .Map(c =>
            {
                c.MapLeftKey("User_Id");
                c.MapRightKey("Lightbox_Id");
                c.ToTable("UserLightboxes");
            });
        modelBuilder
            .Entity<Image>()
            .HasMany(p => p.Lightboxes)
            .WithMany(p => p.Images)
            .Map(c =>
            {
                c.MapLeftKey("Image_Id");
                c.MapRightKey("Lightbox_Id");
                c.ToTable("ImageLightboxes");
            });
    }

有了这个设置,错误现在消失了。


2
 public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        } 

4
砰!数据不见了。当然,将“AutomaticMigrationDataLossAllowed”设置为“true”可以解决问题。但这也会使现有数据消失。毫无用处。鉴于我在问题中明确解释了“不要删除此行”的代码行“AutomaticMigrationDataLossAllowed=false”,这是一个糟糕的答案。-1。 - spender
这不是现有旧数据库的真实解决方案。我更喜欢被接受的答案。 - Alok

1
我遇到了相同的错误,因此我生成了一个脚本并在查询分析器中运行它。结果发现是关键长度问题:
警告!最大键长度为900字节。索引“PK_dbo.__MigrationHistory”的最大长度为1534个字节。对于某些大值组合,插入/更新操作将失败。
看起来EntityFramework团队已经意识到了这个问题:

http://entityframework.codeplex.com/workitem/1216

不确定这会引起什么问题.....


0

我也遇到了这个问题。奇怪的是,所涉及的表格完全没有数据,即它是空的,但在报告如果应用迁移将会导致数据丢失时,Code First 似乎甚至没有检查这一点。


0

我刚刚遇到了一个类似的非常奇怪的错误,与Entity Framework 6.2.0有关。

Configuration.cs:

public class Configuration : DbMigrationsConfiguration<ApplicationDbContext>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = false;
        AutomaticMigrationDataLossAllowed = false;
    }
...
}

下面的代码在一台机器上引发了StackOverflowException,但在另一台机器上正常工作。
var migrator = new DbMigrator(new Configuration());
if (migrator.GetPendingMigrations().Any())
{
    migrator.Update();
}

通过这种方式升级解决了问题:

Database.SetInitializer(new MigrateDatabaseToLatestVersion<ApplicationDbContext, Configuration>());

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接