25

我不确定如何使用代码优先迁移功能。据我了解,如果它不存在,它应该创建我的数据库,并根据迁移文件将其更新为最新模式。但我正在为此苦苦挣扎,因为我总是遇到很多错误,而且我不确定总体上如何正确使用它..

internal class Program
{
    private static void Main()
    {
        EntityFrameworkProfiler.Initialize();

        Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
        Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Migrations.Configuration>());

        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<Dummy> Dummies { get; set; }
}

internal sealed class Configuration : DbMigrationsConfiguration<MyContext>
{
    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): 指定的表不存在。[ __MigrationHistory ] 在 System.Data.SqlServerCe.SqlCeCommand.CompileQueryPlan() 在 System.Data.SqlServerCe.SqlCeCommand.ProcessResults(Int32 hr) 在 System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior 行为,字符串方法,ResultSetOptions 选项) 在System.Data.SqlServerCe.SqlCeCommand.ExecuteReader(CommandBehavior 行为) 在 System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteReader(CommandBehavior 行为) 在 System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteDbDataReader(CommandBehavior 行为) 在 HibernatingRhinos.Profiler.Appender.ProfiledDataAccess。 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): 指定的表不存在。[ __MigrationHistory ] 在 System.Data.SqlServerCe.SqlCeCommand.CompileQueryPlan() 在 System.Data.SqlServerCe.SqlCeCommand.ProcessResults(Int32 hr) 在 System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior 行为,字符串方法,ResultSetOptions 选项) 在System.Data.SqlServerCe.SqlCeCommand.ExecuteReader(CommandBehavior 行为) 在 System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteReader(CommandBehavior 行为) 在 System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteDbDataReader(CommandBehavior 行为) 在 HibernatingRhinos.Profiler.Appender.ProfiledDataAccess。 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): 指定的表不存在。[ __MigrationHistory ] 在 System.Data.SqlServerCe.SqlCeCommand.CompileQueryPlan() 在 System.Data.SqlServerCe.SqlCeCommand.ProcessResults(Int32 hr) 在 System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior 行为,字符串方法,ResultSetOptions 选项) 在System.Data.SqlServerCe.SqlCeCommand.ExecuteReader(CommandBehavior 行为) 在 System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteReader(CommandBehavior 行为) 在 System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteDbDataReader(CommandBehavior 行为) 在 HibernatingRhinos.Profiler.Appender.ProfiledDataAccess。 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): 指定的表不存在。[ __MigrationHistory ] 在 System.Data.SqlServerCe.SqlCeCommand.CompileQueryPlan() 在 System.Data.SqlServerCe.SqlCeCommand.ProcessResults(Int32 hr) 在 System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior 行为,字符串方法,ResultSetOptions 选项) 在System.Data.SqlServerCe.SqlCeCommand.ExecuteReader(CommandBehavior 行为) 在 System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteReader(CommandBehavior 行为) 在 System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteDbDataReader(CommandBehavior 行为) 在 HibernatingRhinos.Profiler.Appender.ProfiledDataAccess。 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 [__MigrationHistory] ​​([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 的最新稳定版本。

包 id="EntityFramework" 版本="4.3.1" targetFramework="net40"

包 id="Microsoft.SqlServer.Compact" 版本="4.0.8854.2" targetFramework="net40"

4

3 回答 3

12

似乎有很多方法可以配置实体框架,每个人都有自己的看法。我所能提供的只是基于我们在工作中标准化的内容。其中很多是开发人员的偏好。我的偏好恰好是尽可能地控制,所以我总是能准确地了解发生了什么以及何时发生。

自动迁移

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

@Terric 的回答让我感到害怕,因为允许自动迁移和数据丢失!我不想成为部署解决方案并清除一些重要数据的人,因为执行不当的列更改会导致数据丢失。附带说明一下,当我们在 dev 中显式地执行迁移时,我经常使用 -v 开关来进行详细输出 ( Update-Database -v)。这让您可以看到正在执行的 SQL 以及任何失败/警告(如果合适)。

根据我们的经验,在多次迁移到开发后更改这些设置并不顺利。我不确定这是在哪里被跟踪的,但是在禁用自动迁移的情况下重新开始一个项目可以确保不会发生任何意外。

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

如果数据库不存在则创建数据库

这种行为是由 DatabaseInitializer 提供的(不是真正的 EntityFramework 本身)。CreateDatabaseIfNotExists初始化器内置在 EntityFramework 中,在某些版本中是默认值。但是,我又不是所有推断的应用程序行为的人。在我看来,我想要更多的控制。

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

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

namespace MyProject.Data.DatabaseInitializers
{
    public class MyCustomDbInit<TContext> : IDatabaseInitializer<TContext>
        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,您将可以很好地控制发生的事情和时间。

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

于 2012-08-10T17:50:54.407 回答
5

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

奇怪的是,当我第一次按原样使用您的代码时,它运行良好。为了出现您的问题,我必须实际删除_MigrationHistory.sdf 文件中的表。

通过删除 .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 ( [迁移 ID]) )

-- 语句 #13 INSERT INTO [__MigrationHistory] ​​([MigrationId], [CreatedOn], [Model], [ProductVersion]) VALUES ('201208101940587_InitialCreate', '2012-08-10T19:40:59.055',0x1F8B0800000000000400CD57DD8ED33C10BDFF24DE21F23D75591082550A5ADA5D5441771159B89FC6D3D6C27642ECACDA67FB2E78245E8171FE9A26FDD95D40E2AE998CCF1C1F9FF1A43FFFFF11BE5D6B15DC6166656246ECD960C802347122A4598E58EE164F5FB1B76F9EFC175E0ABD0EBED679673E8F561A3B622BE7D273CE6DBC420D76A0659C253659B8419C680E22E167C3E12BFE6CC8912018610541F839374E6A2C1EE8719C98185397839A250295ADE2F4262A50836BD068538871C4DE8195F198D2AE64661D0B2E94046211A15A3C90D2F0B5A7C49A6254EE9268B9CDED26C5A2E4884D72AD37ED144AFA809B9D00853E65498A99DB7CC6457BE154B080EF2EE6DDD5CDDAEE42CF62C4A6C63D3F63C175AE14CC150516A02CB2207D791EB924C3F768300387E2133887199DCD5460B18B4A8DF3F4E5FD0479CD87675E100EC6240E1C1D748F7D87AB437F0225D1C865E419165CC9358A8F68966ED5909DC1BA8EBC180EC93A5F8C248F79842CC7F6EECAE7E3554188DB95B47FBB70C8B76EE87B842CEB4092F615A95911C1B5EB98A5CC8ED0B58E572291DF8297E61D9456DB4BA129B66D0C5E7646DD41FC400B8533485392A7D552552488CA7E1A3F8D1EDE37BAC4E0B1DDD33E0DDBA612191596D8794BA5EB2E9E808339F8031B0BDD4B3B296D5D6757E16E8F6E05AFF3FDEF72CDEEA532D8D3F56DE9AE68379A9AACD8183634F6DE16D5CA280605D9A1561F272AD7E6E8C5710CA76CC3364819B93F42D3526D9026D8C70979478AAED8BCA776E71AEC1EDE31D777539AEA8DFB3B2E0F2BC79D9E263D0B96292C2081EEA4F0F68B36D6A11EF88441F45D4598D1B81CE3E0859F8175DA0C8C5C90E8B7C937A43BB878F7D8C9D45CC4D60AF58F8F2769C8672707516F7ADD7FAC983BC8E215647BEEF2DF9A1A8FC47DD850E85F70A787C3E6F06828BD3962629ED0364A8ACD3C79E4DCE8774AC8DB5F67E104AD5C6E21FCB79AC1D87F1D6C41EB9CA95924B5ECB4AB36A33AA5732A337420489C8BCCC905C48E5EC7686D31D0BF82CA29E552CF514CCD4DEED2DC5D588B7AAE76E664C88FD72F86E32EE7F026F54FF64F6C81684ADA02DE9877B954A2E17DD5B7FA2108EF93AA7F88157DD010DC72D3205D27E69E40957C134CD1F8EEBB459D2A02B33726823B3CCCEDB486BB8A851309CB0C745BC132523189802AB74A5081F68A6D3DFFE782FB7F176F7E018E4F04B08F0C0000,“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);

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

-- 语句 #12 插入 [Dummies] ([test], [addThis]) 值 (null, null);

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

-- 语句 #13 插入 [Dummies] ([test], [addThis]) 值 (null, null);

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

-- 语句 #14 插入 [Dummies] ([test], [addThis]) 值 (null, null);

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

-- 语句#15 提交事务

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

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

在您的 appconfig 中创建一个连接字符串(我意识到您可能想要动态连接字符串,这就是它在您的代码中的原因,但这应该是一次性的)。我的连接字符串如下所示:

   <connectionStrings>
        <add connectionString="Data Source=MyContext.sdf;Persist Security Info=False;" name="MyContext" providerName="System.Data.SqlServerCe.4.0"/>
    </connectionStrings>

更改 Context 的构造函数,使其使用连接字符串:

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

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

add-migration initial -ignorechanges

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

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

于 2012-08-10T20:32:48.430 回答
1

我玩弄了您提供的代码,在这种情况下(使用 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<MyContext, MyContextConfiguration>());

        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=<USER_ID>;Password=<PASSWORD>;MultipleActiveResultSets=False;")
    {
    }

    public DbSet<Dummy> Dummies { get; set; }
}

internal sealed class MyContextConfiguration : DbMigrationsConfiguration<MyContext>
{
    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 本身会随着它的发展而变得更好。

于 2012-07-30T12:03:47.977 回答