efcore: SQL Server Migrations: Idempotent scripts fails with 'invalid column name' (needs EXEC)
I have encountered a problem with idempotent migration scripts that make them fail in our continous integration system. The reason is that some migration scripts are parsed even though they are not going to be executed.
This issue is much like issue #10717. This issue can be reproduced by manipulating EF types i a sequence of migrations.
In example I have these two migrations, the first is adding a unique index for a nullable column, the second is removing the column:
IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20180519095519_AddIndexToMyTable')
BEGIN
CREATE UNIQUE INDEX [NameIndex] ON [MyTable] ([Name]) WHERE [Name] IS NOT NULL;
END;
IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20180720070812_RemoveNameFromMyTable')
BEGIN
DROP INDEX [NameIndex] ON [MyTable];
END;
GO
IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20180720070812_RemoveNameFromMyTable')
BEGIN
DECLARE @var19 sysname;
SELECT @var19 = [d].[name]
FROM [sys].[default_constraints] [d]
INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id]
WHERE ([d].[parent_object_id] = OBJECT_ID(N'[MyTable]') AND [c].[name] = N'Name');
IF @var19 IS NOT NULL EXEC(N'ALTER TABLE [MyTable] DROP CONSTRAINT [' + @var19 + '];');
ALTER TABLE [MyTable] DROP COLUMN [Name];
END;
GO
This will work fine in the first execution, just as #10717
The second time this in run in our CI system, MyTable will no longer have the column “Name” and will fail in the execution of
IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20180519095519_AddIndexToMyTable')
BEGIN
CREATE UNIQUE INDEX [NameIndex] ON [MyTable] ([Name]) WHERE [Name] IS NOT NULL;
END;
The SQL error message is
Invalid column name 'Name'.
This happens even though the migration “AddIndexToMyTable” has been installed and the “If not exists…” statement should avoid execution of the script, but as i happens it is parsed anyways, making it fail!
Steps to reproduce
- Create a entity type with a nullable field with a unique index using fluent API
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<MyTable>().HasIndex(c => c.Name).IsUnique();
}
- Make a new migration
- Remove the field Name from MyTable and remove the unique index in OnModelCreating
- Make a new migration
- Create an idempotent migration script with
dotnet ef migrations script -o migrationscript.sql --startup-project MyProject.csproj --configuration release --idempotent
- execute migrationscript.sql twice on the database making it fail
Proposal for a solution
This problem only occurs because the script section is parsed in the sql server even though it is not going to be executed. If this could be avoided the problem would not occur. It could be solved by using dynamic a sql script, as:
IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20180519095519_AddIndexToMyTable')
BEGIN
EXEC('CREATE UNIQUE INDEX [NameIndex] ON [MyTable] ([Name]) WHERE [Name] IS NOT NULL;')
END;
As it is i’m adding the exec to the migration script with powershell in CI, but I believe that everyone would be happier if we could rely on the script produced by EFCore 😃
Further technical details
EF Core version: 2.1.1
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 55
- Comments: 62 (17 by maintainers)
Commits related to this issue
- fix: ATLAS-366: Squash donor import SQL migrations See https://github.com/dotnet/efcore/issues/12911 for known issue with renaming indexes. "Execute" Workaround had caused a state where migrations di... — committed to Anthony-Nolan/Atlas by benbelow 4 years ago
- SqlServer Migrations: Add EXEC calls to idempotent script Also adds the ability for providers to detected whether a script is being generated and whether its idempotent. (resolves #14746) Fixes #129... — committed to bricelam/efcore by bricelam 4 years ago
- SqlServer Migrations: Add EXEC calls to idempotent script Also adds the ability for providers to detected whether a script is being generated and whether its idempotent. (resolves #14746) Fixes #129... — committed to bricelam/efcore by bricelam 4 years ago
Hi,
Another workaround is to globally wrap SQL scripts with EXECUTE() statement. In case of someone looking for such an approach here is very dumb (roughly tested) implementation:
Then somewhere in your IDesignTimeDbContextFactory implementation:
options.ReplaceService<IRelationalCommandBuilderFactory, DynamicSqlRelationalCommandBuilderFactory>();We have been using this extension method in place of
migrationBuilder.Sql("...")for years now and it has worked great for us.Has there been any progress on this? This is causing the idempotent script to fail on simple column rename operations.
We are experiencing the same issue with hand written updates after column was removed/renamed:
migrationBuilder.Sql(@"UPDATE [Foo] SET [Bar] = 15 WHERE [Bar] = 10");- it would be great if this was also wrapped when generating a script.Triage: for 3.0, we will investigate the minimal set of places where we have to wrap in an exec call to avoid the SQL Server parsing issue. Worst case, we will have to do it everywhere. Note that this should only be done when generating idempotent scripts.
If it helps anyone, this seems to cover most/all the cases described above for anyone who is using the command builder hack from earlier in this issue:
Follow up to @CezaryKMakingWaves great workaround, I’ve updated the code to work with EF Core 3.0
Along with the design time options replacement
The workaround proposed by @Inzanit no longer works with EF Core 3.x because the interface has been changed. I rewrote the workaround to work with EF Core 3.x.
Along with the design time options replacement
Please be aware that this might not work with Stored Procedures as mentioned above.
Escape them manually. Users are responsible for ensuring their custom SQL statements work with the idempotent script. (Since we generally avoid trying to parse SQL in EF)
@gabrielionita If you implement
IDesignTimeDbContextFactory<ApplicationContext>then the tooling will use that configuration when generating migrations instead of the configuration inStartup.cs.https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/dbcontext-creation#from-a-design-time-factory
This issue is currently in the 5.0.0 milestone. 5.0 is the next non-patch release of EF Core scheduled for November 2020. I’ll do my best to get a fix in by then. Until then, keep post-processing the SQL file or use a non-idempotent script (e.g.
dotnet ef migrations script %CURRENT_PRODUCTION_MIGRATION%)Ok, I’ve worked around this by using a powershell task in my release pipeline to munge the migration script in the artifact drop folder before executing it. I’m using regex to wrap all
CREATE VIEWandCREATE TRIGGERblocks withEXEC('...'). Here’s my inline script:Make sure you tick the box to use PowerShell Core (for the
-rawparameter support)For those using Azure DevOps, we did release a set of .NET Core Pipeline tasks. A temporary workaround for this issue is included as an option in our tasks that script the migrations (https://marketplace.visualstudio.com/items?itemName=iowacomputergurus.dotnetcore-pipeline-tasks)
Just a workaround until this can be truly fixed
The payload of that is specific to SQL Server. I don’t think there’s particularly high overhead to putting that into a project yourself, personally.
It’s less of a security concern and more of a behavioural quirk, I’d say mainly because this is largely dependent on the engine you’re targeting.
Super useful, great idea, we’re building the idempotent script and publishing as an artifact. At the time of build we can’t determine which migrations have been applied, just when we’re running the release. Should resolve the problem described in this issue about the invalid column name, which we’re experiencing 👍.
Worth noting that this only happens when we’re running the release through Azure DevOps pipelines. It works fine If I take the script and run it through MSSMS.
I’ve found another workaround to this issue by using a powershell script to edit the Idempotent script prior to executing it. It fetches the list of MigrationIds and comments out the relevant script blocks.
We also wrap all of our
migrationBuilder.Sql("...")calls withEXECto prevent future failures if schema’s change. 😃And
CREATE SCHEMAfor the temporal tables.Thanks for the PR!
This blew up all of my entity framework calls. I am now getting syntactical errors in the ef generated sql scripts that get built from my ef queries. Here is an example error
Must declare the scalar @__id_Value_0It fixed my pipeline issues but it causes other problems on my apps utilizing this context. I was able to verify the issue was the custom code by commenting it out locally and verifying the issue was gone
EDIT
It’s an ugly hack but I ended up throwing a simple conditional in the code that will only wrap the commands in “EXEC” if the command contains “CREATE PROCEDURE” or “ALTER PROCEDURE”. These commands were the ones giving me problems
I came here looking for issues around creating VIEWs and TRIGGERs with an
--idempotentflavoured migration script, and I get a different error, albeit should probably be thrown into the same bucket as this one. The script generated bydotnet ef migrations script ...won’t parse at all because of the way it groupsCREATE VIEWandCREATE TRIGGERstatements.According to the SQL laws laid down by Microsoft,
CREATE VIEWneeds to be the first statement in the query batch, but it isn’t, because there’s anIF NOT EXISTSstatement there first. Here’s an example from my idempotent script that was generated:This incurs
Incorrect syntax near the keyword 'VIEW'.when attempting to parse it.More info here:
https://dba.stackexchange.com/questions/215699/if-not-exists-using-object-id-doesnt-work-for-views-and-triggers-why
This ultimately makes dotnet ef migrations script unusable for release automation, arguably it’s principal purpose. Please fix (one solution is to emit a
DROP VIEWguarded by theIF, and always recreate the VIEW [or trigger]).Yep. As a wrote above the only problem I found was the specific scenario specific where I have created an index for a nullable column and later remove it. The problem arises when the CREATE UNIQUE INDEX statement is evaluated even though it is not executed. So I have chosen to wrap all CREATE UNIQUE INDEX statements in the SQL in EXEC blocks. This has the effect that the script is dynamic and will only be evaluated when it actually is executed - fixing the immediate problem.
I use Team City as my build tools. You should be able to use this approach with any build tool.
I have a build step where I create the idempotent SQL file as mentioned in my first post. This step is executed as a command line step using the dotnet cli : dotnet ef migration script…etc (see my first post) This creates the idempotent script ‘migrationscript.sql’
Next I have a powershell script step where I replace CREATE UNIQUE INDEX using regular expressions. The code is here:
As I understand it this problem will be fixed at some point in EF CORE, so this is working fine for me for now. 😃
Hope this helps, /Christian