Rolling back an EF Core migration does not exactly restore the model snapshot - entity-framework-core

I am playing around with EF Core 6.0.0-rc.2.21480.5.
I added an empty migration, just ran the command Add-Migration ShouldBeEmpty without any change to the model.
Checking with such empty migrations is necessary to ensure migrations pulled from other developers work are merged correctly.
As expected the up steps and down steps are empty.
public partial class ShouldBeEmpty : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{ }
protected override void Down(MigrationBuilder migrationBuilder)
{ }
}
Also the Model Snapshot file has not changed.
Now I revert back with the remove migration command Remove-Migration. The newly created file ShouldBeEmpty is removed as a part of rolling back as expected. But as I notice the Model Snapshot file, its checked out, and when I take a diff, I see this.
Note the following.
b.ToTable("Sita", (string)null);
So its not exactly identical as I roll that back. (string)null is being passed additionally to the ToTable method call. It does not seem to pose any problem but just want to know am I missing something here?
I noticed my csproj file that nullable is enabled. I disabled that and tried, but this did not make any difference.
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

Related

Entity Framework Code First post migration step?

Is there some way to add a post migration method to Code First EF migration?
All the stored proc's are in the Visual Studio project. Right now there is an approach to load the stored proc resource from the file and put it into it's own migration:
protected override void Up(MigrationBuilder migrationBuilder)
{
var script = ScriptMgr.LoadStoredProc( "StoredProcThatChanged.sql" );
migrationBuilder.Sql( script );
}
There is a weak link in this process: Each time the script changes (StoredProcThatChanged.sql) a new migration needs to be created to make sure it executes again. The problem is the previous migration is also loading the same file. When generating a new script, the process reads in the one file both times, effectively changing the previous migration. Which is a classic no-no.
This would be resolved if there is a post migration method where ALL stored proc's can be reapplied to the DB. Is such a step possible? If so, now is it done?
I have been digging into the efcore source code and it looks like it is possible, not ideal, but there might be a way...
It looks like efcore has an interface called IMigrator. It contains the method string GenerateScript(...). The implementation of it, class Migrator, has comments all over the place saying that it's implementation of GenerateScript is internal and subject to change. But... It looks to me like I can achieve my end goal:
class MyMigrator : Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator
{
public string GenerateScript(
string? fromMigration = null,
string? toMigration = null,
MigrationsSqlGenerationOptions options = MigrationsSqlGenerationOptions.Default)
{
var result = base.GenerateScript( fromMigration, toMigration, options);
results += MyPostSteps(...);
return results;
}
}
Will this work and does anyone know how I might go about replacing the default Migrator with the MyMigrator?

How to edit previously applied migration without adding another migration in EF code first

I have an applied migration using "haward" db schema.
public partial class CreateCourseCategoryTable : DbMigration
{
public override void Up()
{
CreateTable(
"haward.CourseCategories",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(),
Code = c.String(),
})
.PrimaryKey(t => t.Id);
}
public override void Down()
{
DropTable("haward.CourseCategories");
}
}
with this mapping
public class CourseCategoryMapping : EntityTypeConfiguration<CourseCategory>
{
public CourseCategoryMapping()
{
ToTable("CourseCategories", "haward");
}
}
now I want to change the schema from "haward" to "tr"
I dont want to add migration with this one so I thought of just editing directly the source code of the Migration and Mapping.
public partial class CreateCourseCategoryTable : DbMigration
{
public override void Up()
{
CreateTable(
"tr.CourseCategories",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(),
Code = c.String(),
})
.PrimaryKey(t => t.Id);
}
public override void Down()
{
DropTable("tr.CourseCategories");
}
}
public class CourseCategoryMapping : EntityTypeConfiguration<CourseCategory>
{
public CourseCategoryMapping()
{
ToTable("CourseCategories", "tr");
}
}
then recreate empty database and issued the command update-database
but its telling that I still have pending changes.
so what I did was issued the add-migration command to check which changes are those. and it seems like it still detects my edits(from "haward" to "tr" schema) even without the migrations table.
where do the model changes being saved? and how to edit directly the source code and reapply the migration? I know its not advisable because thats what migration is for. but I dont want to get my history dirty with just those changes specially if I am only at early development stage.
TL;DR: This is very complicated - it's a lot easier just to add a new migration later to correct the problem.
An Entity Framework migration consists of two parts - the code, and a hash of the model. The hash of the model is used to determine whether the model has changed, and hence whether any new migrations are required.
If you change the model, you change the hash. This hash is stored in the MigrationName.designer.cs file. You cannot just edit the model and change the migration.cs code, as the model no longer matches the model hash. You also need to regenerate the hash for the model.
The only way to do this is to roll your database back, and update the hash.
Consider you have 3 migrations applied:
Migration1
Migration2
Migration3
If you want to apply a change from Migration2 onwards...
Roll back to Migration1: Update-Database -TargetMigration Migration1 -Force (NB - This may cause data loss, so work on a development copy of your database)
Make your model code match what you wanted for Migration 2, and update the code for Migration 2 by hand
Regenerate the designer file for Migration2: Add-Migration xxxxxxxxxxx_Migration2 (use the full name of the migration including the date). This will only update the designer.cs file
Apply Migration2: Update-Database -TargetMigration Migration2
Reapply any model changes to your code for Migration3
Regenerate the designer file for Migration3: Add-Migration xxxxxxxxxxx_Migration3
Update the database to latest: Update-Database
I'm not sure if this helps anyone else, but I was able to edit and re-apply a migration by performing the following:
Delete the record of the migration from the _MigrationHistory table. Find the one with a "migrationId" that matches your migration class file name and delete it.
Manually undo all of the previous migration's changes (which would have been everything in the Down() section of your migration class file before you added the new stuff).
Update-Database (or whatever you do to apply your migrations. I have auto-migration turned on so I just run my app).
My migration was pretty simple so I'm not sure if this will work for everyone.
Enjoy!
There's no easy way of doing this but, if you're using a version control system (Git, Subversion, etc.), there's another option. It's a bit more toilsome but was the only one that worked for me.
Considering you have the following migrations:
Migration1
Migration2
Migration3
Assuming you want to change Migration2, you could follow the steps below:
Create a patch with the changes you want to be applied to Migration2
Check out the commit immediately before the migration you want to avoid (in this case, Migration3 - It will look like you have just created Migration2)
Update the DB to Migration1 - Update-Database -TargetMigration Migration1 -Force
Apply the patch you created on step 1
Recreate Migration2 - Add-Migration Migration2 (it will now contain exactly the changes you want)
Copy the migration files created to a different directory
Revert all the uncommitted changes
Check out the HEAD commit again
Delete the files for Migration2 and Migration3
Copy the files from step 6 back to their original directory
Update the database - Update-Database
Recreate Migration3 - Add-Migration Migration3
Update the database again - Update-Database
I assume, this will be the simple one to followed.
Remove the migration from the code base
and Add the same migration after updating the model class.
Then to update-database command.
This will work.

How to recreate migrations from scratch

I recently upgraded from EF6 alpha 1-3 to EF6 beta 1. This meant that I had to recreate all the migrations created using the alpha version.
So I tried to roll back to a migration created using EF5. But I hit the error Introducing FOREIGN KEY constraint on table may cause cycles or multiple cascade paths. I figure this is because I had neglected to fix Down migrations when I was fixing Up migrations for exactly the same problem. (Should have read this before)
Anyway, rather than try to fix it all up I am trying to reset all the migrations - as described here. I deleted my migrations table in the database and all migration .cs files, then in package manager Enable-Migrations -EnableAutomaticMigrations -Force and Add-Migration Initial
When I tried to run my application with my existing database initialiser (which has automatic migrations false) it failed because it tried to create tables that were already there. So I changed my initialiser to Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>())
This time I got the Introducing FOREIGN KEY constraint on table may cause cycles or multiple cascade paths problem again during initialisation
So I changed ALL the cascadeDelete: true to cascadeDelete: false in the migration file
But I still get the same error!
Update 1 I removed all but the creation of 1 table in the migration file but I got the same error. There must be some kind of cache somewhere or it's picking up a file I don't know about or it's generating its own migration in the background
Update 2 I figured that when using DropCreateDatabaseAlways that EF must always generate the migrations and also that changing cascadeDelete to false in the migration file is the wrong place to do it. It should be done in the FluentAPI. So I added this line modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); to onmodelcreating. And I also deleted the Initial migration file. Then I ran the application and it correctly generated a database. I thought I'd cracked it but....
I changed initialisation to use my original configuration file:
internal sealed class Configuration : DbMigrationsConfiguration<SID2013Context>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
AutomaticMigrationDataLossAllowed = true;
}
protected override void Seed(etc..
}
Then I ran the application and it reported that the model had changed. So I did Add-Migration Update-Database and a migration file to create the database was created.
The problem now is that when I run the application it tries to run another update (even though AutomaticMigrationsEnabled = false). I get the "There is already an object named 'xxx' in the database" problem again. There is an entry in the migrations table that does not match the name of the configuration file.
If you would like to start work "code first migration" using an existing database, you can do the following:
I run the add-migration "InitialMigrations".
It explores the existing database and make it a migration step.
temporarily delete the contents of the "InitialMigrations" step:
public partial class InitialMigrations : DbMigration {
public override void Up()
{
//...
//...
}
public override void Down()
{
//...
//...
}
}
I run the update-database
This creates the appropriate entries in the table __MigrationHistory.
Restores the contents of the "InitialMigration" Go to work properly on an empty database.
That's it.
update: initializer
As I understand it, the 'DropCreateDatabaseAlways' always delete and create the database. This can only be used for the very first installation. If you have a working installation launchpad, you erase all data. That is why I hazardous.
I based on this article: coding.abel.nu/2012/03/… my own Initializer I'm using. I'll write.
The seed is definitely executed, so this method yields the same result, but it works even if you do not run installation, but run upgrade.
public partial class MyEntities : DbContext
{
static MyEntities()
{
Database.SetInitializer<MyEntities>(new ValidateDatabase<MyEntities>());
}
}
/// <summary>
/// http://coding.abel.nu/2012/03/prevent-ef-migrations-from-creating-or-changing-the-database/
/// </summary>
/// <typeparam name="TContext"></typeparam>
public class ValidateDatabase<TContext> : IDatabaseInitializer<TContext>
where TContext : DbContext
{
public void InitializeDatabase(TContext context)
{
if (!context.Database.Exists())
{
throw new ConfigurationErrorsException(
"Database does not exist");
}
else
{
if (!context.Database.CompatibleWithModel(true))
{
//from:
//http://stackoverflow.com/questions/11611322/ef-4-3-code-first-migrations-seed-per-migration
var cfg = new Migrations.Configuration();
var migrator = new DbMigrator(cfg);
///Last in the db, (first, the reverse order)
var dbLast = migrator.GetDatabaseMigrations().FirstOrDefault();
///last in the app
var appLast = migrator.GetLocalMigrations().LastOrDefault();
///what is missing
var pendings = string.Join(", ", migrator.GetPendingMigrations());
throw new InvalidOperationException(
string.Format("The database ({0}) is not compatible with the entity model ({1}). Pending migrations: {2}",
dbLast, appLast, pendings));
}
}
}
}
update: setup
Installation I'm using something like this:
http://coding.abel.nu/2012/04/update-database-msi-custom-action/
I finally fixed this by dropping the database manually then running the application with the original MigrateDatabaseToLatestVersion initializer. I had not realised that this would create the database if it did not exist and there was no need to use a DropCreateDatabaseAlways initializer then change to MigrateDatabaseToLatestVersion

Debug code-first Entity Framework migration codes

I'm using Entity Framework code first in my website and I'm just wondering if there is any way to debug the migration codes. You know, like setting breakpoints and stuff like this.
I'm using Package Manager Console to update the database using Update-Database.
Thanks
I know that EF Code First Migrations is relatively new tool but don't forget about you are still in .NET.
So you can use:
if (System.Diagnostics.Debugger.IsAttached == false)
{
System.Diagnostics.Debugger.Launch();
}
After that you can see your InnerException.
Or you can use try...catch statement like this:
Exception handling Entity Framework
To hit a break point in a db migration set the context to MigrateDatabaseToLatestVersion on initialise.
Database.SetInitializer(new MigrateDatabaseToLatestVersion<EnterContextHere, Configuration>());
Then you just debug as normal (run using f5) and the breakpoint will hit the first time you run the project.
The problem now is that if you debug a second time the migration will not run. This is because the __MigrationHistory table has been updated to say you have migrated to the latest version. To re-test the migration open the package manager console and downgrade to the previous migration:
Update-Database –TargetMigration: ThePreviousMigrationName
My answer might be a bit silly but anyway here it goes.
If you, like me, some times have problems in the Seed() method what I usually do is simply create a public method that calls the Protect Seed().
public void SeedDebug(AppDbContext context)
{
Seed(context);
}
then in my HomeController I call this method in Debug mode.
public class HomeController : Controller
{
var appDb = new AppDbContext();
public ActionResult Index()
{
var config = new Configuration();
config.SeedDebug(appDb);
return View();
}
}
I know it's a bit lame solution, but it's simple and quick.
Of course this has to be done after the model been created.
So step by step:
comment the seed method and execute the update-database to create the model
uncomment the method Seed() and plugin the "hack" I mentioned above.
in the configuration disable Auto migrations
AutomaticMigrationsEnabled = false;//if you have this disabled already skip this step
Debug your application, fix the error and remove the "hack"
Here's a more fail-proof method which will do the trick without much fuss:
Step#1: Place this piece of code right above the migration you want to debug:
public partial class ORACLE_Test : DbMigration
{
public override void Up()
{
if (!System.Diagnostics.Debugger.IsAttached)
System.Diagnostics.Debugger.Launch();
AddColumn("TEST", "UR_USER_ID", x => x.Decimal(nullable: false, precision: 11, scale: 0, storeType: "number"));
AddColumn("TEST", "UR_CLIENT_ID", x => x.Decimal(nullable: false, precision: 11, scale: 0, storeType: "number"));
[...]
}
public override void Down()
{
}
}
Step#2: Compile the project containing your migrations
Step#3: Open a console inside the output directory (/bin/Debug, /bin/Release etc) containing the dll of your migrations
Step#4: Invoke migrate.exe with the /scriptFile parameter to launch the debugger and actually debug the desired db-migration
migrate.exe "Your.Migrations.Assembly.dll" /scriptFile="foo.sql" /verbose /startupConfigurationFile="Your.Migrations.Assembly.config"
Once the debugger-selector dialog pops up pick the visual studio instance that you have already opened.
You could add Console.WriteLine statements to the migration code (not a great solution)
Note, the messages are only shown if you run the migration code using the migrate.exe utility (in pacakges\EntityFramework.x.y.z\tools). They will not display if you run the migration through the Package Manager console.
I've had lots of luck using "Debugger.Launch()" (like in m_david's answer above) elsewhere, but inside of CreateDbContext it seems to somehow both attach, and not attach. What I mean is, it attaches and starts trying to step into .asm files and .cpp files (internal code). If I try to set a breakpoint on a Console.Writeline that I KNOW gets executed afterwards (I can see the output from ANY "dotnet ef migrations COMMAND") it both executes it and never hits the breakpoint.
This is what worked for me instead:
while (!System.Diagnostics.Debugger.IsAttached)
System.Threading.Thread.Sleep(10);
// Breakpoint after this...
You can execute the migration and manually attach using Visual Studio and it will actually let you step through the code like you expect, it's just more of a pain. What I should really try is the combination of both methods...
I also found a neat trick here to get the error details...
Basically, the trick is to grab all the information from an exception, put it in a string and throw a new DbEntityValidationException with the generated string and the original exception.

stuck in EF migration limbo

i have somehow gotten my EF5 project into a state where I can't proceed.
When I do an 'update-database' i get:
Unable to update database to match the current model because there are pending changes and automatic migration is disabled. Either write the pending model changes to a code-based migration or enable automatic migration. Set DbMigrationsConfiguration.AutomaticMigrationsEnabled to true to enable automatic migration.
You can use the Add-Migration command to write the pending model changes to a code-based migration.
ok, fine, so i try to 'add-migration', and i get:
Unable to generate an explicit migration because the following explicit migrations are pending: [ ]. Apply the pending explicit migrations before attempting to generate a new explicit migration.
20 GOTO 10 ??
what am i supposed to do at this point? (beyond switching to NHibernate?)
What worked for me was:
Reverting all my changes (making a backup first) back to a known good state.
Doing add-migration DummyMigration. That produced some spurious changes that I commented
Called update-database to add the migration + metadata to the [__MigrationHistory] table.
Make the changes I needed (from the backup of the code I took earlier).
Do the normal add-migration/update-database.
Not ideal, and would be cool to see if there's a better solution, but that worked for me.
For me the issue was that we had renamed the namespace of the migration 2014123456_Initial.cs.
But VS had not regenerated the namespace in its associated 2014123456_Initial.Designer.cs.
Once the Designer.cs was changed to use the same namespace, it all started working again.
(also posted this as an answer here because both questions are so similar)
I changed the following value in my Configuration.cs class from false to true.
AutomaticMigrationsEnabled = true;
In App_Data folder I renamed my project's database - the file with .mdf ending (so a new one will be created), and in Package Manager Console I entered the following command:
update-database
after which the pending migrations ran smoothly.
Note this worked for me, but I'm not 100% if this is the best practice. In any case this Entity Framework Code First guide for migrations says:
"If you get an error that indicates a table already exists and can't be created, it is probably because you ran the application after you deleted the database and before you executed update-database. In that case, delete the Movies.mdf file again and retry the update-database command. If you still get an error, delete the migrations folder.."
Also about AutomaticMigrationsEnabled = true; this MSDN article, Data Points : A Code First Migrations Mystery: Solved tells "Migrations can run automatically, meaning that model changes will be discovered and migrations corresponding to changes will be created and executed on the database. All of this happens at run time during database initialization. Automatic migrations are handy for simple apps, but you have very little control over them and I typically don’t recommend enabling them. I was happy when Code First switched the default to false."
Update-Database –TargetMigration <second_last_migration>
Add-Migration <full_name_including_timestamp_of_last_migration>
You need to include the timestamp so that migrations knows you want to edit the existing migration rather than scaffolding a new one. This will update the metadata for the last migration to match the current model.
Update-Database
Source https://learn.microsoft.com/en-gb/ef/ef6/modeling/code-first/migrations/teams#resolving-the-merge-conflict
I did mistake in creation database
using System.ComponentModel.DataAnnotations;
using System.Globalization;
namespace ProductsManager.Models
{
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Production { get; set; }
public string Size { get; set; }
public decimal<--- Price { get; set; }
public string Barcode { get; set; }
}
}
after add-migration Initial I realized and changed code to public int Price { get; set; }
did same add-migration DummyMigration and its created in migration folder
080372472_dummyMigration
namespace IdetityBeta.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class DummyMigration : DbMigration
{
public override void Up()
{
AlterColumn("dbo.Products", "Price", c => c.String());
}
public override void Down()
{
AlterColumn("dbo.Products", "Price", c => c.Decimal(nullable: false, precision: 18, scale: 2));
}
}
}
So then update-database and problem was solved
I got around this by
Run "Update-Database -Script -Force"
note the last explicit migration attempted before the error "Unable to update database..."
Run "Update-Database -Script -TargetMigration [lastmigration]" using the last migration noted from the previous attempt
I could then run in the script and add a new migration. This is on EF6 and Nuget 2.8 - it may not have worked when the question was posted.
This can happen when you are trying to merge with migration which has the same base as your migration because of which there is model difference in one of migration schema even though that table exists
To solve this problem what you can do is create merge migration by command
Add-Migration -IgnoreChanges
followed by migration name
This creates an empty migration with the current model as a snapshot. Thus this will solve your model difference problem and your tables and models will be in sync