Skip to content

Commit fe4a364

Browse files
committed
Uniquify all variables used in SQL Server migration scripts
Fixes #35132
1 parent 3f5dcef commit fe4a364

File tree

9 files changed

+552
-480
lines changed

9 files changed

+552
-480
lines changed

src/EFCore.Relational/Migrations/MigrationCommandListBuilder.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,18 @@ public virtual MigrationCommandListBuilder AppendLine(string value)
9999
return this;
100100
}
101101

102+
/// <summary>
103+
/// Appends the given string to the command being built, and then starts a new line.
104+
/// </summary>
105+
/// <param name="value">The string to append.</param>
106+
/// <returns>This builder so that additional calls can be chained.</returns>
107+
public virtual MigrationCommandListBuilder AppendLine(FormattableString value)
108+
{
109+
_commandBuilder.AppendLine(value);
110+
111+
return this;
112+
}
113+
102114
/// <summary>
103115
/// Appends the given object to the command being built as multiple lines of text. That is,
104116
/// each line in the passed string is added as a line to the command being built.

src/EFCore.Relational/Storage/IRelationalCommandBuilder.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ public interface IRelationalCommandBuilder
5656
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
5757
IRelationalCommandBuilder Append(string value);
5858

59+
/// <summary>
60+
/// Appends an object to the command text.
61+
/// </summary>
62+
/// <param name="value">The object to be written.</param>
63+
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
64+
IRelationalCommandBuilder Append(FormattableString value);
65+
5966
/// <summary>
6067
/// Appends a blank line to the command text.
6168
/// </summary>

src/EFCore.Relational/Storage/RelationalCommandBuilder.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Text;
5+
46
namespace Microsoft.EntityFrameworkCore.Storage;
57

68
/// <inheritdoc />
@@ -71,6 +73,14 @@ public virtual IRelationalCommandBuilder Append(string value)
7173
return this;
7274
}
7375

76+
/// <inheritdoc />
77+
public virtual IRelationalCommandBuilder Append(FormattableString value)
78+
{
79+
_commandTextBuilder.Append(value);
80+
81+
return this;
82+
}
83+
7484
/// <inheritdoc />
7585
public virtual IRelationalCommandBuilder AppendLine()
7686
{

src/EFCore.Relational/Storage/RelationalCommandBuilderExtensions.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,21 @@ public static IRelationalCommandBuilder AppendLine(
3030
return commandBuilder;
3131
}
3232

33+
/// <summary>
34+
/// Appends an object to the command text on a new line.
35+
/// </summary>
36+
/// <param name="commandBuilder">The command builder.</param>
37+
/// <param name="value">The object to be written.</param>
38+
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
39+
public static IRelationalCommandBuilder AppendLine(
40+
this IRelationalCommandBuilder commandBuilder,
41+
FormattableString value)
42+
{
43+
commandBuilder.Append(value).AppendLine();
44+
45+
return commandBuilder;
46+
}
47+
3348
/// <summary>
3449
/// Appends an object, that contains multiple lines of text, to the command text.
3550
/// Each line read from the object is appended on a new line.

src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs

Lines changed: 56 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ namespace Microsoft.EntityFrameworkCore.Migrations;
2929
public class SqlServerMigrationsSqlGenerator : MigrationsSqlGenerator
3030
{
3131
private IReadOnlyList<MigrationOperation> _operations = null!;
32-
private int _variableCounter;
32+
private int _variableCounter = -1;
3333

3434
private readonly ICommandBatchPreparer _commandBatchPreparer;
3535

@@ -643,25 +643,23 @@ protected override void Generate(
643643

644644
subBuilder.Append(")");
645645

646+
var historyTableName = operation[SqlServerAnnotationNames.TemporalHistoryTableName] as string;
647+
string historyTable;
646648
if (needsExec)
647649
{
648650
subBuilder
649651
.EndCommand();
650652

651653
var execBody = subBuilder.GetCommandList().Single().CommandText.Replace("'", "''");
652654

655+
var schemaVariable = Uniquify("@historyTableSchema");
653656
builder
654-
.AppendLine("DECLARE @historyTableSchema sysname = SCHEMA_NAME()")
657+
.AppendLine($"DECLARE {schemaVariable} sysname = SCHEMA_NAME()")
655658
.Append("EXEC(N'")
656659
.Append(execBody);
657-
}
658660

659-
var historyTableName = operation[SqlServerAnnotationNames.TemporalHistoryTableName] as string;
660-
string historyTable;
661-
if (needsExec)
662-
{
663661
historyTable = Dependencies.SqlGenerationHelper.DelimitIdentifier(historyTableName!);
664-
tableCreationOptions.Add($"SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + N'].{historyTable})");
662+
tableCreationOptions.Add($"SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + {schemaVariable} + N'].{historyTable})");
665663
}
666664
else
667665
{
@@ -1116,10 +1114,11 @@ protected override void Generate(
11161114
{
11171115
if (operation[SqlServerAnnotationNames.EditionOptions] is string editionOptions)
11181116
{
1117+
var dbVariable = Uniquify("@db_name");
11191118
builder
11201119
.AppendLine("BEGIN")
1121-
.AppendLine("DECLARE @db_name nvarchar(max) = DB_NAME();")
1122-
.AppendLine("EXEC(N'ALTER DATABASE [' + @db_name + '] MODIFY ( ")
1120+
.AppendLine($"DECLARE {dbVariable} nvarchar(max) = DB_NAME();")
1121+
.AppendLine($"EXEC(N'ALTER DATABASE [' + {dbVariable} + '] MODIFY ( ")
11231122
.Append(editionOptions.Replace("'", "''"))
11241123
.AppendLine(" );');")
11251124
.AppendLine("END")
@@ -1128,19 +1127,21 @@ protected override void Generate(
11281127

11291128
if (operation.Collation != operation.OldDatabase.Collation)
11301129
{
1130+
var dbVariable = Uniquify("@db_name");
11311131
builder
11321132
.AppendLine("BEGIN")
1133-
.AppendLine("DECLARE @db_name nvarchar(max) = DB_NAME();");
1133+
.AppendLine($"DECLARE {dbVariable} nvarchar(max) = DB_NAME();");
11341134

1135+
var collation = operation.Collation;
11351136
if (operation.Collation == null)
11361137
{
1137-
builder.AppendLine("DECLARE @defaultCollation nvarchar(max) = CAST(SERVERPROPERTY('Collation') AS nvarchar(max));");
1138+
var collationVariable = Uniquify("@defaultCollation");
1139+
builder.AppendLine($"DECLARE {collationVariable} nvarchar(max) = CAST(SERVERPROPERTY('Collation') AS nvarchar(max));");
1140+
collation = "' + " + collationVariable + " + N'";
11381141
}
11391142

11401143
builder
1141-
.Append("EXEC(N'ALTER DATABASE [' + @db_name + '] COLLATE ")
1142-
.Append(operation.Collation ?? "' + @defaultCollation + N'")
1143-
.AppendLine(";');")
1144+
.AppendLine($"EXEC(N'ALTER DATABASE [' + {dbVariable} + '] COLLATE {collation};');")
11441145
.AppendLine("END")
11451146
.AppendLine();
11461147
}
@@ -1167,10 +1168,11 @@ protected override void Generate(
11671168

11681169
using (builder.Indent())
11691170
{
1171+
var dbVariable = Uniquify("@db_name");
11701172
builder
11711173
.AppendLine("BEGIN")
11721174
.AppendLine("ALTER DATABASE CURRENT SET AUTO_CLOSE OFF;")
1173-
.AppendLine("DECLARE @db_name nvarchar(max) = DB_NAME();")
1175+
.AppendLine($"DECLARE {dbVariable} nvarchar(max) = DB_NAME();")
11741176
.AppendLine("DECLARE @fg_name nvarchar(max);")
11751177
.AppendLine("SELECT TOP(1) @fg_name = [name] FROM [sys].[filegroups] WHERE [type] = N'FX';")
11761178
.AppendLine()
@@ -1180,20 +1182,21 @@ protected override void Generate(
11801182
{
11811183
builder
11821184
.AppendLine("BEGIN")
1183-
.AppendLine("SET @fg_name = @db_name + N'_MODFG';")
1185+
.AppendLine($"SET @fg_name = {dbVariable} + N'_MODFG';")
11841186
.AppendLine("EXEC(N'ALTER DATABASE CURRENT ADD FILEGROUP [' + @fg_name + '] CONTAINS MEMORY_OPTIMIZED_DATA;');")
11851187
.AppendLine("END");
11861188
}
11871189

1190+
var pathVariable = Uniquify("@path");
11881191
builder
11891192
.AppendLine()
1190-
.AppendLine("DECLARE @path nvarchar(max);")
1191-
.Append("SELECT TOP(1) @path = [physical_name] FROM [sys].[database_files] ")
1193+
.AppendLine($"DECLARE {pathVariable} nvarchar(max);")
1194+
.Append($"SELECT TOP(1) {pathVariable} = [physical_name] FROM [sys].[database_files] ")
11921195
.AppendLine("WHERE charindex('\\', [physical_name]) > 0 ORDER BY [file_id];")
1193-
.AppendLine("IF (@path IS NULL)")
1194-
.IncrementIndent().AppendLine("SET @path = '\\' + @db_name;").DecrementIndent()
1196+
.AppendLine($"IF ({pathVariable} IS NULL)")
1197+
.IncrementIndent().AppendLine($"SET {pathVariable} = '\\' + {dbVariable};").DecrementIndent()
11951198
.AppendLine()
1196-
.AppendLine("DECLARE @filename nvarchar(max) = right(@path, charindex('\\', reverse(@path)) - 1);")
1199+
.AppendLine($"DECLARE @filename nvarchar(max) = right({pathVariable}, charindex('\\', reverse({pathVariable})) - 1);")
11971200
.AppendLine(
11981201
"SET @filename = REPLACE(left(@filename, len(@filename) - charindex('.', reverse(@filename))), '''', '''''') + N'_MOD';")
11991202
.AppendLine(
@@ -1767,10 +1770,11 @@ protected virtual void Transfer(
17671770
{
17681771
var stringTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(string));
17691772

1773+
var schemaVariable = Uniquify("@defaultSchema");
17701774
builder
1771-
.AppendLine("DECLARE @defaultSchema sysname = SCHEMA_NAME();")
1775+
.AppendLine($"DECLARE {schemaVariable} sysname = SCHEMA_NAME();")
17721776
.Append("EXEC(")
1773-
.Append("N'ALTER SCHEMA [' + @defaultSchema + ")
1777+
.Append($"N'ALTER SCHEMA [' + {schemaVariable} + ")
17741778
.Append(
17751779
stringTypeMapping.GenerateSqlLiteral(
17761780
"] TRANSFER " + Dependencies.SqlGenerationHelper.DelimitIdentifier(name, schema) + ";"))
@@ -1936,7 +1940,7 @@ protected virtual void DropDefaultConstraint(
19361940
{
19371941
var stringTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(string));
19381942

1939-
var variable = "@var" + _variableCounter++;
1943+
var variable = Uniquify("@var");
19401944

19411945
builder
19421946
.Append("DECLARE ")
@@ -2067,18 +2071,18 @@ protected virtual void AddDescription(
20672071
string? column = null,
20682072
bool omitVariableDeclarations = false)
20692073
{
2070-
string schemaLiteral;
2074+
var schemaLiteral = Uniquify("@defaultSchema", increase: !omitVariableDeclarations);
2075+
var descriptionVariable = Uniquify("@description", increase: false);
2076+
20712077
if (schema == null)
20722078
{
20732079
if (!omitVariableDeclarations)
20742080
{
2075-
builder.Append("DECLARE @defaultSchema AS sysname")
2081+
builder.Append($"DECLARE {schemaLiteral} AS sysname")
20762082
.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
2077-
builder.Append("SET @defaultSchema = SCHEMA_NAME()")
2083+
builder.Append($"SET {schemaLiteral} = SCHEMA_NAME()")
20782084
.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
20792085
}
2080-
2081-
schemaLiteral = "@defaultSchema";
20822086
}
20832087
else
20842088
{
@@ -2087,16 +2091,15 @@ protected virtual void AddDescription(
20872091

20882092
if (!omitVariableDeclarations)
20892093
{
2090-
builder.Append("DECLARE @description AS sql_variant")
2094+
builder.Append($"DECLARE {descriptionVariable} AS sql_variant")
20912095
.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
20922096
}
20932097

2094-
builder.Append("SET @description = ")
2095-
.Append(Literal(description))
2098+
builder.Append($"SET {descriptionVariable} = {Literal(description)}")
20962099
.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
20972100
builder
20982101
.Append("EXEC sp_addextendedproperty 'MS_Description', ")
2099-
.Append("@description")
2102+
.Append(descriptionVariable)
21002103
.Append(", 'SCHEMA', ")
21012104
.Append(schemaLiteral)
21022105
.Append(", 'TABLE', ")
@@ -2249,18 +2252,17 @@ protected virtual void DropDescription(
22492252
{
22502253
var stringTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(string));
22512254

2252-
string schemaLiteral;
2255+
var schemaLiteral = Uniquify("@defaultSchema", increase: !omitVariableDeclarations);
2256+
var descriptionVariable = Uniquify("@description", increase: false);
22532257
if (schema == null)
22542258
{
22552259
if (!omitVariableDeclarations)
22562260
{
2257-
builder.Append("DECLARE @defaultSchema AS sysname")
2261+
builder.Append($"DECLARE {schemaLiteral} AS sysname")
22582262
.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
2259-
builder.Append("SET @defaultSchema = SCHEMA_NAME()")
2263+
builder.Append($"SET {schemaLiteral} = SCHEMA_NAME()")
22602264
.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
22612265
}
2262-
2263-
schemaLiteral = "@defaultSchema";
22642266
}
22652267
else
22662268
{
@@ -2269,7 +2271,7 @@ protected virtual void DropDescription(
22692271

22702272
if (!omitVariableDeclarations)
22712273
{
2272-
builder.Append("DECLARE @description AS sql_variant")
2274+
builder.Append($"DECLARE {descriptionVariable} AS sql_variant")
22732275
.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
22742276
}
22752277

@@ -2379,6 +2381,16 @@ private static bool HasDifferences(IEnumerable<IAnnotation> source, IEnumerable<
23792381
return count != targetAnnotations.Count;
23802382
}
23812383

2384+
private string Uniquify(string variableName, bool increase = true)
2385+
{
2386+
if (increase)
2387+
{
2388+
_variableCounter++;
2389+
}
2390+
2391+
return _variableCounter == 0 ? variableName : variableName + _variableCounter;
2392+
}
2393+
23822394
private IReadOnlyList<MigrationOperation> RewriteOperations(
23832395
IReadOnlyList<MigrationOperation> migrationOperations,
23842396
IModel? model,
@@ -3071,10 +3083,12 @@ void EnableVersioning(string table, string? schema, string historyTableName, str
30713083
{
30723084
var stringBuilder = new StringBuilder();
30733085

3086+
string? schemaVariable = null;
30743087
if (historyTableSchema == null)
30753088
{
3089+
schemaVariable = Uniquify("@historyTableSchema");
30763090
// need to run command using EXEC to inject default schema
3077-
stringBuilder.AppendLine("DECLARE @historyTableSchema sysname = SCHEMA_NAME()");
3091+
stringBuilder.AppendLine($"DECLARE {schemaVariable} sysname = SCHEMA_NAME()");
30783092
stringBuilder.Append("EXEC(N'");
30793093
}
30803094

@@ -3093,7 +3107,7 @@ void EnableVersioning(string table, string? schema, string historyTableName, str
30933107
else
30943108
{
30953109
stringBuilder.AppendLine(
3096-
$" SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + '].{historyTable}))')");
3110+
$" SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + {schemaVariable} + '].{historyTable}))')");
30973111
}
30983112

30993113
operations.Add(

0 commit comments

Comments
 (0)