From 79d0942385ae0c1db2e42701d00a4c3352cd1d96 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Mon, 31 Aug 2020 22:11:11 -0700 Subject: [PATCH] Add the column type to the migration when deleting a data using a removed column Add fluent overloads to specify column types for Insert/Update/Delete operations Fixes #21886 Fixes #22302 --- .../CSharpMigrationOperationGenerator.cs | 18 ++ .../Internal/MigrationsModelDiffer.cs | 36 +++ .../Migrations/MigrationBuilder.cs | 288 +++++++++++++++++- .../Operations/DeleteDataOperation.cs | 2 +- .../Operations/InsertDataOperation.cs | 2 +- .../Operations/UpdateDataOperation.cs | 4 +- .../Update/ColumnModification.cs | 2 +- .../CSharpMigrationOperationGeneratorTest.cs | 6 + .../Internal/MigrationsModelDifferTest.cs | 147 ++++++++- .../SqlServerMigrationsSqlGeneratorTest.cs | 5 +- 10 files changed, 487 insertions(+), 23 deletions(-) diff --git a/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs index 512ae1274ab..960b708372a 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs @@ -2020,6 +2020,24 @@ protected virtual void Generate( builder.AppendLine(","); + if (operation.KeyColumnTypes != null) + { + if (operation.KeyColumnTypes.Length == 1) + { + builder + .Append("keyColumnType: ") + .Append(Code.Literal(operation.KeyColumnTypes[0])); + } + else + { + builder + .Append("keyColumnTypes: ") + .Append(Code.Literal(operation.KeyColumnTypes)); + } + + builder.AppendLine(","); + } + if (operation.KeyValues.GetLength(0) == 1 && operation.KeyValues.GetLength(1) == 1) { diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index 60eddd1ef29..eeea455302d 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -1097,6 +1097,8 @@ protected virtual IEnumerable Remove([NotNull] IColumn sourc }; operation.AddAnnotations(MigrationsAnnotations.ForRemove(source)); + diffContext.AddDrop(source, operation); + yield return operation; } @@ -2260,11 +2262,20 @@ private IEnumerable GetDataOperations( break; } + var table = command.Entries.First().EntityType.GetTableMappings().Select(m => m.Table) + .First(t => t.Name == command.TableName && t.Schema == command.Schema); + var keyColumns = command.ColumnModifications.Where(col => col.IsKey) + .Select(c => table.FindColumn(c.ColumnName)); + var anyKeyColumnDropped = keyColumns.Any(c => diffContext.FindDrop(c) != null); + yield return new DeleteDataOperation { Schema = command.Schema, Table = command.TableName, KeyColumns = command.ColumnModifications.Where(col => col.IsKey).Select(col => col.ColumnName).ToArray(), + KeyColumnTypes = anyKeyColumnDropped + ? keyColumns.Select(col => col.StoreType).ToArray() + : null, KeyValues = ToMultidimensionalArray( command.ColumnModifications.Where(col => col.IsKey).Select(GetValue).ToArray()), IsDestructiveChange = true @@ -2565,6 +2576,9 @@ private readonly IDictionary _createTableOperation private readonly IDictionary _dropTableOperations = new Dictionary(); + private readonly IDictionary _dropColumnOperations + = new Dictionary(); + private readonly IDictionary _removedTables = new Dictionary(); @@ -2601,6 +2615,17 @@ public virtual void AddDrop([NotNull] ITable source, [NotNull] DropTableOperatio _removedTables.Add(operation, source); } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void AddDrop([NotNull] IColumn source, [NotNull] DropColumnOperation operation) + { + _dropColumnOperations.Add(source, operation); + } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -2660,6 +2685,17 @@ public virtual DropTableOperation FindDrop([NotNull] ITable source) ? operation : null; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual DropColumnOperation FindDrop([NotNull] IColumn source) + => _dropColumnOperations.TryGetValue(source, out var operation) + ? operation + : null; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.Relational/Migrations/MigrationBuilder.cs b/src/EFCore.Relational/Migrations/MigrationBuilder.cs index 653bb5fdb04..91f91b7f6d6 100644 --- a/src/EFCore.Relational/Migrations/MigrationBuilder.cs +++ b/src/EFCore.Relational/Migrations/MigrationBuilder.cs @@ -1180,6 +1180,27 @@ public virtual OperationBuilder InsertData( [CanBeNull] string schema = null) => InsertData(table, new[] { Check.NotEmpty(column, nameof(column)) }, new[] { value }, schema); + /// + /// Builds an to insert a single seed data value for a single column. + /// + /// The table into which the data will be inserted. + /// The name of the column into which the data will be inserted. + /// The store type for the column into which data will be inserted. + /// The value to insert. + /// The schema that contains the table, or to use the default schema. + /// A builder to allow annotations to be added to the operation. + public virtual OperationBuilder InsertData( + [NotNull] string table, + [NotNull] string column, + [NotNull] string columnType, + [CanBeNull] object value, + [CanBeNull] string schema = null) + => InsertData( + table, + new[] { Check.NotEmpty(column, nameof(column)) }, + new[] { Check.NotEmpty(columnType, nameof(columnType)) }, + new[] { value }, schema); + /// /// Builds an to insert a single row of seed data values. /// @@ -1195,6 +1216,23 @@ public virtual OperationBuilder InsertData( [CanBeNull] string schema = null) => InsertData(table, columns, ToMultidimensionalArray(Check.NotNull(values, nameof(values))), schema); + /// + /// Builds an to insert a single row of seed data values. + /// + /// The table into which the data will be inserted. + /// The names of the columns into which the data will be inserted. + /// A list of store types for the columns into which data will be inserted. + /// The values to insert, one value for each column in 'columns'. + /// The schema that contains the table, or to use the default schema. + /// A builder to allow annotations to be added to the operation. + public virtual OperationBuilder InsertData( + [NotNull] string table, + [NotNull] string[] columns, + [NotNull] string[] columnTypes, + [NotNull] object[] values, + [CanBeNull] string schema = null) + => InsertData(table, columns, columnTypes, ToMultidimensionalArray(Check.NotNull(values, nameof(values))), schema); + /// /// Builds an to insert multiple rows of seed data values for a single column. /// @@ -1208,9 +1246,32 @@ public virtual OperationBuilder InsertData( [NotNull] string column, [NotNull] object[] values, [CanBeNull] string schema = null) - => InsertData( + => InsertDataInternal( + table, + new[] { Check.NotEmpty(column, nameof(column)) }, + null, + ToMultidimensionalArray(Check.NotNull(values, nameof(values)), firstDimension: true), + schema); + + /// + /// Builds an to insert multiple rows of seed data values for a single column. + /// + /// The table into which the data will be inserted. + /// The name of the column into which the data will be inserted. + /// The store type for the column into which data will be inserted. + /// The values to insert, one value for each row. + /// The schema that contains the table, or to use the default schema. + /// A builder to allow annotations to be added to the operation. + public virtual OperationBuilder InsertData( + [NotNull] string table, + [NotNull] string column, + [NotNull] string columnType, + [NotNull] object[] values, + [CanBeNull] string schema = null) + => InsertDataInternal( table, new[] { Check.NotEmpty(column, nameof(column)) }, + new[] { Check.NotEmpty(columnType, nameof(columnType)) }, ToMultidimensionalArray(Check.NotNull(values, nameof(values)), firstDimension: true), schema); @@ -1230,6 +1291,38 @@ public virtual OperationBuilder InsertData( [NotNull] string[] columns, [NotNull] object[,] values, [CanBeNull] string schema = null) + => InsertDataInternal(table, columns, null, values, schema); + + /// + /// Builds an to insert multiple rows of seed data values for multiple columns. + /// + /// The table into which the data will be inserted. + /// The names of the columns into which the data will be inserted. + /// A list of store types for the columns into which data will be inserted. + /// + /// The values to insert where each element of the outer array represents a row, and each inner array contains values for each of the + /// columns in 'columns'. + /// + /// The schema that contains the table, or to use the default schema. + /// A builder to allow annotations to be added to the operation. + public virtual OperationBuilder InsertData( + [NotNull] string table, + [NotNull] string[] columns, + [NotNull] string[] columnTypes, + [NotNull] object[,] values, + [CanBeNull] string schema = null) + { + Check.NotEmpty(columnTypes, nameof(columnTypes)); + + return InsertDataInternal(table, columns, columnTypes, values, schema); + } + + private OperationBuilder InsertDataInternal( + string table, + string[] columns, + string[] columnTypes, + object[,] values, + string schema) { Check.NotEmpty(table, nameof(table)); Check.NotNull(columns, nameof(columns)); @@ -1262,6 +1355,30 @@ public virtual OperationBuilder DeleteData( [CanBeNull] string schema = null) => DeleteData(table, new[] { Check.NotNull(keyColumn, nameof(keyValue)) }, new[] { keyValue }, schema); + /// + /// Builds an to delete a single row of seed data. + /// + /// The table from which the data will be deleted. + /// The name of the key column used to select the row to delete. + /// + /// The store type for the column that will be used to identify the rows that should be deleted. + /// + /// The key value of the row to delete. + /// The schema that contains the table, or to use the default schema. + /// A builder to allow annotations to be added to the operation. + public virtual OperationBuilder DeleteData( + [NotNull] string table, + [NotNull] string keyColumn, + [NotNull] string keyColumnType, + [CanBeNull] object keyValue, + [CanBeNull] string schema = null) + => DeleteData( + table, + new[] { Check.NotNull(keyColumn, nameof(keyValue)) }, + new[] { Check.NotNull(keyColumnType, nameof(keyColumnType)) }, + new[] { keyValue }, + schema); + /// /// Builds an to delete a single row of seed data from /// a table with a composite (multi-column) key. @@ -1282,22 +1399,71 @@ public virtual OperationBuilder DeleteData( ToMultidimensionalArray(Check.NotNull(keyValues, nameof(keyValues))), schema); + /// + /// Builds an to delete a single row of seed data from + /// a table with a composite (multi-column) key. + /// + /// The table from which the data will be deleted. + /// The names of the key columns used to select the row to delete. + /// + /// The store types for the columns that will be used to identify the rows that should be deleted. + /// + /// The key values of the row to delete, one value for each column in 'keyColumns'. + /// The schema that contains the table, or to use the default schema. + /// A builder to allow annotations to be added to the operation. + public virtual OperationBuilder DeleteData( + [NotNull] string table, + [NotNull] string[] keyColumns, + [NotNull] string[] keyColumnTypes, + [NotNull] object[] keyValues, + [CanBeNull] string schema = null) + => DeleteDataInternal( + table, + keyColumns, + keyColumnTypes, + ToMultidimensionalArray(Check.NotNull(keyValues, nameof(keyValues))), + schema); + + /// + /// Builds an to delete multiple rows of seed data. + /// + /// The table from which the data will be deleted. + /// The name of the key column used to select the row to delete. + /// The key values of the rows to delete, one value per row. + /// The schema that contains the table, or to use the default schema. + /// A builder to allow annotations to be added to the operation. + public virtual OperationBuilder DeleteData( + [NotNull] string table, + [NotNull] string keyColumn, + [NotNull] object[] keyValues, + [CanBeNull] string schema = null) + => DeleteData( + table, + new[] { Check.NotEmpty(keyColumn, nameof(keyColumn)) }, + ToMultidimensionalArray(Check.NotNull(keyValues, nameof(keyValues)), firstDimension: true), + schema); + /// /// Builds an to delete multiple rows of seed data. /// /// The table from which the data will be deleted. /// The name of the key column used to select the row to delete. + /// + /// The store type for the column that will be used to identify the rows that should be deleted. + /// /// The key values of the rows to delete, one value per row. /// The schema that contains the table, or to use the default schema. /// A builder to allow annotations to be added to the operation. public virtual OperationBuilder DeleteData( [NotNull] string table, [NotNull] string keyColumn, + [NotNull] string keyColumnType, [NotNull] object[] keyValues, [CanBeNull] string schema = null) => DeleteData( table, new[] { Check.NotEmpty(keyColumn, nameof(keyColumn)) }, + new[] { Check.NotEmpty(keyColumnType, nameof(keyColumnType)) }, ToMultidimensionalArray(Check.NotNull(keyValues, nameof(keyValues)), firstDimension: true), schema); @@ -1318,6 +1484,41 @@ public virtual OperationBuilder DeleteData( [NotNull] string[] keyColumns, [NotNull] object[,] keyValues, [CanBeNull] string schema = null) + => DeleteDataInternal(table, keyColumns, null, keyValues, schema); + + /// + /// Builds an to delete multiple rows of seed data from + /// a table with a composite (multi-column) key. + /// + /// The table from which the data will be deleted. + /// The names of the key columns used to select the rows to delete. + /// + /// The store types for the columns that will be used to identify the rows that should be deleted. + /// + /// + /// The key values of the rows to delete, where each element of the outer array represents a row, and each inner array contains values for + /// each of the key columns in 'keyColumns'. + /// + /// The schema that contains the table, or to use the default schema. + /// A builder to allow annotations to be added to the operation. + public virtual OperationBuilder DeleteData( + [NotNull] string table, + [NotNull] string[] keyColumns, + [NotNull] string[] keyColumnTypes, + [NotNull] object[,] keyValues, + [CanBeNull] string schema = null) + { + Check.NotEmpty(keyColumnTypes, nameof(keyColumnTypes)); + + return DeleteDataInternal(table, keyColumns, keyColumnTypes, keyValues, schema); + } + + private OperationBuilder DeleteDataInternal( + string table, + string[] keyColumns, + string[] keyColumnTypes, + object[,] keyValues, + string schema) { Check.NotEmpty(table, nameof(table)); Check.NotNull(keyColumns, nameof(keyColumns)); @@ -1328,6 +1529,7 @@ public virtual OperationBuilder DeleteData( Table = table, Schema = schema, KeyColumns = keyColumns, + KeyColumnTypes = keyColumnTypes, KeyValues = keyValues }; Operations.Add(operation); @@ -1437,6 +1639,40 @@ public virtual OperationBuilder UpdateData( ToMultidimensionalArray(Check.NotNull(values, nameof(values))), schema); + /// + /// Builds an to update a single row of seed data for a table with + /// a composite (multi-column) key. + /// + /// The table containing the data to be updated. + /// The names of the key columns used to select the row to update. + /// + /// A list of store types for the columns that will be used to identify the rows that should be updated. + /// + /// The key values of the row to update, one value for each column in 'keyColumns'. + /// The columns to update. + /// A list of store types for the columns in which data will be updated. + /// The new values, one for each column in 'columns', for the selected row. + /// The schema that contains the table, or to use the default schema. + /// A builder to allow annotations to be added to the operation. + public virtual OperationBuilder UpdateData( + [NotNull] string table, + [NotNull] string[] keyColumns, + [NotNull] string[] keyColumnTypes, + [NotNull] object[] keyValues, + [NotNull] string[] columns, + [NotNull] string[] columnTypes, + [NotNull] object[] values, + [CanBeNull] string schema = null) + => UpdateData( + table, + keyColumns, + keyColumnTypes, + ToMultidimensionalArray(Check.NotNull(keyValues, nameof(keyValues))), + columns, + columnTypes, + ToMultidimensionalArray(Check.NotNull(values, nameof(values))), + schema); + /// /// Builds an to update multiple rows of seed data. /// @@ -1543,6 +1779,54 @@ public virtual OperationBuilder UpdateData( [NotNull] string[] columns, [NotNull] object[,] values, [CanBeNull] string schema = null) + => UpdateDataInternal(table, keyColumns, null, keyValues, columns, null, values, schema); + + /// + /// Builds an to update multiple rows of seed data for a table with + /// a composite (multi-column) key. + /// + /// The table containing the data to be updated. + /// The names of the key columns used to select the rows to update. + /// + /// A list of store types for the columns that will be used to identify the rows that should be updated. + /// + /// + /// The key values of the rows to update, where each element of the outer array represents a row, and each inner array contains values for + /// each of the key columns in 'keyColumns'. + /// + /// The columns to update. + /// A list of store types for the columns in which data will be updated. + /// + /// The values for each update, where each element of the outer array represents a row specified in + /// 'keyValues', and each inner array contains values for each of the columns in 'columns'. + /// + /// The schema that contains the table, or to use the default schema. + /// A builder to allow annotations to be added to the operation. + public virtual OperationBuilder UpdateData( + [NotNull] string table, + [NotNull] string[] keyColumns, + [NotNull] string[] keyColumnTypes, + [NotNull] object[,] keyValues, + [NotNull] string[] columns, + [NotNull] string[] columnTypes, + [NotNull] object[,] values, + [CanBeNull] string schema = null) + { + Check.NotEmpty(keyColumnTypes, nameof(keyColumnTypes)); + Check.NotEmpty(columnTypes, nameof(columnTypes)); + + return UpdateDataInternal(table, keyColumns, keyColumnTypes, keyValues, columns, columnTypes, values, schema); + } + + private OperationBuilder UpdateDataInternal( + string table, + string[] keyColumns, + string[] keyColumnTypes, + object[,] keyValues, + string[] columns, + string[] columnTypes, + object[,] values, + string schema) { Check.NotEmpty(table, nameof(table)); Check.NotNull(keyColumns, nameof(keyColumns)); @@ -1555,8 +1839,10 @@ public virtual OperationBuilder UpdateData( Table = table, Schema = schema, KeyColumns = keyColumns, + KeyColumnTypes = keyColumnTypes, KeyValues = keyValues, Columns = columns, + ColumnTypes = columnTypes, Values = values }; Operations.Add(operation); diff --git a/src/EFCore.Relational/Migrations/Operations/DeleteDataOperation.cs b/src/EFCore.Relational/Migrations/Operations/DeleteDataOperation.cs index 57bc058b590..3a06916fc13 100644 --- a/src/EFCore.Relational/Migrations/Operations/DeleteDataOperation.cs +++ b/src/EFCore.Relational/Migrations/Operations/DeleteDataOperation.cs @@ -35,7 +35,7 @@ public class DeleteDataOperation : MigrationOperation, ITableMigrationOperation public virtual string[] KeyColumns { get; [param: NotNull] set; } /// - /// A list of column store types for the columns that will be used to identify + /// A list of store types for the columns that will be used to identify /// the rows that should be deleted. /// public virtual string[] KeyColumnTypes { get; [param: NotNull] set; } diff --git a/src/EFCore.Relational/Migrations/Operations/InsertDataOperation.cs b/src/EFCore.Relational/Migrations/Operations/InsertDataOperation.cs index 410f86e292a..1f5322d403c 100644 --- a/src/EFCore.Relational/Migrations/Operations/InsertDataOperation.cs +++ b/src/EFCore.Relational/Migrations/Operations/InsertDataOperation.cs @@ -34,7 +34,7 @@ public class InsertDataOperation : MigrationOperation, ITableMigrationOperation public virtual string[] Columns { get; [param: NotNull] set; } /// - /// A list of column store types for the columns into which data will be inserted. + /// A list of store types for the columns into which data will be inserted. /// public virtual string[] ColumnTypes { get; [param: NotNull] set; } diff --git a/src/EFCore.Relational/Migrations/Operations/UpdateDataOperation.cs b/src/EFCore.Relational/Migrations/Operations/UpdateDataOperation.cs index 0a57f27a64d..aa60705c20a 100644 --- a/src/EFCore.Relational/Migrations/Operations/UpdateDataOperation.cs +++ b/src/EFCore.Relational/Migrations/Operations/UpdateDataOperation.cs @@ -36,7 +36,7 @@ public class UpdateDataOperation : MigrationOperation, ITableMigrationOperation public virtual string[] KeyColumns { get; [param: NotNull] set; } /// - /// A list of column store types for the columns that will be used to identify + /// A list of store types for the columns that will be used to identify /// the rows that should be updated. /// public virtual string[] KeyColumnTypes { get; [param: NotNull] set; } @@ -53,7 +53,7 @@ public class UpdateDataOperation : MigrationOperation, ITableMigrationOperation public virtual string[] Columns { get; [param: NotNull] set; } /// - /// A list of column store types for the columns in which data will be updated. + /// A list of store types for the columns in which data will be updated. /// public virtual string[] ColumnTypes { get; [param: NotNull] set; } diff --git a/src/EFCore.Relational/Update/ColumnModification.cs b/src/EFCore.Relational/Update/ColumnModification.cs index c30214fe08d..3a71e04cfc7 100644 --- a/src/EFCore.Relational/Update/ColumnModification.cs +++ b/src/EFCore.Relational/Update/ColumnModification.cs @@ -62,7 +62,7 @@ public ColumnModification( originalValue: null, value: null, property: property, - null, + column.StoreType, typeMapping, isRead: isRead, isWrite: isWrite, diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs index fbcbfa4f8ae..34da79fd593 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs @@ -2836,6 +2836,7 @@ public void DeleteDataOperation_all_args() Schema = "dbo", Table = "People", KeyColumns = new[] { "First Name" }, + KeyColumnTypes = new[] { "string" }, KeyValues = new object[,] { { "Hodor" }, { "Daenerys" }, { "John" }, { "Arya" }, { "Harry" } } }, "mb.DeleteData(" @@ -2846,6 +2847,8 @@ public void DeleteDataOperation_all_args() + _eol + " keyColumn: \"First Name\"," + _eol + + " keyColumnType: \"string\"," + + _eol + " keyValues: new object[]" + _eol + " {" @@ -2880,6 +2883,7 @@ public void DeleteDataOperation_all_args_composite() { Table = "People", KeyColumns = new[] { "First Name", "Last Name" }, + KeyColumnTypes = new[] { "string", "string" }, KeyValues = new object[,] { { "Hodor", null }, { "Daenerys", "Targaryen" }, { "John", "Snow" }, { "Arya", "Stark" }, { "Harry", "Strickland" } @@ -2891,6 +2895,8 @@ public void DeleteDataOperation_all_args_composite() + _eol + " keyColumns: new[] { \"First Name\", \"Last Name\" }," + _eol + + " keyColumnTypes: new[] { \"string\", \"string\" }," + + _eol + " keyValues: new object[,]" + _eol + " {" diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs index 6ec303c6be7..a55ce99ba73 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs @@ -1416,6 +1416,7 @@ public void Add_column_with_seed_data() o => { var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(43, v)); @@ -1487,6 +1488,7 @@ public void Add_seed_data_with_non_writable_column_insert_operations_with_batchi o => { var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(42, v)); @@ -1494,6 +1496,7 @@ public void Add_seed_data_with_non_writable_column_insert_operations_with_batchi o => { var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(43, v)); @@ -1501,6 +1504,7 @@ public void Add_seed_data_with_non_writable_column_insert_operations_with_batchi o => { var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(44, v)); @@ -1508,6 +1512,7 @@ public void Add_seed_data_with_non_writable_column_insert_operations_with_batchi o => { var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(45, v)); @@ -2139,6 +2144,7 @@ public void Alter_key_column_type_with_seed_data() o => { var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(42, v)); @@ -2146,6 +2152,7 @@ public void Alter_key_column_type_with_seed_data() o => { var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(43, v)); @@ -5232,6 +5239,7 @@ public void Add_subtype_with_shared_column_with_seed_data() o => { var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(43, v)); @@ -5684,6 +5692,7 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data() Assert.Equal("Dogs", operation.Table); Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, v => Assert.Equal(21, v)); @@ -5694,6 +5703,7 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data() Assert.Equal("Dogs", operation.Table); Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, v => Assert.Equal(22, v)); @@ -5704,6 +5714,7 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data() Assert.Equal("Dogs", operation.Table); Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, v => Assert.Equal(23, v)); @@ -5724,6 +5735,7 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data() Assert.Equal("Animal", operation.Table); Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, v => Assert.Equal(33, v)); @@ -6321,6 +6333,7 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data_readonly_discriminator() Assert.Equal("Dogs", operation.Table); Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, v => Assert.Equal(21, v)); @@ -6331,6 +6344,7 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data_readonly_discriminator() Assert.Equal("Dogs", operation.Table); Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, v => Assert.Equal(22, v)); @@ -6341,6 +6355,7 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data_readonly_discriminator() Assert.Equal("Dogs", operation.Table); Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, v => Assert.Equal(23, v)); @@ -6411,6 +6426,7 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data_readonly_discriminator() Assert.Equal("Animal", operation.Table); Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, v => Assert.Equal(33, v)); @@ -6909,6 +6925,7 @@ public void Split_out_subtype_with_seed_data() { var operation = Assert.IsType(o); Assert.Equal("Animal", operation.Table); + Assert.Null(operation.KeyColumnTypes); Assert.Collection( operation.KeyColumns, v => Assert.Equal("Id", v)); @@ -6940,6 +6957,7 @@ public void Split_out_subtype_with_seed_data() Assert.Collection( operation.KeyColumns, v => Assert.Equal("Id", v)); + Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, v => Assert.Equal(43, v)); @@ -7238,6 +7256,7 @@ public void Add_foreign_key_referencing_renamed_column_with_seed_data() Assert.Collection( operation.KeyColumns, v => Assert.Equal("ReferencedTableId", v)); + Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, v => Assert.Equal(42, v)); @@ -7972,6 +7991,7 @@ public void Add_foreign_key_referencing_added_alternate_key_with_seed_data() { var operation = Assert.IsType(o); Assert.Equal("Table", operation.Table); + Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, v => Assert.Equal(42, v)); @@ -8021,6 +8041,7 @@ public void Update_AK_seed_value_with_a_referencing_foreign_key() { var operation = Assert.IsType(o); Assert.Equal("ReferencedTable", operation.Table); + Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, v => Assert.Equal(42, v)); @@ -8040,6 +8061,7 @@ public void Update_AK_seed_value_with_a_referencing_foreign_key() { var operation = Assert.IsType(o); Assert.Equal("ReferencedTable", operation.Table); + Assert.Null(operation.KeyColumnTypes); AssertMultidimensionalArray( operation.KeyValues, v => Assert.Equal(42, v)); @@ -8048,6 +8070,7 @@ public void Update_AK_seed_value_with_a_referencing_foreign_key() { var operation = Assert.IsType(o); Assert.Equal("ReferencedTable", operation.Table); + Assert.Null(operation.ColumnTypes); AssertMultidimensionalArray( operation.Values, v => Assert.Equal(42, v), @@ -8056,7 +8079,7 @@ public void Update_AK_seed_value_with_a_referencing_foreign_key() } [ConditionalFact] - public void SeedData_add_on_existing_table() + public void SeedData_and_PK_rename() { Execute( _ => { }, @@ -8064,9 +8087,10 @@ public void SeedData_add_on_existing_table() "EntityWithTwoProperties", x => { - x.Property("Id"); + x.Property("Key"); x.Property("Value1"); x.Property("Value2"); + x.HasKey("Key"); }), target => target.Entity( "EntityWithTwoProperties", @@ -8081,8 +8105,17 @@ public void SeedData_add_on_existing_table() upOps => Assert.Collection( upOps, o => + { + var operation = Assert.IsType(o); + Assert.Equal("EntityWithTwoProperties", operation.Table); + Assert.Equal("Key", operation.Name); + Assert.Equal("Id", operation.NewName); + }, + o => { var m = Assert.IsType(o); + Assert.Equal(new[] { "Id", "Value1", "Value2" }, m.Columns); + Assert.Null(m.ColumnTypes); AssertMultidimensionalArray( m.Values, v => Assert.Equal(42, v), @@ -8094,14 +8127,23 @@ public void SeedData_add_on_existing_table() o => { var m = Assert.IsType(o); + Assert.Equal(new[] { "Id" }, m.KeyColumns); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(42, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("EntityWithTwoProperties", operation.Table); + Assert.Equal("Id", operation.Name); + Assert.Equal("Key", operation.NewName); })); } [ConditionalFact] - public void SeedData_remove() + public void SeedData_and_change_PK_type() { Execute( _ => { }, @@ -8109,11 +8151,10 @@ public void SeedData_remove() "EntityWithTwoProperties", x => { - x.Property("Id"); + x.Property("Key"); x.Property("Value1"); x.Property("Value2"); - x.HasData( - new { Id = 42, Value1 = 32 }); + x.HasKey("Key"); }), target => target.Entity( "EntityWithTwoProperties", @@ -8122,18 +8163,36 @@ public void SeedData_remove() x.Property("Id"); x.Property("Value1"); x.Property("Value2"); + x.HasData( + new { Id = 42, Value1 = 32 }); }), upOps => Assert.Collection( upOps, o => { - var m = Assert.IsType(o); - AssertMultidimensionalArray( - m.KeyValues, - v => Assert.Equal(42, v)); - }), - downOps => Assert.Collection( - downOps, + var operation = Assert.IsType(o); + Assert.Equal("EntityWithTwoProperties", operation.Table); + Assert.Equal("PK_EntityWithTwoProperties", operation.Name); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("EntityWithTwoProperties", operation.Table); + Assert.Equal("Key", operation.Name); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("EntityWithTwoProperties", operation.Table); + Assert.Equal("Id", operation.Name); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("EntityWithTwoProperties", operation.Table); + Assert.Equal("PK_EntityWithTwoProperties", operation.Name); + Assert.Equal(new[] { "Id" }, operation.Columns); + }, o => { var m = Assert.IsType(o); @@ -8141,7 +8200,43 @@ public void SeedData_remove() m.Values, v => Assert.Equal(42, v), v => Assert.Equal(32, v), - v => Assert.Null(v)); + Assert.Null); + }), + downOps => Assert.Collection( + downOps, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("EntityWithTwoProperties", operation.Table); + Assert.Equal("PK_EntityWithTwoProperties", operation.Name); + }, + o => + { + var m = Assert.IsType(o); + Assert.Equal(new[] { "Id" }, m.KeyColumns); + Assert.Equal(new[] { "default_int_mapping" }, m.KeyColumnTypes); + AssertMultidimensionalArray( + m.KeyValues, + v => Assert.Equal(42, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("EntityWithTwoProperties", operation.Table); + Assert.Equal("Id", operation.Name); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("EntityWithTwoProperties", operation.Table); + Assert.Equal("Key", operation.Name); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("EntityWithTwoProperties", operation.Table); + Assert.Equal("PK_EntityWithTwoProperties", operation.Name); + Assert.Equal(new[] { "Key" }, operation.Columns); })); } @@ -8677,6 +8772,7 @@ public void SeedData_all_operations() o => { var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(99999, v)); @@ -8684,6 +8780,7 @@ public void SeedData_all_operations() o => { var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(24, v)); @@ -8695,6 +8792,7 @@ public void SeedData_all_operations() o => { var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(42, v)); @@ -8705,6 +8803,7 @@ public void SeedData_all_operations() o => { var m = Assert.IsType(o); + Assert.Null(m.ColumnTypes); AssertMultidimensionalArray( m.Values, v => Assert.Equal(11111, v), @@ -8714,6 +8813,7 @@ public void SeedData_all_operations() o => { var m = Assert.IsType(o); + Assert.Null(m.ColumnTypes); AssertMultidimensionalArray( m.Values, v => Assert.Equal(11112, v), @@ -8725,6 +8825,7 @@ public void SeedData_all_operations() o => { var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(11111, v)); @@ -8732,6 +8833,7 @@ public void SeedData_all_operations() o => { var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(11112, v)); @@ -8739,6 +8841,7 @@ public void SeedData_all_operations() o => { var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(24, v)); @@ -8750,6 +8853,7 @@ public void SeedData_all_operations() o => { var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(42, v)); @@ -8760,6 +8864,7 @@ public void SeedData_all_operations() o => { var m = Assert.IsType(o); + Assert.Null(m.ColumnTypes); AssertMultidimensionalArray( m.Values, v => Assert.Equal(99999, v), @@ -8832,6 +8937,7 @@ public void SeedData_with_timestamp_column() o => { var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(21, v)); @@ -8839,6 +8945,7 @@ public void SeedData_with_timestamp_column() o => { var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(11, v)); @@ -8849,6 +8956,7 @@ public void SeedData_with_timestamp_column() o => { var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(12, v)); @@ -8861,6 +8969,7 @@ public void SeedData_with_timestamp_column() o => { var m = Assert.IsType(o); + Assert.Null(m.ColumnTypes); AssertMultidimensionalArray( m.Values, v => Assert.Equal(31, v), @@ -8869,6 +8978,7 @@ public void SeedData_with_timestamp_column() o => { var m = Assert.IsType(o); + Assert.Null(m.ColumnTypes); AssertMultidimensionalArray( m.Values, v => Assert.Equal(32, v), @@ -8884,6 +8994,7 @@ public void SeedData_with_timestamp_column() o => { var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(31, v)); @@ -8891,6 +9002,7 @@ public void SeedData_with_timestamp_column() o => { var m = Assert.IsType(o); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(32, v)); @@ -9079,6 +9191,7 @@ private void SeedData_with_navigation_properties(Action buildTarge { var m = Assert.IsType(o); Assert.Equal("Post", m.Table); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(390, v)); @@ -9087,6 +9200,7 @@ private void SeedData_with_navigation_properties(Action buildTarge { var m = Assert.IsType(o); Assert.Equal("Blog", m.Table); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(32, v)); @@ -9140,6 +9254,7 @@ private void SeedData_with_navigation_properties(Action buildTarge { var m = Assert.IsType(o); Assert.Equal("Blog", m.Table); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(38, v)); @@ -9156,6 +9271,7 @@ private void SeedData_with_navigation_properties(Action buildTarge { var m = Assert.IsType(o); Assert.Equal("Post", m.Table); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(546, v)); @@ -9164,6 +9280,7 @@ private void SeedData_with_navigation_properties(Action buildTarge { var m = Assert.IsType(o); Assert.Equal("Blog", m.Table); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(32, v)); @@ -9175,6 +9292,7 @@ private void SeedData_with_navigation_properties(Action buildTarge { var m = Assert.IsType(o); Assert.Equal("Post", m.Table); + Assert.Null(m.KeyColumnTypes); AssertMultidimensionalArray( m.KeyValues, v => Assert.Equal(545, v)); @@ -9187,6 +9305,7 @@ private void SeedData_with_navigation_properties(Action buildTarge { var m = Assert.IsType(o); Assert.Equal("Post", m.Table); + Assert.Null(m.ColumnTypes); AssertMultidimensionalArray( m.Values, v => Assert.Equal(390, v), diff --git a/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationsSqlGeneratorTest.cs b/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationsSqlGeneratorTest.cs index aac80af8446..5cb993cbf50 100644 --- a/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationsSqlGeneratorTest.cs +++ b/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationsSqlGeneratorTest.cs @@ -1012,9 +1012,8 @@ public virtual void DeleteData_generates_exec_when_idempotent() .DeleteData( table: "Table1", keyColumn: "Id", - keyValue: 1) - .GetInfrastructure() - .KeyColumnTypes = new[] { "int" }, + keyColumnType: "int", + keyValue: 1), MigrationsSqlGenerationOptions.Idempotent); AssertSql(