代码优先迁移和初始化错误

我不确定如何使用代码首次迁移function。 根据我的理解,它应该创建我的数据库(如果它已经不存在),并根据迁移文件将其更新为最新的模式。 但我正在努力解决这个问题,因为我总是遇到很多错误而且我不确定如何正确使用它。

internal class Program { private static void Main() { EntityFrameworkProfiler.Initialize(); Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0"); Database.SetInitializer(new MigrateDatabaseToLatestVersion()); using (var context = new MyContext()) { var exists = context.Database.Exists(); if (!exists) { context.Database.Create(); } var element = context.Dummies.FirstOrDefault(); } } } public class MyContext : DbContext { public MyContext() : base(string.Format(@"DataSource=""{0}""", @"C:\Users\user\Desktop\MyContext.sdf")) { } public DbSet Dummies { get; set; } } internal sealed class Configuration : DbMigrationsConfiguration { public Configuration() { AutomaticMigrationsEnabled = true; } protected override void Seed(CodeFirstTest.MyContext context) { } } 

使用Entity Framework Profiler我检查执行的语句。 当我运行没有数据库的程序时,我得到以下输出:

– 语句#1 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__ MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

– 语句#2 WARN:System.Data.SqlServerCe.SqlCeException(0x80004005):指定的表不存在。 位于System.Data.SqlServerCe.SqlCeCommand.CompileQueryPlan()的System.Data.SqlServerCe.SqlCeCommand.ProcessResults(Int32hr)中的[__ MigrationAstory],位于System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior behavior,String method,ResultSetOptions options)at at位于System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteReader(CommandBehavior行为)的System.Data.SqlServerCe.SqlCeCommand.ExecuteReader(CommandBehavior behavior),位于HibernatingRhinos.Profiler.Appender.ProfiledDataAccess的System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteDbDataReader(CommandBehavior行为)。 ProfiledCommand.ExecuteDbDataReader(CommandBehavior行为)

– 语句#3 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__ MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

– 语句#4 WARN:System.Data.SqlServerCe.SqlCeException(0x80004005):指定的表不存在。 位于System.Data.SqlServerCe.SqlCeCommand.CompileQueryPlan()的System.Data.SqlServerCe.SqlCeCommand.ProcessResults(Int32hr)中的[__ MigrationAstory],位于System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior behavior,String method,ResultSetOptions options)at at位于System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteReader(CommandBehavior行为)的System.Data.SqlServerCe.SqlCeCommand.ExecuteReader(CommandBehavior behavior),位于HibernatingRhinos.Profiler.Appender.ProfiledDataAccess的System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteDbDataReader(CommandBehavior行为)。 ProfiledCommand.ExecuteDbDataReader(CommandBehavior行为)

– 语句#5 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__ MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

– 语句#6 WARN:System.Data.SqlServerCe.SqlCeException(0x80004005):指定的表不存在。 位于System.Data.SqlServerCe.SqlCeCommand.CompileQueryPlan()的System.Data.SqlServerCe.SqlCeCommand.ProcessResults(Int32hr)中的[__ MigrationAstory],位于System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior behavior,String method,ResultSetOptions options)at at位于System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteReader(CommandBehavior行为)的System.Data.SqlServerCe.SqlCeCommand.ExecuteReader(CommandBehavior behavior),位于HibernatingRhinos.Profiler.Appender.ProfiledDataAccess的System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteDbDataReader(CommandBehavior行为)。 ProfiledCommand.ExecuteDbDataReader(CommandBehavior行为)

– 语句#7 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__ MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

– 语句#8 WARN:System.Data.SqlServerCe.SqlCeException(0x80004005):指定的表不存在。 位于System.Data.SqlServerCe.SqlCeCommand.CompileQueryPlan()的System.Data.SqlServerCe.SqlCeCommand.ProcessResults(Int32hr)中的[__ MigrationAstory],位于System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior behavior,String method,ResultSetOptions options)at at位于System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteReader(CommandBehavior行为)的System.Data.SqlServerCe.SqlCeCommand.ExecuteReader(CommandBehavior behavior),位于HibernatingRhinos.Profiler.Appender.ProfiledDataAccess的System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteDbDataReader(CommandBehavior行为)。 ProfiledCommand.ExecuteDbDataReader(CommandBehavior行为)

– 语句#9以隔离级别开始事务:可序列化

– 语句#10 CREATE TABLE [Dummies]([Name] nvarchar NOT NULL,CONSTRAINT [PK_Dummies] PRIMARY KEY([Name]))

– 语句#11 CREATE TABLE [ MigrationHistory]([MigrationId] nvarchar NOT NULL,[CreatedOn] [datetime] NOT NULL,[Model] [image] NOT NULL,[ProductVersion] nvarchar NOT NULL,CONSTRAINT [PK _MigrationHistory] ​​PRIMARY KEY ([MigrationId]))

– 声明#12 INSERT INTO [__ MigigrationHistory]([MigrationId],[CreatedOn],[Model],[ProductVersion])VALUES(’201207261524579_InitialCreate’,’2012-07-26T15:24:58.523’,0x1F8B080,’4.3。 1′ )

– 声明#13提交事务

– 声明#14 SELECT TOP(1)[c]。[Name] AS [Name] FROM [Dummies] AS [c]

正如您所看到的,它在实际创建数据库之前尝试访问数据库四次 。 这似乎不对。 当我使用现有数据库启动应用程序时,它将在执行任何实际查询之前查询数据库7次。 请注意,这发生在context.Database.Create() ,而不是.Exists()

我的配置的种子方法永远不会被调用,但构造函数是。

这一切看起来都非常错误和令人困惑。 我希望有人可以告诉我为什么错误在开始时经常发生,以及为什么我的种子方法根本不被调用。

我正在使用SqlServer compact和Entity Framework的最新稳定版本。

package id =“EntityFramework”version =“4.3.1”targetFramework =“net40”

package id =“Microsoft.SqlServer.Compact”version =“4.0.8854.2”targetFramework =“net40”

似乎有很多方法可以配置entity framework,每个人都有自己最好的方法。 我所能提供的只是基于我们在工作中标准化的内容。 很多这是开发者的偏好。 我的偏好恰好是尽可能地控制,所以我总是能够准确理解发生了什么,什么时候发生。

自动迁移

首先,虽然自动迁移可能很方便,但它们会带来很多麻烦,特别是当项目增长和/或数据变得更加复杂时。 在我看来,任何商业/生产系统都应该有更多的控制权。 我们总是通过设置AutomaticMigrationsEnabled = false;来关闭所有主要项目的自动迁移AutomaticMigrationsEnabled = false; 。 当我们希望它完成时,我们显式地运行我们的迁移(在dev上,这是在visual studio中的包管理器控制台中,通过键入Update-Database ,在生产中我们编写了我们自己的小迁移实用程序,它只是调用迁移到最新的代码 – 但是没有是自动的)。

@ Terric的回答让我感到害怕,自动迁移和数据丢失都是允许的! 我不想成为部署解决方案的人并清除一些重要数据,因为执行错误的列更改会导致数据丢失。 作为旁注,当我们在dev中明确地运行迁移时,我经常使用-v开关来进行详细的ouptut( Update-Database -v )。 这使您可以查看正在执行的SQL以及任何适当的故障/警告。

我们的经验是,在您进行多次迁移后,更改这些设置并不顺利。 我不确定跟踪它的位置,但启动一个禁用自动迁移的新项目可确保不会发生任何意外情况。

就个人而言,我将删除你有MigrateDatabaseToLatestVersion的初始化MigrateDatabaseToLatestVersion并在我想要的时候自己运行迁移器(通过包管理器控制台或通过我自己的显式代码)。

创建数据库(如果不存在)

此行为由DatabaseInitializer(实际上不是EntityFramework本身)提供。 CreateDatabaseIfNotExists初始值设定项内置于EntityFramework中,在某些版本中是默认值。 但是,我并不是所有推断的应用程序行为的东西。 在我看来,我想要更多的控制权。

这个人有一个自定义数据库初始化程序的例子,它inheritance自内置的CreateDatabaseIfNotExists 。 但是你总是可以创建自己的并实现你想要查看的任何逻辑(包括数据库的创建)。 同样,这只是避免意外行为。 我作为开发人员的个人偏好是密切控制这些东西,除非我只是在使用模型或测试项目。

超级简单的自定义DatabaseInitializer,没有意外行为:

 namespace MyProject.Data.DatabaseInitializers { public class MyCustomDbInit : IDatabaseInitializer where TContext : DbContext { public void InitializeDatabase(TContext context) { // Create our database if it doesn't already exist. context.Database.CreateIfNotExists() // Do you want to migrate to latest in your initializer? Add code here! // Do you want to seed data in your initializer? Add code here! } } } 

结果

如果您使用代码优先方法,禁用自动迁移并使用如上所述的自定义DatabaseInitializer,您将可以很好地控制发生的事情和时间。

我们在工作中使用这些策略并且没有问题(尽管确定这些策略确实需要一些麻烦)。 希望你会找到类似的成功!

我能够使用SQL CE复制您的问题,以及首先使用上面的代码使用EF代码。

奇怪的是,当我第一次使用你的代码时,它完美地运行了。 为了解决您的问题,我必须删除.sdf文件中的_MigrationHistory表。

通过删除.sdf文件(我意识到这可能不是你的情况下的选项,但我会解决更远的问题)下次运行时,它创建了迁移表 – 但它仍然无法正常工作。 如果您注意到,在步骤12,它最终会创建表格。

– 语句#1 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [Dummies] AS [Extent1])AS [GroupBy1]

– 语句#2 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__ MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

– 语句#3 WARN:System.Data.SqlServerCe.SqlCeException(0x80004005):指定的表不存在。 [__MigrationHistory]

– 语句#4 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__ MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

– 语句#5 WARN:System.Data.SqlServerCe.SqlCeException(0x80004005):指定的表不存在。 [__MigrationHistory]

– 语句#6 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__ MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

– 语句#7 WARN:System.Data.SqlServerCe.SqlCeException(0x80004005):指定的表不存在。 [__MigrationHistory]

– 语句#8 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__ MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

– 语句#9 WARN:System.Data.SqlServerCe.SqlCeException(0x80004005):指定的表不存在。 [__MigrationHistory]

– 语句#10以隔离级别开始事务:可序列化

– 语句#11 CREATE TABLE [Dummies]([DummyId] [int] NOT NULL IDENTITY,[test] nvarchar,[addThis] nvarchar,CONSTRAINT [PK_Dummies] PRIMARY KEY([DummyId]))

– 语句#12 CREATE TABLE [__ MigrationHistory]([MigrationId] nvarchar NOT NULL,[CreatedOn] [datetime] NOT NULL,[Model] [image] NOT NULL,[ProductVersion] nvarchar NOT NULL,CONSTRAINT [PK___MigrationHistory] ​​PRIMARY KEY( [MigrationId]))

– 声明#13 INSERT INTO [__ MigigrationHistory]([MigrationId],[CreatedOn],[Model],[ProductVersion])VALUES(’201208101940587_InitialCreate’,’2012-08-10T19:40:59.055′,,’4.3.1 “)

– 声明#14提交事务

– 语句#15 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [Dummies] AS [Extent1])AS [GroupBy1]

通过在正确的位置创建表来解决问题:

在此处输入图像描述

一旦我这样做,下次代码运行时,一切都再次运行完美。

– 语句#1 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__ MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

– 声明#2 SELECT TOP(1)[c]。[ProductVersion] AS [ProductVersion] FROM [__ MigrationHistory] ​​AS [c]

– 语句#3 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__ MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

– 语句#4 SELECT [Extent1]。[MigrationId] AS [MigrationId] FROM [__ MigrationHistory] ​​AS [Extent1]

– 语句#5 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__ MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

– 语句#6 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__ MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

– 语句#7 SELECT TOP(1)[Project1]。[C1] AS [C1],[Project1]。[MigrationId] AS [MigrationId],[Project1]。[Model] AS [Model] FROM(SELECT [Extent1] ] [MigrationId] AS [MigrationId],[Extent1]。[CreatedOn] AS [CreatedOn],[Extent1]。[Model] AS [Model],1 AS [C1] FROM [__ MigrationHistory] ​​AS [Extent1])AS [ Project1] ORDER BY [Project1]。[CreatedOn] DESC

– 语句#8 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [__ MigrationHistory] ​​AS [Extent1])AS [GroupBy1]

– 语句#9 SELECT TOP(1)[Project1]。[C1] AS [C1],[Project1]。[MigrationId] AS [MigrationId],[Project1]。[Model] AS [Model] FROM(SELECT [Extent1] ] [MigrationId] AS [MigrationId],[Extent1]。[CreatedOn] AS [CreatedOn],[Extent1]。[Model] AS [Model],1 AS [C1] FROM [__ MigrationHistory] ​​AS [Extent1])AS [ Project1] ORDER BY [Project1]。[CreatedOn] DESC

– 语句#10以隔离级别开始事务:未指定

– 声明#11插入[Dummies]([test],[addThis])值(null,null);

从[Dummies]中选择[DummyId],其中[DummyId] = @@ IDENTITY

– 声明#12插入[Dummies]([test],[addThis])值(null,null);

从[Dummies]中选择[DummyId],其中[DummyId] = @@ IDENTITY

– 声明#13插入[Dummies]([test],[addThis])值(null,null);

从[Dummies]中选择[DummyId],其中[DummyId] = @@ IDENTITY

– 声明#14插入[Dummies]([test],[addThis])值(null,null);

从[Dummies]中选择[DummyId],其中[DummyId] = @@ IDENTITY

– 声明#15提交事务

– 语句#16 SELECT [GroupBy1]。[A1] AS [C1] FROM(SELECT COUNT(1)AS [A1] FROM [Dummies] AS [Extent1])AS [GroupBy1]

如果放弃整个SDF并让它重新创建不是一个选项,那么我就是这样做的。

在你的appconfig中创建一个连接字符串(我意识到你可能想要动态连接字符串,这就是为什么它在你的代码中,但这应该是一次性的事情)。 我的连接字符串看起来像这样:

     

更改Context的构造函数,以便它将使用连接字符串:

 public MyContext() //: base(string.Format(@"DataSource=""{0}""", "MyContext.sdf")) : base("MyContext") 

所有这些都是必要的,因此您可以在Package Manager Console中运行一些命令,以便重新创建表。 打开包管理器控制台,然后运行以下命令:

 add-migration initial -ignorechanges 

接下来,运行程序 – 它会抛出一些警告,但随后它将为您创建表,并填充它。 之后,您可以更改构造函数,我没有更多问题。

注意:一旦它开始工作,种子function也开始工作

我玩了你提供的代码,在这种情况下(使用SQL Server而不是CE)并得到了以下内容。 我删除了Database.Create代码并允许EF的自动迁移来完成它。 现在,它会贯穿并正确调用Seed方法。

 internal class Program { private static void Main() { EntityFrameworkProfiler.Initialize(); Database.DefaultConnectionFactory = new SqlConnectionFactory("System.Data.SqlServer"); Database.SetInitializer(new MigrateDatabaseToLatestVersion()); using (var context = new MyContext()) { var element = context.Dummies.FirstOrDefault(); } } } internal class Dummy { public String Id { get; set; } } internal sealed class MyContext : DbContext { public MyContext() : base(@"Data Source=localhost;Initial Catalog=Dummies;User Id=;Password=;MultipleActiveResultSets=False;") { } public DbSet Dummies { get; set; } } internal sealed class MyContextConfiguration : DbMigrationsConfiguration { public MyContextConfiguration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } protected override void Seed(MyContext context) { context.Dummies.AddOrUpdate(new Dummy() { Id = "First" }); } } 

如果你查看EF分析器,你会看到现在有更多的查询运行DB(甚至检查旧的EdmMetaData表…这是非常奇怪的,因为它应该删除该表,如果它现在遇到支持的__MigrationHistory表)。 我不知道为什么会发生这种情况,我想这可能是我们方面的配置问题(我还不知道如何修复),或者它是迁移代码中的一个错误。

因此,我认为通过EF迁移,我们要么继续进行基于代码的迁移( 请参阅我的博客文章 )或自动迁移(如此代码段所示)。 我想随着时间的推移,我会更好地理解为什么EF(或我迁移的方式)有这种奇怪的行为 – 或者EF本身随着它的发展会变得更好。