Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Code First Migrations and initialization error

Tags:

I'm unsure about how to use the code first migration feature. In my understanding it should create my database if it's not existing already, and update it to the latest schema according to migration files. But I'm struggling with it, because I always get a lot of errors and I'm unsure overall how to use this properly..

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)     {     } } 

Using the Entity Framework Profiler I check what statements are executed. When I run the program with no database existing I get the following output:

-- statement #1 SELECT [GroupBy1].[A1] AS [C1] FROM (SELECT COUNT(1) AS [A1] FROM [__MigrationHistory] AS [Extent1]) AS [GroupBy1]

-- statement #2 WARN: System.Data.SqlServerCe.SqlCeException (0x80004005): The specified table does not exist. [ __MigrationHistory ] at System.Data.SqlServerCe.SqlCeCommand.ProcessResults(Int32 hr) at System.Data.SqlServerCe.SqlCeCommand.CompileQueryPlan() at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior behavior, String method, ResultSetOptions options) at System.Data.SqlServerCe.SqlCeCommand.ExecuteReader(CommandBehavior behavior) at System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteReader(CommandBehavior behavior) at System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteDbDataReader(CommandBehavior behavior) at HibernatingRhinos.Profiler.Appender.ProfiledDataAccess.ProfiledCommand.ExecuteDbDataReader(CommandBehavior behavior)

-- statement #3 SELECT [GroupBy1].[A1] AS [C1] FROM (SELECT COUNT(1) AS [A1] FROM [__MigrationHistory] AS [Extent1]) AS [GroupBy1]

-- statement #4 WARN: System.Data.SqlServerCe.SqlCeException (0x80004005): The specified table does not exist. [ __MigrationHistory ] at System.Data.SqlServerCe.SqlCeCommand.ProcessResults(Int32 hr) at System.Data.SqlServerCe.SqlCeCommand.CompileQueryPlan() at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior behavior, String method, ResultSetOptions options) at System.Data.SqlServerCe.SqlCeCommand.ExecuteReader(CommandBehavior behavior) at System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteReader(CommandBehavior behavior) at System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteDbDataReader(CommandBehavior behavior) at HibernatingRhinos.Profiler.Appender.ProfiledDataAccess.ProfiledCommand.ExecuteDbDataReader(CommandBehavior behavior)

-- statement #5 SELECT [GroupBy1].[A1] AS [C1] FROM (SELECT COUNT(1) AS [A1] FROM [__MigrationHistory] AS [Extent1]) AS [GroupBy1]

-- statement #6 WARN: System.Data.SqlServerCe.SqlCeException (0x80004005): The specified table does not exist. [ __MigrationHistory ] at System.Data.SqlServerCe.SqlCeCommand.ProcessResults(Int32 hr) at System.Data.SqlServerCe.SqlCeCommand.CompileQueryPlan() at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior behavior, String method, ResultSetOptions options) at System.Data.SqlServerCe.SqlCeCommand.ExecuteReader(CommandBehavior behavior) at System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteReader(CommandBehavior behavior) at System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteDbDataReader(CommandBehavior behavior) at HibernatingRhinos.Profiler.Appender.ProfiledDataAccess.ProfiledCommand.ExecuteDbDataReader(CommandBehavior behavior)

-- statement #7 SELECT [GroupBy1].[A1] AS [C1] FROM (SELECT COUNT(1) AS [A1] FROM [__MigrationHistory] AS [Extent1]) AS [GroupBy1]

-- statement #8 WARN: System.Data.SqlServerCe.SqlCeException (0x80004005): The specified table does not exist. [ __MigrationHistory ] at System.Data.SqlServerCe.SqlCeCommand.ProcessResults(Int32 hr) at System.Data.SqlServerCe.SqlCeCommand.CompileQueryPlan() at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior behavior, String method, ResultSetOptions options) at System.Data.SqlServerCe.SqlCeCommand.ExecuteReader(CommandBehavior behavior) at System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteReader(CommandBehavior behavior) at System.Data.SqlServerCe.SqlCeMultiCommand.ExecuteDbDataReader(CommandBehavior behavior) at HibernatingRhinos.Profiler.Appender.ProfiledDataAccess.ProfiledCommand.ExecuteDbDataReader(CommandBehavior behavior)

-- statement #9 begin transaction with isolation level: Serializable

-- statement #10 CREATE TABLE [Dummies] ( [Name] nvarchar NOT NULL, CONSTRAINT [PK_Dummies] PRIMARY KEY ([Name]) )

-- statement #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]) )

-- statement #12 INSERT INTO [__MigrationHistory] ([MigrationId], [CreatedOn], [Model], [ProductVersion]) VALUES ('201207261524579_InitialCreate', '2012-07-26T15:24:58.523', 0x1F8B080 , '4.3.1')

-- statement #13 commit transaction

-- statement #14 SELECT TOP (1) [c].[Name] AS [Name] FROM [Dummies] AS [c]

As you can see it is trying to access the database four times before it actually creates the database. This does not seem right. When I start the application with an existing database it'll query the database 7 times before any of my actual queries will be executed. Note that this happens with context.Database.Create(), not with .Exists().

Also the seed method of my configuration is never called, but the constructor is.

This all just seems very wrong and confusing. I hope someone can enlighten me why the errors happen so often in the beginning, and why my seed method is not called at all.

I'm using the latest stable versions of SqlServer compact and Entity Framework.

package id="EntityFramework" version="4.3.1" targetFramework="net40"

package id="Microsoft.SqlServer.Compact" version="4.0.8854.2" targetFramework="net40"

like image 305
Martin1921 Avatar asked Jul 26 '12 15:07

Martin1921


Video Answer


2 Answers

It seems there are a lot of ways to configure entityframework and everyone has there own take on what is best. All I can offer is my take based on what we've standardized at my work. A lot of this is developer preference. My preference happens to be controlling as much as possible so I always understand exactly what is happening and when.

Automatic Migrations

First off, while Automatic Migrations may be convenient but they cause a lot of trouble particularly as a project grows and/or data becomes more complex. In my opinion any commercial/production system should have more control than this. We always turn off automatic migrations for all of our major projects by setting AutomaticMigrationsEnabled = false;. We run our migrations explicitly when we want it done (on dev this is in the package manager console in visual studio by typing Update-Database and in production we have written our own little migration utility that just calls the migrate to latest code explicitly - but none are automatic).

@Terric's answer scares me with both automatic migrations AND data loss being permitted! I don't want to be the guy who deploys a solution and wipes out some important data because of a badly executed column alteration that resulted in data loss. As a side note when we run out migration explicitly in dev I often use the -v switch for verbose ouptut (Update-Database -v). This lets you see the SQL being executed and any failures/warnings if appropriate.

It has also been our experience that changing these settings after you are several migrations into development doesn't go well. I'm not sure where this is being tracked, but starting a project fresh with automatic migrations disabled ensures nothing unexpected is going to happen.

Personally, I'd remove the Initializer you have MigrateDatabaseToLatestVersion and run the migrator myself exactly when I want to (either via the package manager console or via my own explicit code somewhere).

Creating a database if it doesn't exist

This behavior is provided by a DatabaseInitializer (not really EntityFramework itself). The CreateDatabaseIfNotExists initializer is built into EntityFramework and a default in some versions. However, again I'm not one for all the inferred app behavior stuff. In my opinion I'd like a little more control.

This guy has an example of a custom database initializer inheriting from the built in CreateDatabaseIfNotExists. But you could always just create your own and implement whatever exact logic you want to see (including the creation of your database). Again this just avoids unexpected behavior. My personal preference as a developer is to control this stuff closely unless I'm just goofing around with a mockup or test project.

Super simple custom DatabaseInitializer with no unexpected behavior:

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!         }     } } 

The Results

If you use a code first approach, disable automatic migrations and use a custom DatabaseInitializer like the above, you will have very good control over what is happening and when.

We use these strategies at work and have zero issues (although it did take some trouble to settle on these strategies). Hopefully you will find similar success!

like image 65
BenSwayne Avatar answered Sep 19 '22 01:09

BenSwayne


I was able to replicate your problem using SQL CE, as well as using EF code first using the code above.

The strange thing is that when I used your code as is, the first time, it worked perfectly. In order to have your issue arise, I had to actually delete the _MigrationHistory table in my .sdf file.

By deleting the .sdf file (I realize this might not be an option in your case, but I'll address that farther down) The next time it ran, it created the migration table - but it still didn't work quite properly. If you notice, at step 12 it's finally creates the table.

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

-- statement #2 SELECT [GroupBy1].[A1] AS [C1] FROM (SELECT COUNT(1) AS [A1] FROM [__MigrationHistory] AS [Extent1]) AS [GroupBy1]

-- statement #3 WARN: System.Data.SqlServerCe.SqlCeException (0x80004005): The specified table does not exist. [ __MigrationHistory ]

-- statement #4 SELECT [GroupBy1].[A1] AS [C1] FROM (SELECT COUNT(1) AS [A1] FROM [__MigrationHistory] AS [Extent1]) AS [GroupBy1]

-- statement #5 WARN: System.Data.SqlServerCe.SqlCeException (0x80004005): The specified table does not exist. [ __MigrationHistory ]

-- statement #6 SELECT [GroupBy1].[A1] AS [C1] FROM (SELECT COUNT(1) AS [A1] FROM [__MigrationHistory] AS [Extent1]) AS [GroupBy1]

-- statement #7 WARN: System.Data.SqlServerCe.SqlCeException (0x80004005): The specified table does not exist. [ __MigrationHistory ]

-- statement #8 SELECT [GroupBy1].[A1] AS [C1] FROM (SELECT COUNT(1) AS [A1] FROM [__MigrationHistory] AS [Extent1]) AS [GroupBy1]

-- statement #9 WARN: System.Data.SqlServerCe.SqlCeException (0x80004005): The specified table does not exist. [ __MigrationHistory ]

-- statement #10 begin transaction with isolation level: Serializable

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

-- statement #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]) )

-- statement #13 INSERT INTO [__MigrationHistory] ([MigrationId], [CreatedOn], [Model], [ProductVersion]) VALUES ('201208101940587_InitialCreate', '2012-08-10T19:40:59.055', 0x1F8B0800000000000400CD57DD8ED33C10BDFF24DE21F23D75591082550A5ADA5D5441771159B89FC6D3D6C27642ECACDA67FB2E78245E8171FE9A26FDD95D40E2AE998CCF1C1F9FF1A43FFFFF11BE5D6B15DC6166656246ECD960C802347122A4598E58EE164F5FB1B76F9EFC175E0ABD0EBED679673E8F561A3B622BE7D273CE6DBC420D76A0659C253659B8419C680E22E167C3E12BFE6CC8912018610541F839374E6A2C1EE8719C98185397839A250295ADE2F4262A50836BD068538871C4DE8195F198D2AE64661D0B2E94046211A15A3C90D2F0B5A7C49A6254EE9268B9CDED26C5A2E4884D72AD37ED144AFA809B9D00853E65498A99DB7CC6457BE154B080EF2EE6DDD5CDDAEE42CF62C4A6C63D3F63C175AE14CC150516A02CB2207D791EB924C3F768300387E2133887199DCD5460B18B4A8DF3F4E5FD0479CD87675E100EC6240E1C1D748F7D87AB437F0225D1C865E419165CC9358A8F68966ED5909DC1BA8EBC180EC93A5F8C248F79842CC7F6EECAE7E3554188DB95B47FBB70C8B76EE87B842CEB4092F615A95911C1B5EB98A5CC8ED0B58E572291DF8297E61D9456DB4BA129B66D0C5E7646DD41FC400B8533485392A7D552552488CA7E1A3F8D1EDE37BAC4E0B1DDD33E0DDBA612191596D8794BA5EB2E9E808339F8031B0BDD4B3B296D5D6757E16E8F6E05AFF3FDEF72CDEEA532D8D3F56DE9AE68379A9AACD8183634F6DE16D5CA280605D9A1561F272AD7E6E8C5710CA76CC3364819B93F42D3526D9026D8C70979478AAED8BCA776E71AEC1EDE31D777539AEA8DFB3B2E0F2BC79D9E263D0B96292C2081EEA4F0F68B36D6A11EF88441F45D4598D1B81CE3E0859F8175DA0C8C5C90E8B7C937A43BB878F7D8C9D45CC4D60AF58F8F2769C8672707516F7ADD7FAC983BC8E215647BEEF2DF9A1A8FC47DD850E85F70A787C3E6F06828BD3962629ED0364A8ACD3C79E4DCE8774AC8DB5F67E104AD5C6E21FCB79AC1D87F1D6C41EB9CA95924B5ECB4AB36A33AA5732A337420489C8BCCC905C48E5EC7686D31D0BF82CA29E552CF514CCD4DEED2DC5D588B7AAE76E664C88FD72F86E32EE7F026F54FF64F6C81684ADA02DE9877B954A2E17DD5B7FA2108EF93AA7F88157DD010DC72D3205D27E69E40957C134CD1F8EEBB459D2A02B33726823B3CCCEDB486BB8A851309CB0C745BC132523189802AB74A5081F68A6D3DFFE782FB7F176F7E018E4F04B08F0C0000 , '4.3.1')

-- statement #14 commit transaction

-- statement #15 SELECT [GroupBy1].[A1] AS [C1] FROM (SELECT COUNT(1) AS [A1] FROM [Dummies] AS [Extent1]) AS [GroupBy1]

Which fixes the problem by creating the table in the right place:

enter image description here

Once I did this, the next time the code ran, everything ran perfectly again.

-- statement #1 SELECT [GroupBy1].[A1] AS [C1] FROM (SELECT COUNT(1) AS [A1] FROM [__MigrationHistory] AS [Extent1]) AS [GroupBy1]

-- statement #2 SELECT TOP (1) [c].[ProductVersion] AS [ProductVersion] FROM [__MigrationHistory] AS [c]

-- statement #3 SELECT [GroupBy1].[A1] AS [C1] FROM (SELECT COUNT(1) AS [A1] FROM [__MigrationHistory] AS [Extent1]) AS [GroupBy1]

-- statement #4 SELECT [Extent1].[MigrationId] AS [MigrationId] FROM [__MigrationHistory] AS [Extent1]

-- statement #5 SELECT [GroupBy1].[A1] AS [C1] FROM (SELECT COUNT(1) AS [A1] FROM [__MigrationHistory] AS [Extent1]) AS [GroupBy1]

-- statement #6 SELECT [GroupBy1].[A1] AS [C1] FROM (SELECT COUNT(1) AS [A1] FROM [__MigrationHistory] AS [Extent1]) AS [GroupBy1]

-- statement #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

-- statement #8 SELECT [GroupBy1].[A1] AS [C1] FROM (SELECT COUNT(1) AS [A1] FROM [__MigrationHistory] AS [Extent1]) AS [GroupBy1]

-- statement #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

-- statement #10 begin transaction with isolation level: Unspecified

-- statement #11 insert [Dummies] ([test], [addThis]) values (null, null);

select [DummyId] from [Dummies] where [DummyId] = @@IDENTITY

-- statement #12 insert [Dummies] ([test], [addThis]) values (null, null);

select [DummyId] from [Dummies] where [DummyId] = @@IDENTITY

-- statement #13 insert [Dummies] ([test], [addThis]) values (null, null);

select [DummyId] from [Dummies] where [DummyId] = @@IDENTITY

-- statement #14 insert [Dummies] ([test], [addThis]) values (null, null);

select [DummyId] from [Dummies] where [DummyId] = @@IDENTITY

-- statement #15 commit transaction

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

If dropping the entire SDF and letting it recreate is not an option, here's what I did to get it back.

Create a connectionstring in your appconfig (I realize you're probably wanting dynamic connection strings that's why it's in your Code, but this should be a one time thing). My connection string looked like this:

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

Change the constructor of your Context so that it will use the connection string:

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

All of this is necessary so you can run some commands in Package Manager Console, so that it will re-create the table. Open up package manager console, and run this command:

add-migration initial -ignorechanges 

Next, run the program - it will throw some warnings, but then it will create the table for you, and populate it. After that, you can change your constructor back and I had no more issues.

Note: once it started working the Seed Function started working as well

like image 38
Mark Oreta Avatar answered Sep 17 '22 01:09

Mark Oreta