Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EF5 Migrations - Duplicate/Re-defined variable bug when dropping constraints | Issue with usage of SQL GO command

Background:

We have a project that uses ef-migrations containing multiple (read ~60) migrations created over a long period of development. Naturally, some of these migrations also involve:

  • dropping constraints1,2
  • creating triggers

All is unicorns and rainbows when we run

Update-Database

because each migration runs as a separate batch. But when creating SQL Scripts for these migrations using

Update-Database -Script

we encountered a few issues as described below:

Problem 1:

When dropping multiple constraints across multiple migration files, the script generated by EF tends to re-declare variables that it uses for dropping. This is because it ensures uniqueness of variable names within the same migration file, but on change of file, it resets the counter, thus overlapping the names.

Problem 2:

SQL enforces that CREATE TRIGGER is always the first statement in a batch. When the script is generated, EF is oblivious to the contents of Sql("CREATE TRIGGER ... "); and thus doesn't treat it any specially. Thus the statement may appear right in the middle of a script file, and error out.

Solution: (or so we thought!)

A common/common-sense solution to the two problems is to insert Begin/End the sql batch at the right places. Manually doing this would make me a very rich man, so that is not an efficient solution.

Instead, we used the technique provided by @DavidSette. Creating a new BatchSqlServerMigrationSqlGenerator inheriting from SqlServerMigrationSqlGenerator which effectively overrides dropColumnOperation and sqlOperation and then forcing a GO statement around the sensitive ones as such:

protected override void Generate (System.Data.Entity.Migrations.Model.DropColumnOperation dropColumnOperation)
{
    base.Generate(dropColumnOperation);
    Statement("GO");
}

Boo Boo:

This solution breaks running Update-Database without the -Script flag with the following error:

System.Data.SqlClient.SqlException (0x80131904): Could not find stored procedure 'GO'.

Which was added by our custom generator. Now I'm not certain why, but there should be a pretty good reason EF doesn't recognise GO!

More Info:

  1. Migrations: Duplicate @var0 variables in script that drops two constraints
  2. The variable name '@number' has already been declared
  3. How can I override SQL scripts generated by MigratorScriptingDecorator
  4. Entity Framework Migrations: Including Go statement only in -Script output

Full error:

Applying code-based migration: 201205181406363_AddTriggerForOverlap.
GO
System.Data.SqlClient.SqlException (0x80131904): Could not find stored procedure 'GO'.
    at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
    at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
    at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
    at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
    at System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async, Int32 timeout)
    at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, String methodName, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite)
    at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
    at System.Data.Entity.Migrations.DbMigrator.ExecuteSql(DbTransaction transaction, MigrationStatement migrationStatement)
    at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.ExecuteSql(DbTransaction transaction, MigrationStatement migrationStatement)
    at System.Data.Entity.Migrations.DbMigrator.ExecuteStatements(IEnumerable`1 migrationStatements)
    at System.Data.Entity.Migrations.Infrastructure.MigratorBase.ExecuteStatements(IEnumerable`1 migrationStatements)
    at System.Data.Entity.Migrations.DbMigrator.ExecuteOperations(String migrationId, XDocument targetModel, IEnumerable`1 operations, Boolean downgrading, Boolean auto)
    at System.Data.Entity.Migrations.DbMigrator.ApplyMigration(DbMigration migration, DbMigration lastMigration)
    at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.ApplyMigration(DbMigration migration, DbMigration lastMigration)
    at System.Data.Entity.Migrations.DbMigrator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId)
    at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId)
    at System.Data.Entity.Migrations.DbMigrator.Update(String targetMigration)
    at System.Data.Entity.Migrations.Infrastructure.MigratorBase.Update(String targetMigration)
    at System.Data.Entity.Migrations.Design.ToolingFacade.UpdateRunner.RunCore()
    at System.Data.Entity.Migrations.Design.ToolingFacade.BaseRunner.Run()
ClientConnectionId:ac53af4b-1f9b-4849-a0da-9eb33b836caf
Could not find stored procedure 'GO'.

So basically fixing the scripts breaks an essential command. Please help me decide which is the lesser of the two evils!

like image 907
bPratik Avatar asked Nov 27 '12 17:11

bPratik


2 Answers

I put a Sql("--<GO>"); at the end of each migration. This runs OK as an applied migration and when I script the SQL I just do a find and replace on "--<GO>" to "GO". A bit manual but works for me. You could put Sql("--<GO>"); around your create trigger statements.

like image 196
Youngs Avatar answered Oct 23 '22 15:10

Youngs


As per msdn

"GO is not a Transact-SQL statement; it is a command recognized by the sqlcmd and osql utilities and SQL Server Management Studio Code editor."

Since you are not using any of the above tools but SqlCommand class to execute your Sql statement Sql Server (and not EF - see the stack trace where the exception originated) is choking on it

like image 26
Pawel Avatar answered Oct 23 '22 15:10

Pawel