From 5aa0783e1374b7263de15b86f149281d8ca77a03 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 17 Dec 2019 15:48:35 +0100 Subject: [PATCH] Implement functional migration tests Closes #19039 --- .../Scaffolding/Metadata/DatabaseColumn.cs | 2 + .../Metadata/DatabaseForeignKey.cs | 2 + .../Scaffolding/Metadata/DatabaseIndex.cs | 2 + .../Metadata/DatabasePrimaryKey.cs | 2 + .../Scaffolding/Metadata/DatabaseSequence.cs | 2 + .../Scaffolding/Metadata/DatabaseTable.cs | 2 + .../Metadata/DatabaseUniqueConstraint.cs | 2 + .../MigrationSqlGeneratorTestBase.cs | 705 ----- .../MigrationsFixtureBase.cs | 108 - .../MigrationsInfrastructureTestBase.cs | 408 +++ .../MigrationsTestBase.cs | 1581 +++++++++-- .../TestUtilities/TestSqlLoggerFactory.cs | 3 +- .../Migrations/MigrationSqlGeneratorTest.cs | 433 --- .../MigrationSqlGeneratorTestBase.cs | 168 ++ .../TestUtilities/ListLoggerFactory.cs | 20 +- .../MigrationsInfrastructureSqlServerTest.cs | 1347 +++++++++ .../MigrationsSqlServerFixture.cs | 30 - .../MigrationsSqlServerTest.cs | 2485 +++++++++-------- .../SqlServerMigrationSqlGeneratorTest.cs | 2073 -------------- .../TestUtilities/SqlServerCondition.cs | 3 +- .../config.json | 3 +- .../SqlServerMigrationSqlGeneratorTest.cs | 553 ++++ .../MigrationsInfrastructureSqliteTest.cs | 1084 +++++++ .../MigrationsSqliteFixture.cs | 12 - .../MigrationsSqliteTest.cs | 1382 ++------- .../SqliteMigrationSqlGeneratorTest.cs | 637 ----- .../SqliteMigrationSqlGeneratorTest.cs | 281 ++ 27 files changed, 6800 insertions(+), 6530 deletions(-) delete mode 100644 test/EFCore.Relational.Specification.Tests/MigrationSqlGeneratorTestBase.cs delete mode 100644 test/EFCore.Relational.Specification.Tests/MigrationsFixtureBase.cs create mode 100644 test/EFCore.Relational.Specification.Tests/MigrationsInfrastructureTestBase.cs delete mode 100644 test/EFCore.Relational.Tests/Migrations/MigrationSqlGeneratorTest.cs create mode 100644 test/EFCore.Relational.Tests/Migrations/MigrationSqlGeneratorTestBase.cs create mode 100644 test/EFCore.SqlServer.FunctionalTests/MigrationsInfrastructureSqlServerTest.cs delete mode 100644 test/EFCore.SqlServer.FunctionalTests/MigrationsSqlServerFixture.cs delete mode 100644 test/EFCore.SqlServer.FunctionalTests/SqlServerMigrationSqlGeneratorTest.cs create mode 100644 test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationSqlGeneratorTest.cs create mode 100644 test/EFCore.Sqlite.FunctionalTests/MigrationsInfrastructureSqliteTest.cs delete mode 100644 test/EFCore.Sqlite.FunctionalTests/MigrationsSqliteFixture.cs delete mode 100644 test/EFCore.Sqlite.FunctionalTests/SqliteMigrationSqlGeneratorTest.cs create mode 100644 test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationSqlGeneratorTest.cs diff --git a/src/EFCore.Relational/Scaffolding/Metadata/DatabaseColumn.cs b/src/EFCore.Relational/Scaffolding/Metadata/DatabaseColumn.cs index a463e9df03b..475957fec45 100644 --- a/src/EFCore.Relational/Scaffolding/Metadata/DatabaseColumn.cs +++ b/src/EFCore.Relational/Scaffolding/Metadata/DatabaseColumn.cs @@ -61,5 +61,7 @@ public DatabaseColumn([NotNull] DatabaseTable table, [NotNull] string name, [Not /// the database will not generate values. /// public virtual ValueGenerated? ValueGenerated { get; set; } + + public override string ToString() => Name; } } diff --git a/src/EFCore.Relational/Scaffolding/Metadata/DatabaseForeignKey.cs b/src/EFCore.Relational/Scaffolding/Metadata/DatabaseForeignKey.cs index ba179c5c09a..cc0c3cfd2a6 100644 --- a/src/EFCore.Relational/Scaffolding/Metadata/DatabaseForeignKey.cs +++ b/src/EFCore.Relational/Scaffolding/Metadata/DatabaseForeignKey.cs @@ -58,5 +58,7 @@ public DatabaseForeignKey( /// is deleted, or null if there is no action defined. /// public virtual ReferentialAction? OnDelete { get; set; } + + public override string ToString() => Name ?? ""; } } diff --git a/src/EFCore.Relational/Scaffolding/Metadata/DatabaseIndex.cs b/src/EFCore.Relational/Scaffolding/Metadata/DatabaseIndex.cs index 7211a25dd4c..44000493ff6 100644 --- a/src/EFCore.Relational/Scaffolding/Metadata/DatabaseIndex.cs +++ b/src/EFCore.Relational/Scaffolding/Metadata/DatabaseIndex.cs @@ -45,5 +45,7 @@ public DatabaseIndex([NotNull] DatabaseTable table, [CanBeNull] string? name) /// The filter expression, or null if the index has no filter. /// public virtual string? Filter { get; [param: CanBeNull] set; } + + public override string ToString() => Name ?? ""; } } diff --git a/src/EFCore.Relational/Scaffolding/Metadata/DatabasePrimaryKey.cs b/src/EFCore.Relational/Scaffolding/Metadata/DatabasePrimaryKey.cs index 34ee5c00ce2..0b3f5aa3d6e 100644 --- a/src/EFCore.Relational/Scaffolding/Metadata/DatabasePrimaryKey.cs +++ b/src/EFCore.Relational/Scaffolding/Metadata/DatabasePrimaryKey.cs @@ -35,5 +35,7 @@ public DatabasePrimaryKey([NotNull] DatabaseTable table, [CanBeNull] string? nam /// The ordered list of columns that make up the primary key. /// public virtual IList Columns { get; } + + public override string ToString() => Name ?? ""; } } diff --git a/src/EFCore.Relational/Scaffolding/Metadata/DatabaseSequence.cs b/src/EFCore.Relational/Scaffolding/Metadata/DatabaseSequence.cs index f1bd6b2ff25..6488b8bcb6f 100644 --- a/src/EFCore.Relational/Scaffolding/Metadata/DatabaseSequence.cs +++ b/src/EFCore.Relational/Scaffolding/Metadata/DatabaseSequence.cs @@ -63,5 +63,7 @@ public DatabaseSequence([NotNull] DatabaseModel database, [NotNull] string name) /// Indicates whether or not the sequence will start over when the max value is reached, or null if not set. /// public virtual bool? IsCyclic { get; set; } + + public override string ToString() => Schema == null ? Name : $"{Schema}.{Name}"; } } diff --git a/src/EFCore.Relational/Scaffolding/Metadata/DatabaseTable.cs b/src/EFCore.Relational/Scaffolding/Metadata/DatabaseTable.cs index c85a2098122..1c28c1fbc18 100644 --- a/src/EFCore.Relational/Scaffolding/Metadata/DatabaseTable.cs +++ b/src/EFCore.Relational/Scaffolding/Metadata/DatabaseTable.cs @@ -68,5 +68,7 @@ public DatabaseTable([NotNull] DatabaseModel database, [NotNull] string name) /// The list of foreign key constraints defined on the table. /// public virtual IList ForeignKeys { get; } + + public override string ToString() => Schema == null ? Name : $"{Schema}.{Name}"; } } diff --git a/src/EFCore.Relational/Scaffolding/Metadata/DatabaseUniqueConstraint.cs b/src/EFCore.Relational/Scaffolding/Metadata/DatabaseUniqueConstraint.cs index aa91f333b10..7f24cc62a4a 100644 --- a/src/EFCore.Relational/Scaffolding/Metadata/DatabaseUniqueConstraint.cs +++ b/src/EFCore.Relational/Scaffolding/Metadata/DatabaseUniqueConstraint.cs @@ -35,5 +35,7 @@ public DatabaseUniqueConstraint([NotNull] DatabaseTable table, [CanBeNull] strin /// The ordered list of columns that make up the constraint. /// public virtual IList Columns { get; } + + public override string ToString() => Name ?? ""; } } diff --git a/test/EFCore.Relational.Specification.Tests/MigrationSqlGeneratorTestBase.cs b/test/EFCore.Relational.Specification.Tests/MigrationSqlGeneratorTestBase.cs deleted file mode 100644 index d700103e8b2..00000000000 --- a/test/EFCore.Relational.Specification.Tests/MigrationSqlGeneratorTestBase.cs +++ /dev/null @@ -1,705 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Linq; -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Migrations.Operations; -using Microsoft.EntityFrameworkCore.TestUtilities; -using Microsoft.Extensions.DependencyInjection; -using Xunit; - -// ReSharper disable ClassNeverInstantiated.Local -// ReSharper disable InconsistentNaming -namespace Microsoft.EntityFrameworkCore -{ - public abstract class MigrationSqlGeneratorTestBase - { - protected static string EOL => Environment.NewLine; - - protected virtual string Sql { get; set; } - - [ConditionalFact] - public virtual void CreateIndexOperation_with_filter_where_clause() - => Generate( - modelBuilder => modelBuilder.Entity("People").Property("Name").IsRequired(), - new CreateIndexOperation - { - Name = "IX_People_Name", - Table = "People", - Columns = new[] { "Name" }, - Filter = "[Name] IS NOT NULL" - }); - - [ConditionalFact] - public virtual void CreateIndexOperation_with_filter_where_clause_and_is_unique() - => Generate( - modelBuilder => modelBuilder.Entity("People").Property("Name"), - new CreateIndexOperation - { - Name = "IX_People_Name", - Table = "People", - Columns = new[] { "Name" }, - IsUnique = true, - Filter = "[Name] IS NOT NULL AND <> ''" - }); - - [ConditionalFact] - public virtual void AddColumnOperation_with_defaultValue() - => Generate( - new AddColumnOperation - { - Table = "People", - Schema = "dbo", - Name = "Name", - ClrType = typeof(string), - ColumnType = "varchar(30)", - IsNullable = false, - DefaultValue = "John Doe" - }); - - [ConditionalFact] - public virtual void AddColumnOperation_with_defaultValueSql() - => Generate( - new AddColumnOperation - { - Table = "People", - Name = "Birthday", - ClrType = typeof(DateTime), - ColumnType = "date", - IsNullable = true, - DefaultValueSql = "CURRENT_TIMESTAMP" - }); - - [ConditionalFact] - public virtual void AddColumnOperation_without_column_type() - => Generate( - new AddColumnOperation - { - Table = "People", - Name = "Alias", - ClrType = typeof(string) - }); - - [ConditionalFact] - public virtual void AddColumnOperation_with_ansi() - => Generate( - modelBuilder => modelBuilder.Entity("Person").Property("Name").IsUnicode(false), - new AddColumnOperation - { - Table = "Person", - Name = "Name", - ClrType = typeof(string), - IsUnicode = false, - IsNullable = true - }); - - [ConditionalFact] - public virtual void AddColumnOperation_with_unicode_overridden() - => Generate( - modelBuilder => modelBuilder.Entity("Person").Property("Name").IsUnicode(false), - new AddColumnOperation - { - Table = "Person", - Name = "Name", - ClrType = typeof(string), - IsUnicode = true, - IsNullable = true - }); - - [ConditionalFact] - public virtual void AddColumnOperation_with_unicode_no_model() - => Generate( - new AddColumnOperation - { - Table = "Person", - Name = "Name", - ClrType = typeof(string), - IsUnicode = false, - IsNullable = true - }); - - [ConditionalFact] - public virtual void AddColumnOperation_with_fixed_length() - => Generate( - modelBuilder => modelBuilder.Entity("Person").Property("Name").HasMaxLength(100).IsFixedLength(), - new AddColumnOperation - { - Table = "Person", - Name = "Name", - ClrType = typeof(string), - IsUnicode = true, - IsNullable = true, - IsFixedLength = true, - MaxLength = 100 - }); - - [ConditionalFact] - public virtual void AddColumnOperation_with_fixed_length_no_model() - => Generate( - new AddColumnOperation - { - Table = "Person", - Name = "Name", - ClrType = typeof(string), - IsUnicode = false, - IsNullable = true, - IsFixedLength = true, - MaxLength = 100 - }); - - [ConditionalFact] - public virtual void AddColumnOperation_with_maxLength() - => Generate( - modelBuilder => modelBuilder.Entity("Person").Property("Name").HasMaxLength(30), - new AddColumnOperation - { - Table = "Person", - Name = "Name", - ClrType = typeof(string), - MaxLength = 30, - IsNullable = true - }); - - [ConditionalFact] - public virtual void AddColumnOperation_with_maxLength_overridden() - => Generate( - modelBuilder => modelBuilder.Entity("Person").Property("Name").HasMaxLength(30), - new AddColumnOperation - { - Table = "Person", - Name = "Name", - ClrType = typeof(string), - MaxLength = 32, - IsNullable = true - }); - - [ConditionalFact] - public virtual void AddColumnOperation_with_maxLength_no_model() - => Generate( - new AddColumnOperation - { - Table = "Person", - Name = "Name", - ClrType = typeof(string), - MaxLength = 30, - IsNullable = true - }); - - [ConditionalFact] - public virtual void AddColumnOperation_with_maxLength_on_derived() - => Generate( - modelBuilder => - { - modelBuilder.Entity("Person"); - modelBuilder.Entity( - "SpecialPerson", b => - { - b.HasBaseType("Person"); - b.Property("Name").HasMaxLength(30); - }); - - modelBuilder.Entity("MoreSpecialPerson").HasBaseType("SpecialPerson"); - }, - new AddColumnOperation - { - Table = "Person", - Name = "Name", - ClrType = typeof(string), - MaxLength = 30, - IsNullable = true - }); - - [ConditionalFact] - public virtual void AddColumnOperation_with_shared_column() - => Generate( - modelBuilder => - { - modelBuilder.Entity(); - modelBuilder.Entity(); - modelBuilder.Entity(); - }, - new AddColumnOperation - { - Table = "Base", - Name = "Foo", - ClrType = typeof(string), - IsNullable = true - }); - - private class Base - { - // ReSharper disable once UnusedMember.Local - public int Id { get; set; } - } - - private class Derived1 : Base - { - // ReSharper disable once UnusedMember.Local - public string Foo { get; set; } - } - - private class Derived2 : Base - { - // ReSharper disable once UnusedMember.Local - public string Foo { get; set; } - } - - [ConditionalFact] - public virtual void AddForeignKeyOperation_with_name() - => Generate( - new AddForeignKeyOperation - { - Table = "People", - Schema = "dbo", - Name = "FK_People_Companies", - Columns = new[] { "EmployerId1", "EmployerId2" }, - PrincipalTable = "Companies", - PrincipalSchema = "hr", - PrincipalColumns = new[] { "Id1", "Id2" }, - OnDelete = ReferentialAction.Cascade - }); - - [ConditionalFact] - public virtual void AddForeignKeyOperation_without_name() - => Generate( - new AddForeignKeyOperation - { - Table = "People", - Columns = new[] { "SpouseId" }, - PrincipalTable = "People", - PrincipalColumns = new[] { "Id" } - }); - - [ConditionalFact] - public virtual void AddForeignKeyOperation_without_principal_columns() - => Generate( - new AddForeignKeyOperation - { - Table = "People", - Columns = new[] { "SpouseId" }, - PrincipalTable = "People" - }); - - [ConditionalFact] - public virtual void AddPrimaryKeyOperation_with_name() - => Generate( - new AddPrimaryKeyOperation - { - Table = "People", - Schema = "dbo", - Name = "PK_People", - Columns = new[] { "Id1", "Id2" } - }); - - [ConditionalFact] - public virtual void AddPrimaryKeyOperation_without_name() - => Generate( - new AddPrimaryKeyOperation { Table = "People", Columns = new[] { "Id" } }); - - [ConditionalFact] - public virtual void AddUniqueConstraintOperation_with_name() - => Generate( - new AddUniqueConstraintOperation - { - Table = "People", - Schema = "dbo", - Name = "AK_People_DriverLicense", - Columns = new[] { "DriverLicense_State", "DriverLicense_Number" } - }); - - [ConditionalFact] - public virtual void AddUniqueConstraintOperation_without_name() - => Generate( - new AddUniqueConstraintOperation { Table = "People", Columns = new[] { "SSN" } }); - - [ConditionalFact] - public virtual void CreateCheckConstraintOperation_with_name() - => Generate( - new CreateCheckConstraintOperation - { - Table = "People", - Schema = "dbo", - Name = "CK_People_DriverLicense", - Sql = "DriverLicense_Number > 0" - }); - - [ConditionalFact] - public virtual void AlterColumnOperation() - => Generate( - new AlterColumnOperation - { - Table = "People", - Schema = "dbo", - Name = "LuckyNumber", - ClrType = typeof(int), - ColumnType = "int", - IsNullable = false, - DefaultValue = 7 - }); - - [ConditionalFact] - public virtual void AlterColumnOperation_without_column_type() - => Generate( - new AlterColumnOperation - { - Table = "People", - Name = "LuckyNumber", - ClrType = typeof(int) - }); - - [ConditionalFact] - public virtual void AlterSequenceOperation_with_minValue_and_maxValue() - => Generate( - new AlterSequenceOperation - { - Name = "EntityFrameworkHiLoSequence", - Schema = "dbo", - IncrementBy = 1, - MinValue = 2, - MaxValue = 816, - IsCyclic = true - }); - - [ConditionalFact] - public virtual void AlterSequenceOperation_without_minValue_and_maxValue() - => Generate( - new AlterSequenceOperation { Name = "EntityFrameworkHiLoSequence", IncrementBy = 1 }); - - [ConditionalFact] - public virtual void RenameTableOperation_legacy() - => Generate( - new RenameTableOperation - { - Name = "People", - Schema = "dbo", - NewName = "Person" - }); - - [ConditionalFact] - public virtual void RenameTableOperation() - => Generate( - modelBuilder => modelBuilder.HasAnnotation(CoreAnnotationNames.ProductVersion, "2.1.0"), - new RenameTableOperation - { - Name = "People", - Schema = "dbo", - NewName = "Person", - NewSchema = "dbo" - }); - - [ConditionalFact] - public virtual void CreateIndexOperation_unique() - => Generate( - new CreateIndexOperation - { - Name = "IX_People_Name", - Table = "People", - Schema = "dbo", - Columns = new[] { "FirstName", "LastName" }, - IsUnique = true - }); - - [ConditionalFact] - public virtual void CreateIndexOperation_nonunique() - => Generate( - new CreateIndexOperation - { - Name = "IX_People_Name", - Table = "People", - Columns = new[] { "Name" }, - IsUnique = false - }); - - [ConditionalFact] - public virtual void CreateIndexOperation_with_where_clauses() - => Generate( - new CreateIndexOperation - { - Name = "IX_People_Name", - Table = "People", - Columns = new[] { "Name" }, - IsUnique = false, - Filter = "[Id] > 2" - }); - - [ConditionalFact] - public virtual void CreateSequenceOperation_with_minValue_and_maxValue() - => Generate( - new CreateSequenceOperation - { - Name = "EntityFrameworkHiLoSequence", - Schema = "dbo", - StartValue = 3, - IncrementBy = 1, - MinValue = 2, - MaxValue = 816, - ClrType = typeof(long), - IsCyclic = true - }); - - [ConditionalFact] - public virtual void CreateSequenceOperation_with_minValue_and_maxValue_not_long() - => Generate( - new CreateSequenceOperation - { - Name = "EntityFrameworkHiLoSequence", - Schema = "dbo", - StartValue = 3, - IncrementBy = 1, - MinValue = 2, - MaxValue = 816, - ClrType = typeof(int), - IsCyclic = true - }); - - [ConditionalFact] - public virtual void CreateSequenceOperation_without_minValue_and_maxValue() - => Generate( - new CreateSequenceOperation - { - Name = "EntityFrameworkHiLoSequence", - ClrType = typeof(long), - StartValue = 3, - IncrementBy = 1 - }); - - [ConditionalFact] - public virtual void CreateTableOperation() - => Generate( - new CreateTableOperation - { - Name = "People", - Schema = "dbo", - Columns = - { - new AddColumnOperation - { - Name = "Id", - Table = "People", - ClrType = typeof(int), - IsNullable = false - }, - new AddColumnOperation - { - Name = "EmployerId", - Table = "People", - ClrType = typeof(int), - IsNullable = true, - Comment = "Employer ID comment" - }, - new AddColumnOperation - { - Name = "SSN", - Table = "People", - ClrType = typeof(string), - ColumnType = "char(11)", - IsNullable = true - } - }, - PrimaryKey = new AddPrimaryKeyOperation { Columns = new[] { "Id" } }, - UniqueConstraints = { new AddUniqueConstraintOperation { Columns = new[] { "SSN" } } }, - CheckConstraints = { new CreateCheckConstraintOperation { Sql = "SSN > 0" } }, - ForeignKeys = - { - new AddForeignKeyOperation - { - Columns = new[] { "EmployerId" }, - PrincipalTable = "Companies", - PrincipalColumns = new[] { "Id" } - } - }, - Comment = "Table comment" - }); - - [ConditionalFact] - public virtual void CreateTableOperation_no_key() - => Generate( - new CreateTableOperation - { - Name = "Anonymous", - Columns = - { - new AddColumnOperation - { - Name = "Value", - Table = "Anonymous", - ClrType = typeof(int), - IsNullable = false - } - } - }); - - [ConditionalFact] - public virtual void DropColumnOperation() - => Generate( - new DropColumnOperation - { - Table = "People", - Schema = "dbo", - Name = "LuckyNumber" - }); - - [ConditionalFact] - public virtual void DropForeignKeyOperation() - => Generate( - new DropForeignKeyOperation - { - Table = "People", - Schema = "dbo", - Name = "FK_People_Companies" - }); - - [ConditionalFact] - public virtual void DropIndexOperation() - => Generate( - new DropIndexOperation - { - Name = "IX_People_Name", - Table = "People", - Schema = "dbo" - }); - - [ConditionalFact] - public virtual void DropPrimaryKeyOperation() - => Generate( - new DropPrimaryKeyOperation - { - Table = "People", - Schema = "dbo", - Name = "PK_People" - }); - - [ConditionalFact] - public virtual void DropSequenceOperation() - => Generate( - new DropSequenceOperation { Name = "EntityFrameworkHiLoSequence", Schema = "dbo" }); - - [ConditionalFact] - public virtual void DropTableOperation() - => Generate( - new DropTableOperation { Name = "People", Schema = "dbo" }); - - [ConditionalFact] - public virtual void DropUniqueConstraintOperation() - => Generate( - new DropUniqueConstraintOperation - { - Table = "People", - Schema = "dbo", - Name = "AK_People_SSN" - }); - - [ConditionalFact] - public virtual void DropCheckConstraintOperation() - => Generate( - new DropCheckConstraintOperation - { - Table = "People", - Schema = "dbo", - Name = "CK_People_SSN" - }); - - [ConditionalFact] - public virtual void SqlOperation() - => Generate( - new SqlOperation { Sql = "-- I <3 DDL" }); - - [ConditionalFact] - public virtual void InsertDataOperation() - => Generate( - new InsertDataOperation - { - Table = "People", - Columns = new[] { "Id", "Full Name" }, - Values = new object[,] - { - { 0, null }, { 1, "Daenerys Targaryen" }, { 2, "John Snow" }, { 3, "Arya Stark" }, { 4, "Harry Strickland" } - } - }); - - [ConditionalFact] - public virtual void DeleteDataOperation_simple_key() - => Generate( - new DeleteDataOperation - { - Table = "People", - KeyColumns = new[] { "Id" }, - KeyValues = new object[,] { { 2 }, { 4 } } - }); - - [ConditionalFact] - public virtual void DeleteDataOperation_composite_key() - => Generate( - new DeleteDataOperation - { - Table = "People", - KeyColumns = new[] { "First Name", "Last Name" }, - KeyValues = new object[,] { { "Hodor", null }, { "Daenerys", "Targaryen" } } - }); - - [ConditionalFact] - public virtual void UpdateDataOperation_simple_key() - => Generate( - new UpdateDataOperation - { - Table = "People", - KeyColumns = new[] { "Id" }, - KeyValues = new object[,] { { 1 }, { 4 } }, - Columns = new[] { "Full Name" }, - Values = new object[,] { { "Daenerys Stormborn" }, { "Homeless Harry Strickland" } } - }); - - [ConditionalFact] - public virtual void UpdateDataOperation_composite_key() - => Generate( - new UpdateDataOperation - { - Table = "People", - KeyColumns = new[] { "Id", "Last Name" }, - KeyValues = new object[,] { { 0, null }, { 4, "Strickland" } }, - Columns = new[] { "First Name" }, - Values = new object[,] { { "Hodor" }, { "Harry" } } - }); - - [ConditionalFact] - public virtual void UpdateDataOperation_multiple_columns() - => Generate( - new UpdateDataOperation - { - Table = "People", - KeyColumns = new[] { "Id" }, - KeyValues = new object[,] { { 1 }, { 4 } }, - Columns = new[] { "First Name", "Nickname" }, - Values = new object[,] { { "Daenerys", "Dany" }, { "Harry", "Homeless" } } - }); - - protected TestHelpers TestHelpers { get; } - - protected MigrationSqlGeneratorTestBase(TestHelpers testHelpers) - { - TestHelpers = testHelpers; - } - - protected virtual void Generate(params MigrationOperation[] operation) - => Generate(_ => { }, operation); - - protected virtual void Generate(Action buildAction, params MigrationOperation[] operation) - { - var modelBuilder = TestHelpers.CreateConventionBuilder(); - modelBuilder.Model.RemoveAnnotation(CoreAnnotationNames.ProductVersion); - buildAction(modelBuilder); - - var batch = TestHelpers.CreateContextServices().GetRequiredService() - .Generate(operation, modelBuilder.Model); - - Sql = string.Join( - "GO" + EOL + EOL, - batch.Select(b => b.CommandText)); - } - - protected void AssertSql(string expected) - => Assert.Equal(expected, Sql, ignoreLineEndingDifferences: true); - } -} diff --git a/test/EFCore.Relational.Specification.Tests/MigrationsFixtureBase.cs b/test/EFCore.Relational.Specification.Tests/MigrationsFixtureBase.cs deleted file mode 100644 index 17fdb2ce673..00000000000 --- a/test/EFCore.Relational.Specification.Tests/MigrationsFixtureBase.cs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.TestUtilities; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.EntityFrameworkCore -{ - public abstract class MigrationsFixtureBase : SharedStoreFixtureBase - { - public static string ActiveProvider { get; set; } - public new RelationalTestStore TestStore => (RelationalTestStore)base.TestStore; - protected override string StoreName { get; } = "MigrationsTest"; - - public EmptyMigrationsContext CreateEmptyContext() - => new EmptyMigrationsContext( - TestStore.AddProviderOptions( - new DbContextOptionsBuilder()) - .UseInternalServiceProvider( - TestStoreFactory.AddProviderServices( - new ServiceCollection()) - .BuildServiceProvider()) - .Options); - - public new virtual MigrationsContext CreateContext() => base.CreateContext(); - - public class EmptyMigrationsContext : DbContext - { - public EmptyMigrationsContext(DbContextOptions options) - : base(options) - { - } - } - - public class MigrationsContext : PoolableDbContext - { - public MigrationsContext(DbContextOptions options) - : base(options) - { - } - - public DbSet Foos { get; set; } - } - - public class Foo - { - public int Id { get; set; } - } - - [DbContext(typeof(MigrationsContext))] - [Migration("00000000000001_Migration1")] - private class Migration1 : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - MigrationsFixtureBase.ActiveProvider = migrationBuilder.ActiveProvider; - - migrationBuilder - .CreateTable( - name: "Table1", - columns: x => new { Id = x.Column(), Foo = x.Column() }) - .PrimaryKey( - name: "PK_Table1", - columns: x => x.Id); - } - - protected override void Down(MigrationBuilder migrationBuilder) - => migrationBuilder.DropTable("Table1"); - } - - [DbContext(typeof(MigrationsContext))] - [Migration("00000000000002_Migration2")] - private class Migration2 : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - => migrationBuilder.RenameColumn( - name: "Foo", - table: "Table1", - newName: "Bar"); - - protected override void Down(MigrationBuilder migrationBuilder) - => migrationBuilder.RenameColumn( - name: "Bar", - table: "Table1", - newName: "Foo"); - } - - [DbContext(typeof(MigrationsContext))] - [Migration("00000000000003_Migration3")] - private class Migration3 : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - if (ActiveProvider == "Microsoft.EntityFrameworkCore.SqlServer") - { - migrationBuilder.Sql("CREATE DATABASE TransactionSuppressed;", suppressTransaction: true); - migrationBuilder.Sql("DROP DATABASE TransactionSuppressed;", suppressTransaction: true); - } - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - } - } - } -} diff --git a/test/EFCore.Relational.Specification.Tests/MigrationsInfrastructureTestBase.cs b/test/EFCore.Relational.Specification.Tests/MigrationsInfrastructureTestBase.cs new file mode 100644 index 00000000000..0db3e8a7452 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/MigrationsInfrastructureTestBase.cs @@ -0,0 +1,408 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +// ReSharper disable InconsistentNaming +namespace Microsoft.EntityFrameworkCore +{ + public abstract class MigrationsInfrastructureTestBase : IClassFixture + where TFixture : MigrationsFixtureBase, new() + { + protected TFixture Fixture { get; } + + protected MigrationsInfrastructureTestBase(TFixture fixture) + { + Fixture = fixture; + Fixture.TestStore.CloseConnection(); + } + + protected string Sql { get; private set; } + + protected string ActiveProvider { get; private set; } + + // Database deletion can happen as async file operation and SQLClient + // doesn't account for this, so give some time for it to happen on slow C.I. machines + protected virtual void GiveMeSomeTime(DbContext db) + { + var stillExists = true; + for (var i = 0; stillExists && i < 10; i++) + { + try + { + Thread.Sleep(500); + + stillExists = db.GetService().Exists(); + } + catch + { + } + } + } + + protected virtual async Task GiveMeSomeTimeAsync(DbContext db) + { + var stillExists = true; + for (var i = 0; stillExists && i < 10; i++) + { + try + { + await Task.Delay(500); + + stillExists = await db.GetService().ExistsAsync(); + } + catch + { + } + } + } + + [ConditionalFact] + public virtual void Can_apply_all_migrations() + { + using var db = Fixture.CreateContext(); + db.Database.EnsureDeleted(); + + GiveMeSomeTime(db); + + db.Database.Migrate(); + + var history = db.GetService(); + Assert.Collection( + history.GetAppliedMigrations(), + x => Assert.Equal("00000000000001_Migration1", x.MigrationId), + x => Assert.Equal("00000000000002_Migration2", x.MigrationId), + x => Assert.Equal("00000000000003_Migration3", x.MigrationId)); + } + + [ConditionalFact] + public virtual void Can_apply_one_migration() + { + using var db = Fixture.CreateContext(); + db.Database.EnsureDeleted(); + + GiveMeSomeTime(db); + + var migrator = db.GetService(); + migrator.Migrate("Migration1"); + + var history = db.GetService(); + Assert.Collection( + history.GetAppliedMigrations(), + x => Assert.Equal("00000000000001_Migration1", x.MigrationId)); + } + + [ConditionalFact] + public virtual void Can_revert_all_migrations() + { + using var db = Fixture.CreateContext(); + db.Database.EnsureDeleted(); + + GiveMeSomeTime(db); + + db.Database.Migrate(); + + var migrator = db.GetService(); + migrator.Migrate(Migration.InitialDatabase); + + var history = db.GetService(); + Assert.Empty(history.GetAppliedMigrations()); + } + + [ConditionalFact] + public virtual void Can_revert_one_migrations() + { + using var db = Fixture.CreateContext(); + db.Database.EnsureDeleted(); + + GiveMeSomeTime(db); + + db.Database.Migrate(); + + var migrator = db.GetService(); + migrator.Migrate("Migration1"); + + var history = db.GetService(); + Assert.Collection( + history.GetAppliedMigrations(), + x => Assert.Equal("00000000000001_Migration1", x.MigrationId)); + } + + [ConditionalFact] + public virtual async Task Can_apply_all_migrations_async() + { + using var db = Fixture.CreateContext(); + await db.Database.EnsureDeletedAsync(); + + await GiveMeSomeTimeAsync(db); + + await db.Database.MigrateAsync(); + + var history = db.GetService(); + Assert.Collection( + await history.GetAppliedMigrationsAsync(), + x => Assert.Equal("00000000000001_Migration1", x.MigrationId), + x => Assert.Equal("00000000000002_Migration2", x.MigrationId), + x => Assert.Equal("00000000000003_Migration3", x.MigrationId)); + } + + [ConditionalFact] + public virtual void Can_generate_no_migration_script() + { + using var db = Fixture.CreateEmptyContext(); + var migrator = db.GetService(); + + SetSql(migrator.GenerateScript()); + } + + [ConditionalFact] + public virtual void Can_generate_migration_from_initial_database_to_initial() + { + using var db = Fixture.CreateContext(); + var migrator = db.GetService(); + + SetSql(migrator.GenerateScript(fromMigration: Migration.InitialDatabase, toMigration: Migration.InitialDatabase)); + } + + [ConditionalFact] + public virtual void Can_generate_up_scripts() + { + using var db = Fixture.CreateContext(); + var migrator = db.GetService(); + + SetSql(migrator.GenerateScript()); + } + + [ConditionalFact] + public virtual void Can_generate_one_up_script() + { + using var db = Fixture.CreateContext(); + var migrator = db.GetService(); + + SetSql(migrator.GenerateScript(fromMigration: "00000000000001_Migration1", toMigration: "00000000000002_Migration2")); + } + + [ConditionalFact] + public virtual void Can_generate_up_script_using_names() + { + using var db = Fixture.CreateContext(); + var migrator = db.GetService(); + + SetSql(migrator.GenerateScript(fromMigration: "Migration1", toMigration: "Migration2")); + } + + [ConditionalFact] + public virtual void Can_generate_idempotent_up_scripts() + { + using var db = Fixture.CreateContext(); + var migrator = db.GetService(); + + SetSql(migrator.GenerateScript(idempotent: true)); + } + + [ConditionalFact] + public virtual void Can_generate_down_scripts() + { + using var db = Fixture.CreateContext(); + var migrator = db.GetService(); + + SetSql( + migrator.GenerateScript( + fromMigration: "Migration2", + toMigration: Migration.InitialDatabase)); + } + + [ConditionalFact] + public virtual void Can_generate_one_down_script() + { + using var db = Fixture.CreateContext(); + var migrator = db.GetService(); + + SetSql( + migrator.GenerateScript( + fromMigration: "00000000000002_Migration2", + toMigration: "00000000000001_Migration1")); + } + + [ConditionalFact] + public virtual void Can_generate_down_script_using_names() + { + using var db = Fixture.CreateContext(); + var migrator = db.GetService(); + + SetSql( + migrator.GenerateScript( + fromMigration: "Migration2", + toMigration: "Migration1")); + } + + [ConditionalFact] + public virtual void Can_generate_idempotent_down_scripts() + { + using var db = Fixture.CreateContext(); + var migrator = db.GetService(); + + SetSql( + migrator.GenerateScript( + fromMigration: "Migration2", + toMigration: Migration.InitialDatabase, + idempotent: true)); + } + + [ConditionalFact] + public virtual void Can_get_active_provider() + { + using var db = Fixture.CreateContext(); + var migrator = db.GetService(); + MigrationsFixtureBase.ActiveProvider = null; + + migrator.GenerateScript(toMigration: "Migration1"); + + ActiveProvider = MigrationsFixtureBase.ActiveProvider; + } + + [ConditionalFact] + public abstract void Can_diff_against_2_2_model(); + + [ConditionalFact] + public abstract void Can_diff_against_3_0_ASP_NET_Identity_model(); + + [ConditionalFact] + public abstract void Can_diff_against_2_2_ASP_NET_Identity_model(); + + [ConditionalFact] + public abstract void Can_diff_against_2_1_ASP_NET_Identity_model(); + + protected virtual void DiffSnapshot(ModelSnapshot snapshot, DbContext context) + { + var sourceModel = ((IMutableModel)snapshot.Model).FinalizeModel(); + var targetModel = context.Model; + + var typeMapper = context.GetService(); + + foreach (var property in sourceModel.GetEntityTypes().SelectMany(e => e.GetDeclaredProperties())) + { + Assert.NotNull(typeMapper.FindMapping(property)); + } + + foreach (var property in targetModel.GetEntityTypes().SelectMany(e => e.GetDeclaredProperties())) + { + Assert.NotNull(typeMapper.FindMapping(property)); + } + + var modelDiffer = context.GetService(); + var operations = modelDiffer.GetDifferences(sourceModel, targetModel); + + Assert.Equal(0, operations.Count); + } + + private void SetSql(string value) => Sql = value.Replace(ProductInfo.GetVersion(), "7.0.0-test"); + } + + public abstract class MigrationsFixtureBase : SharedStoreFixtureBase + { + public static string ActiveProvider { get; set; } + public new RelationalTestStore TestStore => (RelationalTestStore)base.TestStore; + protected override string StoreName { get; } = "MigrationsTest"; + + public EmptyMigrationsContext CreateEmptyContext() + => new EmptyMigrationsContext( + TestStore.AddProviderOptions( + new DbContextOptionsBuilder()) + .UseInternalServiceProvider( + TestStoreFactory.AddProviderServices( + new ServiceCollection()) + .BuildServiceProvider()) + .Options); + + public new virtual MigrationsContext CreateContext() => base.CreateContext(); + + public class EmptyMigrationsContext : DbContext + { + public EmptyMigrationsContext(DbContextOptions options) + : base(options) + { + } + } + + public class MigrationsContext : PoolableDbContext + { + public MigrationsContext(DbContextOptions options) + : base(options) + { + } + + public DbSet Foos { get; set; } + } + + public class Foo + { + public int Id { get; set; } + } + + [DbContext(typeof(MigrationsContext))] + [Migration("00000000000001_Migration1")] + private class Migration1 : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + MigrationsFixtureBase.ActiveProvider = migrationBuilder.ActiveProvider; + + migrationBuilder + .CreateTable( + name: "Table1", + columns: x => new { Id = x.Column(), Foo = x.Column() }) + .PrimaryKey( + name: "PK_Table1", + columns: x => x.Id); + } + + protected override void Down(MigrationBuilder migrationBuilder) + => migrationBuilder.DropTable("Table1"); + } + + [DbContext(typeof(MigrationsContext))] + [Migration("00000000000002_Migration2")] + private class Migration2 : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + => migrationBuilder.RenameColumn( + name: "Foo", + table: "Table1", + newName: "Bar"); + + protected override void Down(MigrationBuilder migrationBuilder) + => migrationBuilder.RenameColumn( + name: "Bar", + table: "Table1", + newName: "Foo"); + } + + [DbContext(typeof(MigrationsContext))] + [Migration("00000000000003_Migration3")] + private class Migration3 : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + if (ActiveProvider == "Microsoft.EntityFrameworkCore.SqlServer") + { + migrationBuilder.Sql("CREATE DATABASE TransactionSuppressed;", suppressTransaction: true); + migrationBuilder.Sql("DROP DATABASE TransactionSuppressed;", suppressTransaction: true); + } + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } + } +} diff --git a/test/EFCore.Relational.Specification.Tests/MigrationsTestBase.cs b/test/EFCore.Relational.Specification.Tests/MigrationsTestBase.cs index 34757cac189..4867c874e89 100644 --- a/test/EFCore.Relational.Specification.Tests/MigrationsTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/MigrationsTestBase.cs @@ -2,409 +2,1452 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Data.Common; +using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Conventions; +using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Scaffolding; +using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.DependencyInjection; using Xunit; -// ReSharper disable InconsistentNaming +#nullable enable + namespace Microsoft.EntityFrameworkCore { - public abstract class MigrationsTestBase : IClassFixture - where TFixture : MigrationsFixtureBase, new() + public class MigrationsTestBase : IClassFixture + where TFixture : MigrationsTestBase.MigrationsFixtureBase, new() { + private readonly ISqlGenerationHelper _sqlGenerationHelper; + private readonly IRelationalTypeMappingSource _typeMappingSource; + protected TFixture Fixture { get; } protected MigrationsTestBase(TFixture fixture) { Fixture = fixture; - Fixture.TestStore.CloseConnection(); + _sqlGenerationHelper = Fixture.ServiceProvider.GetService(); + _typeMappingSource = Fixture.ServiceProvider.GetService(); } - protected string Sql { get; private set; } - - protected string ActiveProvider { get; private set; } - - // Database deletion can happen as async file operation and SQLClient - // doesn't account for this, so give some time for it to happen on slow C.I. machines - protected virtual void GiveMeSomeTime(DbContext db) - { - var stillExists = true; - for (var i = 0; stillExists && i < 10; i++) - { - try + [ConditionalFact] + public virtual Task Create_table() + => Test( + builder => { }, + builder => builder.Entity( + "People", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.Property("Name"); + e.HasKey("Id"); + }), + model => { - Thread.Sleep(500); + var table = Assert.Single(model.Tables); + Assert.Collection( + table.Columns, + c => Assert.Equal("Id", c.Name), + c => Assert.Equal("Name", c.Name)); + Assert.Same( + table.Columns.Single(c => c.Name == "Id"), + Assert.Single(table.PrimaryKey!.Columns)); + }); - stillExists = db.GetService().Exists(); - } - catch + [ConditionalFact] + public virtual async Task Create_table_all_settings() + { + var intStoreType = TypeMappingSource.FindMapping(typeof(int)).StoreType; + var char11StoreType = TypeMappingSource.FindMapping(typeof(string), storeTypeName: null, size: 11).StoreType; + + await Test( + builder => builder.Entity( + "Employers", e => + { + e.Property("Id"); + e.HasKey("Id"); + }), + builder => { }, + builder => builder.Entity( + "People", e => + { + e.ToTable("People", "dbo2"); + + e.Property("CustomId"); + e.Property("EmployerId") + .HasComment("Employer ID comment"); + e.Property("SSN") + .HasColumnType(char11StoreType) + .IsRequired(false); + + e.HasKey("CustomId"); + e.HasAlternateKey("SSN"); + e.HasCheckConstraint("CK_SSN", $"{DelimitIdentifier("SSN")} > 0"); + e.HasOne("Employers").WithMany("People").HasForeignKey("EmployerId"); + + e.HasComment("Table comment"); + }), + model => { - } - } + var employersTable = Assert.Single(model.Tables, t => t.Name == "Employers"); + var peopleTable = Assert.Single(model.Tables, t => t.Name == "People"); + + Assert.Equal("People", peopleTable.Name); + Assert.Equal("dbo2", peopleTable.Schema); + + Assert.Collection( + peopleTable.Columns.OrderBy(c => c.Name), + c => + { + Assert.Equal("CustomId", c.Name); + Assert.False(c.IsNullable); + Assert.Equal(intStoreType, c.StoreType); + Assert.Null(c.Comment); + }, + c => + { + Assert.Equal("EmployerId", c.Name); + Assert.False(c.IsNullable); + Assert.Equal(intStoreType, c.StoreType); + Assert.Equal("Employer ID comment", c.Comment); + }, + c => + { + Assert.Equal("SSN", c.Name); + Assert.False(c.IsNullable); + Assert.Equal(char11StoreType, c.StoreType); + Assert.Null(c.Comment); + }); + + Assert.Same( + peopleTable.Columns.Single(c => c.Name == "CustomId"), + Assert.Single(peopleTable.PrimaryKey!.Columns)); + Assert.Same( + peopleTable.Columns.Single(c => c.Name == "SSN"), + Assert.Single(Assert.Single(peopleTable.UniqueConstraints).Columns)); + // TODO: Need to scaffold check constraints, https://github.com/aspnet/EntityFrameworkCore/issues/15408 + + var foreignKey = Assert.Single(peopleTable.ForeignKeys); + Assert.Same(peopleTable, foreignKey.Table); + Assert.Same(peopleTable.Columns.Single(c => c.Name == "EmployerId"), Assert.Single(foreignKey.Columns)); + Assert.Same(employersTable, foreignKey.PrincipalTable); + Assert.Same(employersTable.Columns.Single(), Assert.Single(foreignKey.PrincipalColumns)); + + Assert.Equal("Table comment", peopleTable.Comment); + }); } - protected virtual async Task GiveMeSomeTimeAsync(DbContext db) - { - var stillExists = true; - for (var i = 0; stillExists && i < 10; i++) - { - try + [ConditionalFact] + public virtual Task Create_table_no_key() + => Test( + builder => { }, + builder => builder.Entity("Anonymous").Property("SomeColumn"), + model => { - await Task.Delay(500); + var table = Assert.Single(model.Tables); + Assert.Null(table.PrimaryKey); + }); - stillExists = await db.GetService().ExistsAsync(); - } - catch + [ConditionalFact] + public virtual Task Create_table_with_comments() + => Test( + builder => { }, + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("Name").HasComment("Column comment"); + e.HasComment("Table comment"); + }), + model => { - } - } - } + var table = Assert.Single(model.Tables); + Assert.Equal("Table comment", table.Comment); + var column = Assert.Single(table.Columns, c => c.Name == "Name"); + Assert.Equal("Column comment", column.Comment); + }); [ConditionalFact] - public virtual void Can_apply_all_migrations() + public virtual Task Create_table_with_multiline_comments() { - using var db = Fixture.CreateContext(); - db.Database.EnsureDeleted(); + var tableComment = @"This is a multi-line +table comment. +More information can +be found in the docs."; + var columnComment = @"This is a multi-line +column comment. +More information can +be found in the docs."; + + return Test( + builder => { }, + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("Name").HasComment(columnComment); + e.HasComment(tableComment); + }), + model => + { + var table = Assert.Single(model.Tables); + Assert.Equal(tableComment, table.Comment); + var column = Assert.Single(table.Columns, c => c.Name == "Name"); + Assert.Equal(columnComment, column.Comment); + }); + } - GiveMeSomeTime(db); + [ConditionalFact] + public virtual Task Alter_table_add_comment() + => Test( + builder => builder.Entity("People").Property("Id"), + builder => { }, + builder => builder.Entity("People").HasComment("Table comment"), + model => Assert.Equal("Table comment", Assert.Single(model.Tables).Comment)); - db.Database.Migrate(); + [ConditionalFact] + public virtual Task Alter_table_add_comment_non_default_schema() + => Test( + builder => builder.Entity("People") + .ToTable("People", "SomeOtherSchema") + .Property("Id"), + builder => { }, + builder => builder.Entity("People") + .ToTable("People", "SomeOtherSchema") + .HasComment("Table comment"), + model => Assert.Equal("Table comment", Assert.Single(model.Tables).Comment)); - var history = db.GetService(); - Assert.Collection( - history.GetAppliedMigrations(), - x => Assert.Equal("00000000000001_Migration1", x.MigrationId), - x => Assert.Equal("00000000000002_Migration2", x.MigrationId), - x => Assert.Equal("00000000000003_Migration3", x.MigrationId)); - } + [ConditionalFact] + public virtual Task Alter_table_change_comment() + => Test( + builder => builder.Entity("People").Property("Id"), + builder => builder.Entity("People").HasComment("Table comment1"), + builder => builder.Entity("People").HasComment("Table comment2"), + model => Assert.Equal("Table comment2", Assert.Single(model.Tables).Comment)); [ConditionalFact] - public virtual void Can_apply_one_migration() - { - using var db = Fixture.CreateContext(); - db.Database.EnsureDeleted(); + public virtual Task Alter_table_remove_comment() + => Test( + builder => builder.Entity("People").Property("Id"), + builder => builder.Entity("People").HasComment("Table comment1"), + builder => { }, + model => Assert.Null(Assert.Single(model.Tables).Comment)); - GiveMeSomeTime(db); + [ConditionalFact] + public virtual Task Drop_table() + => Test( + builder => builder.Entity("People", e => e.Property("Id")), + builder => { }, + model => Assert.Empty(model.Tables)); - var migrator = db.GetService(); - migrator.Migrate("Migration1"); + [ConditionalFact] + public virtual Task Rename_table() + => Test( + builder => builder.Entity("People").Property("Id"), + builder => builder.Entity("people").Property("Id"), + model => + { + var table = Assert.Single(model.Tables); + Assert.Equal("people", table.Name); + }); - var history = db.GetService(); - Assert.Collection( - history.GetAppliedMigrations(), - x => Assert.Equal("00000000000001_Migration1", x.MigrationId)); - } + [ConditionalFact] + public virtual Task Rename_table_with_primary_key() + => Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.HasKey("Id"); + }), + builder => builder.Entity( + "people", e => + { + e.Property("Id"); + e.HasKey("Id"); + }), + model => + { + var table = Assert.Single(model.Tables); + Assert.Equal("people", table.Name); + }); [ConditionalFact] - public virtual void Can_revert_all_migrations() - { - using var db = Fixture.CreateContext(); - db.Database.EnsureDeleted(); + public virtual Task Move_table() + => Test( + builder => builder.Entity("TestTable").Property("Id"), + builder => { }, + builder => builder.Entity("TestTable").ToTable("TestTable", "TestTableSchema"), + model => + { + var table = Assert.Single(model.Tables); + Assert.Equal("TestTableSchema", table.Schema); + Assert.Equal("TestTable", table.Name); + }); - GiveMeSomeTime(db); + [ConditionalFact] + public virtual Task Create_schema() + => Test( + builder => { }, + builder => builder.Entity("People") + .ToTable("People", "SomeOtherSchema") + .Property("Id"), + model => Assert.Equal("SomeOtherSchema", Assert.Single(model.Tables).Schema)); - db.Database.Migrate(); + [ConditionalFact] + public virtual Task Add_column_with_defaultValue_string() + => Test( + builder => builder.Entity("People").Property("Id"), + builder => { }, + builder => builder.Entity("People").Property("Name") + .IsRequired() + .HasDefaultValue("John Doe"), + model => + { + var table = Assert.Single(model.Tables); + Assert.Equal(2, table.Columns.Count); + var nameColumn = Assert.Single(table.Columns, c => c.Name == "Name"); + Assert.False(nameColumn.IsNullable); + Assert.Contains("John Doe", nameColumn.DefaultValueSql); + }); - var migrator = db.GetService(); - migrator.Migrate(Migration.InitialDatabase); + [ConditionalFact] + public virtual Task Add_column_with_defaultValue_datetime() + => Test( + builder => builder.Entity("People").Property("Id"), + builder => { }, + builder => builder.Entity("People").Property("Birthday") + .HasDefaultValue(new DateTime(2015, 4, 12, 17, 5, 0)), + model => + { + var table = Assert.Single(model.Tables); + Assert.Equal(2, table.Columns.Count); + var birthdayColumn = Assert.Single(table.Columns, c => c.Name == "Birthday"); + Assert.False(birthdayColumn.IsNullable); + }); - var history = db.GetService(); - Assert.Empty(history.GetAppliedMigrations()); - } + [ConditionalFact] + public virtual Task Add_column_with_defaultValueSql() + => Test( + builder => builder.Entity("People").Property("Id"), + builder => { }, + builder => builder.Entity("People").Property("Birthday") + .HasColumnType("date") + .HasDefaultValueSql("CURRENT_TIMESTAMP"), + model => + { + var table = Assert.Single(model.Tables); + Assert.Equal(2, table.Columns.Count); + var nameColumn = Assert.Single(table.Columns, c => c.Name == "Birthday"); + Assert.Equal("date", nameColumn.StoreType); + Assert.True(nameColumn.IsNullable); + Assert.Equal("(getdate())", nameColumn.DefaultValueSql); + }); [ConditionalFact] - public virtual void Can_revert_one_migrations() - { - using var db = Fixture.CreateContext(); - db.Database.EnsureDeleted(); + public virtual Task Add_column_with_computedSql() + => Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("FirstName"); + e.Property("LastName"); + }), + builder => { }, + builder => builder.Entity("People").Property("FullName").HasComputedColumnSql("FirstName + ' ' + LastName"), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns, c => c.Name == "FullName"); + Assert.Contains("FirstName", column.ComputedColumnSql); + Assert.Contains("LastName", column.ComputedColumnSql); + }); - GiveMeSomeTime(db); + // TODO: Check this out + [ConditionalFact] + public virtual Task Add_column_with_required() + => Test( + builder => builder.Entity("People").Property("Id"), + builder => { }, + builder => builder.Entity("People").Property("Name").IsRequired(), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns, c => c.Name == "Name"); + Assert.Equal(TypeMappingSource.FindMapping(typeof(string)).StoreType, column.StoreType); + Assert.False(column.IsNullable); + }); - db.Database.Migrate(); + [ConditionalFact] + public virtual Task Add_column_with_ansi() + => Test( + builder => builder.Entity("People").Property("Id"), + builder => { }, + builder => builder.Entity("People").Property("Name").IsUnicode(false), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns, c => c.Name == "Name"); + Assert.Equal( + TypeMappingSource + .FindMapping(typeof(string), storeTypeName: null, unicode: false) + .StoreType, column.StoreType); + Assert.True(column.IsNullable); + }); - var migrator = db.GetService(); - migrator.Migrate("Migration1"); + [ConditionalFact] + public virtual Task Add_column_with_max_length() + => Test( + builder => builder.Entity("People").Property("Id"), + builder => { }, + builder => builder.Entity("People").Property("Name").HasMaxLength(30), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns, c => c.Name == "Name"); + Assert.Equal( + TypeMappingSource + .FindMapping(typeof(string), storeTypeName: null, size: 30) + .StoreType, + column.StoreType); + }); - var history = db.GetService(); - Assert.Collection( - history.GetAppliedMigrations(), - x => Assert.Equal("00000000000001_Migration1", x.MigrationId)); - } + [ConditionalFact] + public virtual Task Add_column_with_max_length_on_derived() + => Test( + builder => + { + builder.Entity("Person"); + builder.Entity( + "SpecialPerson", e => + { + e.HasBaseType("Person"); + e.Property("Name").HasMaxLength(30); + }); + + builder.Entity("MoreSpecialPerson").HasBaseType("SpecialPerson"); + }, + builder => { }, + builder => builder.Entity("Person").Property("Name").HasMaxLength(30), + model => + { + var table = Assert.Single(model.Tables, t => t.Name == "Person"); + var column = Assert.Single(table.Columns, c => c.Name == "Name"); + Assert.Equal( + TypeMappingSource + .FindMapping(typeof(string), storeTypeName: null, size: 30) + .StoreType, + column.StoreType); + }); [ConditionalFact] - public virtual async Task Can_apply_all_migrations_async() - { - using var db = Fixture.CreateContext(); - await db.Database.EnsureDeletedAsync(); + public virtual Task Add_column_with_fixed_length() + => Test( + builder => builder.Entity("People").Property("Id"), + builder => { }, + builder => builder.Entity("People").Property("Name") + .IsFixedLength() + .HasMaxLength(100), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns, c => c.Name == "Name"); + Assert.Equal( + TypeMappingSource + .FindMapping(typeof(string), storeTypeName: null, fixedLength: true, size: 100) + .StoreType, + column.StoreType); + }); - await GiveMeSomeTimeAsync(db); + [ConditionalFact] + public virtual Task Add_column_with_comment() + => Test( + builder => builder.Entity("People").Property("Id"), + builder => { }, + builder => builder.Entity("People").Property("FullName").HasComment("My comment"), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns, c => c.Name == "FullName"); + Assert.Equal("My comment", column.Comment); + }); - await db.Database.MigrateAsync(); + [ConditionalFact] + public virtual Task Add_column_shared() + => Test( + builder => + { + builder.Entity("Base").Property("Id"); + builder.Entity("Derived1").Property("Foo"); + builder.Entity("Derived2").Property("Foo"); + }, + builder => { }, + builder => builder.Entity("Base").Property("Foo"), + model => + { + // var table = Assert.Single(model.Tables); + // var column = Assert.Single(table.Columns, c => c.Name == "Name"); + // Assert.Equal("nvarchar(30)", column.StoreType); + }); - var history = db.GetService(); - Assert.Collection( - await history.GetAppliedMigrationsAsync(), - x => Assert.Equal("00000000000001_Migration1", x.MigrationId), - x => Assert.Equal("00000000000002_Migration2", x.MigrationId), - x => Assert.Equal("00000000000003_Migration3", x.MigrationId)); - } + [ConditionalFact] + public virtual Task Alter_column_change_type() + => Test( + builder => builder.Entity("People").Property("Id"), + builder => builder.Entity("People").Property("SomeColumn"), + builder => builder.Entity("People").Property("SomeColumn"), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns, c => c.Name == "SomeColumn"); + Assert.Equal(_typeMappingSource.FindMapping(typeof(long)).StoreType, column.StoreType); + }); [ConditionalFact] - public virtual void Can_generate_no_migration_script() - { - using var db = Fixture.CreateEmptyContext(); - var migrator = db.GetService(); + public virtual Task Alter_column_make_required() + => Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("SomeColumn"); + }), + builder => { }, + builder => builder.Entity("People").Property("SomeColumn").IsRequired(), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns, c => c.Name != "Id"); + Assert.False(column.IsNullable); + }); - SetSql(migrator.GenerateScript()); - } + [ConditionalFact] + public virtual Task Alter_column_make_required_with_index() + => Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("SomeColumn"); + e.HasIndex("SomeColumn"); + }), + builder => { }, + builder => builder.Entity("People").Property("SomeColumn").IsRequired(), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns, c => c.Name != "Id"); + Assert.False(column.IsNullable); + var index = Assert.Single(table.Indexes); + Assert.Same(column, Assert.Single(index.Columns)); + }); [ConditionalFact] - public virtual void Can_generate_migration_from_initial_database_to_initial() - { - using var db = Fixture.CreateContext(); - var migrator = db.GetService(); + public virtual Task Alter_column_make_required_with_composite_index() + => Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("FirstName"); + e.Property("LastName"); + e.HasIndex("FirstName", "LastName"); + }), + builder => { }, + builder => builder.Entity("People").Property("FirstName").IsRequired(), + model => + { + var table = Assert.Single(model.Tables); + var firstNameColumn = Assert.Single(table.Columns, c => c.Name == "FirstName"); + Assert.False(firstNameColumn.IsNullable); + var index = Assert.Single(table.Indexes); + Assert.Equal(2, index.Columns.Count); + Assert.Contains(table.Columns.Single(c => c.Name == "FirstName"), index.Columns); + Assert.Contains(table.Columns.Single(c => c.Name == "LastName"), index.Columns); + }); - SetSql(migrator.GenerateScript(fromMigration: Migration.InitialDatabase, toMigration: Migration.InitialDatabase)); - } + [ConditionalFact] + public virtual Task Alter_column_make_computed() + => Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("FirstName"); + e.Property("LastName"); + }), + builder => builder.Entity("People").Property("FullName"), + builder => builder.Entity("People").Property("FullName").HasComputedColumnSql("FirstName + ' ' + LastName"), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns, c => c.Name == "FullName"); + Assert.Contains("FirstName", column.ComputedColumnSql); + Assert.Contains("LastName", column.ComputedColumnSql); + }); [ConditionalFact] - public virtual void Can_generate_up_scripts() - { - using var db = Fixture.CreateContext(); - var migrator = db.GetService(); + public virtual Task Alter_column_change_computed() + => Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("FirstName"); + e.Property("LastName"); + e.Property("FullName"); + }), + builder => builder.Entity("People").Property("FullName").HasComputedColumnSql("FirstName + ' ' + LastName"), + builder => builder.Entity("People").Property("FullName").HasComputedColumnSql("FirstName + ', ' + LastName"), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns, c => c.Name == "FullName"); + Assert.Contains("FirstName", column.ComputedColumnSql); + Assert.Contains("LastName", column.ComputedColumnSql); + }); - SetSql(migrator.GenerateScript()); - } + [ConditionalFact] + public virtual Task Alter_column_add_comment() + => Test( + builder => builder.Entity("People").Property("Id"), + builder => builder.Entity("People").Property("Id").HasComment("Some comment"), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns); + Assert.Equal("Some comment", column.Comment); + }); [ConditionalFact] - public virtual void Can_generate_one_up_script() - { - using var db = Fixture.CreateContext(); - var migrator = db.GetService(); + public virtual Task Alter_column_change_comment() + => Test( + builder => builder.Entity("People").Property("Id").HasComment("Some comment1"), + builder => builder.Entity("People").Property("Id").HasComment("Some comment2"), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns); + Assert.Equal("Some comment2", column.Comment); + }); - SetSql(migrator.GenerateScript(fromMigration: "00000000000001_Migration1", toMigration: "00000000000002_Migration2")); - } + [ConditionalFact] + public virtual Task Alter_column_remove_comment() + => Test( + builder => builder.Entity("People").Property("Id").HasComment("Some comment"), + builder => builder.Entity("People").Property("Id"), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns); + Assert.Null(column.Comment); + }); [ConditionalFact] - public virtual void Can_generate_up_script_using_names() - { - using var db = Fixture.CreateContext(); - var migrator = db.GetService(); + public virtual Task Drop_column() + => Test( + builder => builder.Entity("People").Property("Id"), + builder => builder.Entity("People").Property("SomeColumn"), + builder => { }, + model => + { + var table = Assert.Single(model.Tables); + Assert.Equal("Id", Assert.Single(table.Columns).Name); + }); - SetSql(migrator.GenerateScript(fromMigration: "Migration1", toMigration: "Migration2")); - } + [ConditionalFact] + public virtual Task Drop_column_primary_key() + => Test( + builder => builder.Entity("People").Property("SomeColumn"), + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.HasKey("Id"); + }), + builder => { }, + model => + { + var table = Assert.Single(model.Tables); + Assert.Equal("SomeColumn", Assert.Single(table.Columns).Name); + }); [ConditionalFact] - public virtual void Can_generate_idempotent_up_scripts() - { - using var db = Fixture.CreateContext(); - var migrator = db.GetService(); + public virtual Task Rename_column() + => Test( + builder => builder.Entity("People").Property("Id"), + builder => builder.Entity("People").Property("SomeColumn"), + builder => builder.Entity("People").Property("somecolumn"), + model => + { + var table = Assert.Single(model.Tables); + Assert.Equal(2, table.Columns.Count); + Assert.Single(table.Columns, c => c.Name == "somecolumn"); + }); - SetSql(migrator.GenerateScript(idempotent: true)); - } + [ConditionalFact] + public virtual Task Create_index() + => Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("FirstName"); + }), + builder => { }, + builder => builder.Entity("People").HasIndex("FirstName"), + model => + { + var table = Assert.Single(model.Tables); + var index = Assert.Single(table.Indexes); + Assert.Same(table, index.Table); + Assert.Same(table.Columns.Single(c => c.Name == "FirstName"), Assert.Single(index.Columns)); + Assert.Equal("IX_People_FirstName", index.Name); + Assert.False(index.IsUnique); + Assert.Null(index.Filter); + }); [ConditionalFact] - public virtual void Can_generate_down_scripts() - { - using var db = Fixture.CreateContext(); - var migrator = db.GetService(); + public virtual Task Create_index_unique() + => Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("FirstName"); + e.Property("LastName"); + }), + builder => { }, + builder => builder.Entity("People").HasIndex("FirstName", "LastName").IsUnique(), + model => + { + var table = Assert.Single(model.Tables); + var index = Assert.Single(table.Indexes); + Assert.True(index.IsUnique); + }); - SetSql( - migrator.GenerateScript( - fromMigration: "Migration2", - toMigration: Migration.InitialDatabase)); - } + [ConditionalFact] + public virtual Task Create_index_with_filter() + => Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("Name"); + }), + builder => { }, + builder => builder.Entity("People").HasIndex("Name") + .HasFilter($"{DelimitIdentifier("Name")} IS NOT NULL"), + model => + { + var table = Assert.Single(model.Tables); + var index = Assert.Single(table.Indexes); + Assert.Same(table.Columns.Single(c => c.Name == "Name"), Assert.Single(index.Columns)); + Assert.Contains("Name", index.Filter); + }); [ConditionalFact] - public virtual void Can_generate_one_down_script() - { - using var db = Fixture.CreateContext(); - var migrator = db.GetService(); + public virtual Task Create_unique_index_with_filter() + => Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("Name"); + }), + builder => { }, + builder => builder.Entity("People").HasIndex("Name").IsUnique() + .HasFilter($"{DelimitIdentifier("Name")} IS NOT NULL AND {DelimitIdentifier("Name")} <> ''"), + model => Assert.Contains("Name", model.Tables.Single().Indexes.Single().Filter)); - SetSql( - migrator.GenerateScript( - fromMigration: "00000000000002_Migration2", - toMigration: "00000000000001_Migration1")); - } + [ConditionalFact] + public virtual Task Drop_index() + => Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("SomeField"); + }), + builder => builder.Entity("People").HasIndex("SomeField"), + builder => { }, + model => Assert.Empty(Assert.Single(model.Tables).Indexes)); [ConditionalFact] - public virtual void Can_generate_down_script_using_names() - { - using var db = Fixture.CreateContext(); - var migrator = db.GetService(); + public virtual Task Rename_index() + => Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("FirstName"); + }), + builder => builder.Entity("People").HasIndex("FirstName").HasName("Foo"), + builder => builder.Entity("People").HasIndex("FirstName").HasName("foo"), + model => + { + var table = Assert.Single(model.Tables); + var index = Assert.Single(table.Indexes); + Assert.Equal("foo", index.Name); + }); - SetSql( - migrator.GenerateScript( - fromMigration: "Migration2", - toMigration: "Migration1")); - } + [ConditionalFact] + public virtual Task Add_primary_key() + => Test( + builder => builder.Entity("People").Property("SomeField"), + builder => { }, + builder => builder.Entity("People").HasKey("SomeField"), + model => + { + var table = Assert.Single(model.Tables); + var primaryKey = table.PrimaryKey; + Assert.NotNull(primaryKey); + Assert.Same(table, primaryKey!.Table); + Assert.Same(table.Columns.Single(), Assert.Single(primaryKey.Columns)); + Assert.Equal("PK_People", primaryKey.Name); + }); [ConditionalFact] - public virtual void Can_generate_idempotent_down_scripts() - { - using var db = Fixture.CreateContext(); - var migrator = db.GetService(); - - SetSql( - migrator.GenerateScript( - fromMigration: "Migration2", - toMigration: Migration.InitialDatabase, - idempotent: true)); - } + public virtual Task Add_primary_key_with_name() + => Test( + builder => builder.Entity("People").Property("SomeField"), + builder => { }, + builder => builder.Entity("People").HasKey("SomeField").HasName("PK_Foo"), + model => + { + var table = Assert.Single(model.Tables); + var primaryKey = table.PrimaryKey; + Assert.NotNull(primaryKey); + Assert.Same(table, primaryKey!.Table); + Assert.Same(table.Columns.Single(), Assert.Single(primaryKey.Columns)); + Assert.Equal("PK_Foo", primaryKey.Name); + }); [ConditionalFact] - public virtual void Can_get_active_provider() - { - using var db = Fixture.CreateContext(); - var migrator = db.GetService(); - MigrationsFixtureBase.ActiveProvider = null; + public virtual Task Add_primary_key_composite_with_name() + => Test( + builder => builder.Entity( + "People", e => + { + e.Property("SomeField1"); + e.Property("SomeField2"); + }), + builder => { }, + builder => builder.Entity("People").HasKey("SomeField1", "SomeField2").HasName("PK_Foo"), + model => + { + var table = Assert.Single(model.Tables); + var primaryKey = table.PrimaryKey; + Assert.NotNull(primaryKey); + Assert.Same(table, primaryKey!.Table!); + Assert.Collection( + primaryKey.Columns, + c => Assert.Same(table.Columns[0], c), + c => Assert.Same(table.Columns[1], c)); + Assert.Equal("PK_Foo", primaryKey.Name); + }); - migrator.GenerateScript(toMigration: "Migration1"); + [ConditionalFact] + public virtual Task Drop_primary_key() + => Test( + builder => builder.Entity("People").Property("SomeField"), + builder => builder.Entity("People").HasKey("SomeField"), + builder => { }, + model => Assert.Null(Assert.Single(model.Tables).PrimaryKey)); - ActiveProvider = MigrationsFixtureBase.ActiveProvider; - } + [ConditionalFact] + public virtual Task Add_foreign_key() + => Test( + builder => + { + builder.Entity( + "Customers", e => + { + e.Property("Id"); + e.HasKey("Id"); + }); + builder.Entity( + "Orders", e => + { + e.Property("Id"); + e.Property("CustomerId"); + }); + }, + builder => { }, + builder => builder.Entity("Orders").HasOne("Customers").WithMany() + .HasForeignKey("CustomerId"), + model => + { + var customersTable = Assert.Single(model.Tables, t => t.Name == "Customers"); + var ordersTable = Assert.Single(model.Tables, t => t.Name == "Orders"); + var foreignKey = ordersTable.ForeignKeys.Single(); + Assert.Equal("FK_Orders_Customers_CustomerId", foreignKey.Name); + Assert.Equal(ReferentialAction.NoAction, foreignKey.OnDelete); + Assert.Same(customersTable, foreignKey.PrincipalTable); + Assert.Same(customersTable.Columns.Single(), Assert.Single(foreignKey.PrincipalColumns)); + Assert.Equal("CustomerId", Assert.Single(foreignKey.Columns).Name); + }); - /// - /// Creating databases and executing DDL is slow. This oddly-structured test allows us to get the most amount of - /// coverage using the least amount of database operations. - /// [ConditionalFact] - public virtual async Task Can_execute_operations() - { - using var db = Fixture.CreateContext(); - await db.Database.EnsureDeletedAsync(); + public virtual Task Add_foreign_key_with_name() + => Test( + builder => + { + builder.Entity( + "Customers", e => + { + e.Property("Id"); + e.HasKey("Id"); + }); + builder.Entity( + "Orders", e => + { + e.Property("Id"); + e.Property("CustomerId"); + }); + }, + builder => { }, + builder => builder.Entity("Orders").HasOne("Customers").WithMany() + .HasForeignKey("CustomerId").HasConstraintName("FK_Foo"), + model => + { + var table = Assert.Single(model.Tables, t => t.Name == "Orders"); + var foreignKey = table.ForeignKeys.Single(); + Assert.Equal("FK_Foo", foreignKey.Name); + }); - await GiveMeSomeTimeAsync(db); + [ConditionalFact] + public virtual Task Drop_foreign_key() + => Test( + builder => + { + builder.Entity( + "Customers", e => + { + e.Property("Id"); + e.HasKey("Id"); + }); + builder.Entity( + "Orders", e => + { + e.Property("Id"); + e.Property("CustomerId"); + }); + }, + builder => builder.Entity("Orders").HasOne("Customers").WithMany().HasForeignKey("CustomerId"), + builder => { }, + model => + { + var customersTable = Assert.Single(model.Tables, t => t.Name == "Customers"); + Assert.Empty(customersTable.ForeignKeys); + }); - await db.Database.EnsureCreatedAsync(); + [ConditionalFact] + public virtual Task Add_unique_constraint() + => Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("AlternateKeyColumn"); + }), + builder => { }, + builder => builder.Entity("People").HasAlternateKey("AlternateKeyColumn"), + model => + { + var table = Assert.Single(model.Tables); + var uniqueConstraint = table.UniqueConstraints.Single(); + Assert.Same(table, uniqueConstraint.Table); + Assert.Same(table.Columns.Single(c => c.Name == "AlternateKeyColumn"), Assert.Single(uniqueConstraint.Columns)); + Assert.Equal("AK_People_AlternateKeyColumn", uniqueConstraint.Name); + }); - var services = db.GetInfrastructure(); - var connection = db.Database.GetDbConnection(); + [ConditionalFact] + public virtual Task Add_unique_constraint_composite_with_name() + => Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("AlternateKeyColumn1"); + e.Property("AlternateKeyColumn2"); + }), + builder => { }, + builder => builder.Entity("People").HasAlternateKey("AlternateKeyColumn1", "AlternateKeyColumn2").HasName("AK_Foo"), + model => + { + var table = Assert.Single(model.Tables); + var uniqueConstraint = table.UniqueConstraints.Single(); + Assert.Same(table, uniqueConstraint.Table); + Assert.Collection( + uniqueConstraint.Columns, + c => Assert.Same(table.Columns.Single(c => c.Name == "AlternateKeyColumn1"), c), + c => Assert.Same(table.Columns.Single(c => c.Name == "AlternateKeyColumn2"), c)); + Assert.Equal("AK_Foo", uniqueConstraint.Name); + }); - await db.Database.OpenConnectionAsync(); + [ConditionalFact] + public virtual Task Drop_unique_constraint() + => Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("AlternateKeyColumn"); + }), + builder => builder.Entity("People").HasAlternateKey("AlternateKeyColumn"), + builder => { }, + model => + { + Assert.Empty(Assert.Single(model.Tables).UniqueConstraints); + }); - try - { - await ExecuteAsync(services, BuildFirstMigration); - await AssertFirstMigrationAsync(connection); - await ExecuteAsync(services, BuildSecondMigration); - await AssertSecondMigrationAsync(connection); - } - finally - { - await db.Database.CloseConnectionAsync(); - } - } + [ConditionalFact] + public virtual Task Add_check_constraint_with_name() + => Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("DriverLicense"); + }), + builder => { }, + builder => builder.Entity("People").HasCheckConstraint("CK_Foo", $"{DelimitIdentifier("DriverLicense")} > 0"), + model => + { + // TODO: no scaffolding support for check constraints, https://github.com/aspnet/EntityFrameworkCore/issues/15408 + }); - protected virtual Task ExecuteAsync(IServiceProvider services, Action buildMigration) - { - var generator = services.GetRequiredService(); - var executor = services.GetRequiredService(); - var connection = services.GetRequiredService(); - var databaseProvider = services.GetRequiredService(); + [ConditionalFact] + public virtual Task Drop_check_constraint() + => Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("DriverLicense"); + }), + builder => builder.Entity("People").HasCheckConstraint("CK_Foo", $"{DelimitIdentifier("DriverLicense")} > 0"), + builder => { }, + model => + { + // TODO: no scaffolding support for check constraints, https://github.com/aspnet/EntityFrameworkCore/issues/15408 + }); - var migrationBuilder = new MigrationBuilder(databaseProvider.Name); - buildMigration(migrationBuilder); - var operations = migrationBuilder.Operations.ToList(); + [ConditionalFact] + public virtual Task Create_sequence() + => Test( + builder => { }, + builder => builder.HasSequence("TestSequence"), + model => + { + var sequence = Assert.Single(model.Sequences); + Assert.Equal("TestSequence", sequence.Name); + }); - var commandList = generator.Generate(operations); + [ConditionalFact] + public virtual Task Create_sequence_all_settings() + => Test( + builder => { }, + builder => builder.HasSequence("TestSequence", "dbo2") + .StartsAt(3) + .IncrementsBy(2) + .HasMin(2) + .HasMax(916) + .IsCyclic(), + model => + { + var sequence = Assert.Single(model.Sequences); + Assert.Equal("TestSequence", sequence.Name); + Assert.Equal("dbo2", sequence.Schema); + Assert.Equal(3, sequence.StartValue); + Assert.Equal(2, sequence.IncrementBy); + Assert.Equal(2, sequence.MinValue); + Assert.Equal(916, sequence.MaxValue); + Assert.True(sequence.IsCyclic); + }); - return executor.ExecuteNonQueryAsync(commandList, connection); - } + [ConditionalFact] + public virtual Task Alter_sequence_all_settings() + => Test( + builder => builder.HasSequence("foo"), + builder => { }, + builder => builder.HasSequence("foo") + .StartsAt(-3) + .IncrementsBy(2) + .HasMin(-5) + .HasMax(10) + .IsCyclic(), + model => + { + var sequence = Assert.Single(model.Sequences); + Assert.Equal(-3, sequence.StartValue); + Assert.Equal(2, sequence.IncrementBy); + Assert.Equal(-5, sequence.MinValue); + Assert.Equal(10, sequence.MaxValue); + Assert.True(sequence.IsCyclic); + }); [ConditionalFact] - public abstract void Can_diff_against_2_2_model(); + public virtual Task Alter_sequence_increment_by() + => Test( + builder => builder.HasSequence("foo"), + builder => { }, + builder => builder.HasSequence("foo").IncrementsBy(2), + model => + { + var sequence = Assert.Single(model.Sequences); + Assert.Equal(2, sequence.IncrementBy); + }); [ConditionalFact] - public abstract void Can_diff_against_3_0_ASP_NET_Identity_model(); + public virtual Task Drop_sequence() + => Test( + builder => builder.HasSequence("TestSequence"), + builder => { }, + model => Assert.Empty(model.Sequences)); [ConditionalFact] - public abstract void Can_diff_against_2_2_ASP_NET_Identity_model(); + public virtual Task Rename_sequence() + => Test( + builder => builder.HasSequence("TestSequence"), + builder => builder.HasSequence("testsequence"), + model => + { + var sequence = Assert.Single(model.Sequences); + Assert.Equal("testsequence", sequence.Name); + }); [ConditionalFact] - public abstract void Can_diff_against_2_1_ASP_NET_Identity_model(); + public virtual Task Move_sequence() + => Test( + builder => builder.HasSequence("TestSequence"), + builder => builder.HasSequence("TestSequence", "TestSequenceSchema"), + model => + { + var sequence = Assert.Single(model.Sequences); + Assert.Equal("TestSequenceSchema", sequence.Schema); + Assert.Equal("TestSequence", sequence.Name); + }); - protected virtual void DiffSnapshot(ModelSnapshot snapshot, DbContext context) - { - var sourceModel = ((IMutableModel)snapshot.Model).FinalizeModel(); - var targetModel = context.Model; + [ConditionalFact] + public virtual Task InsertDataOperation() + => Test( + builder => builder.Entity( + "Person", e => + { + e.Property("Id"); + e.Property("Name"); + e.HasKey("Id"); + }), + builder => { }, + builder => builder.Entity("Person") + .HasData( + new Person { Id = 1, Name = "Daenerys Targaryen" }, + new Person { Id = 2, Name = "John Snow" }, + new Person { Id = 3, Name = "Arya Stark" }, + new Person { Id = 4, Name = "Harry Strickland" }, + new Person { Id = 5, Name = null }), + model => { }); - var typeMapper = context.GetService(); + [ConditionalFact] + public virtual Task DeleteDataOperation_simple_key() + => Test( + builder => builder.Entity( + "Person", e => + { + e.Property("Id"); + e.Property("Name"); + e.HasKey("Id"); + e.HasData(new Person { Id = 1, Name = "Daenerys Targaryen" }); + }), + builder => builder.Entity("Person").HasData(new Person { Id = 2, Name = "John Snow" }), + builder => { }, + model => { }); - foreach (var property in sourceModel.GetEntityTypes().SelectMany(e => e.GetDeclaredProperties())) - { - Assert.NotNull(typeMapper.FindMapping(property)); - } + [ConditionalFact] + public virtual Task DeleteDataOperation_composite_key() + => Test( + builder => builder.Entity( + "Person", e => + { + e.Property("Id"); + e.Property("AnotherId"); + e.HasKey("Id", "AnotherId"); + e.Property("Name"); + e.HasData( + new Person + { + Id = 1, + AnotherId = 11, + Name = "Daenerys Targaryen" + }); + }), + builder => builder.Entity("Person").HasData( + new Person + { + Id = 2, + AnotherId = 12, + Name = "John Snow" + }), + builder => { }, + model => { }); - foreach (var property in targetModel.GetEntityTypes().SelectMany(e => e.GetDeclaredProperties())) - { - Assert.NotNull(typeMapper.FindMapping(property)); - } + [ConditionalFact] + public virtual Task UpdateDataOperation_simple_key() + => Test( + builder => builder.Entity( + "Person", e => + { + e.Property("Id"); + e.Property("Name"); + e.HasKey("Id"); + e.HasData(new Person { Id = 1, Name = "Daenerys Targaryen" }); + }), + builder => builder.Entity("Person").HasData(new Person { Id = 2, Name = "John Snow" }), + builder => builder.Entity("Person").HasData(new Person { Id = 2, Name = "Another John Snow" }), + model => { }); - var modelDiffer = context.GetService(); - var operations = modelDiffer.GetDifferences(sourceModel, targetModel); + [ConditionalFact] + public virtual Task UpdateDataOperation_composite_key() + => Test( + builder => builder.Entity( + "Person", e => + { + e.Property("Id"); + e.Property("AnotherId"); + e.HasKey("Id", "AnotherId"); + e.Property("Name"); + e.HasData( + new Person + { + Id = 1, + AnotherId = 11, + Name = "Daenerys Targaryen" + }); + }), + builder => builder.Entity("Person").HasData( + new Person + { + Id = 2, + AnotherId = 11, + Name = "John Snow" + }), + builder => builder.Entity("Person").HasData( + new Person + { + Id = 2, + AnotherId = 11, + Name = "Another John Snow" + }), + model => { }); - Assert.Equal(0, operations.Count); - } + [ConditionalFact] + public virtual Task UpdateDataOperation_multiple_columns() + => Test( + builder => builder.Entity( + "Person", e => + { + e.Property("Id"); + e.Property("Name"); + e.Property("Age"); + e.HasKey("Id"); + e.HasData( + new Person + { + Id = 1, + Name = "Daenerys Targaryen", + Age = 18 + }); + }), + builder => builder.Entity("Person").HasData( + new Person + { + Id = 2, + Name = "John Snow", + Age = 20 + }), + builder => builder.Entity("Person").HasData( + new Person + { + Id = 2, + Name = "Another John Snow", + Age = 21 + }), + model => { }); - protected virtual void BuildFirstMigration(MigrationBuilder migrationBuilder) + [ConditionalFact] + public virtual async Task SqlOperation() { - migrationBuilder.CreateTable( - name: "CreatedTable", - columns: x => new + await Test( + builder => { }, + new SqlOperation { Sql = "-- I <3 DDL" }, + model => { - Id = x.Column(), - ColumnWithDefaultToDrop = x.Column(nullable: true, defaultValue: 0), - ColumnWithDefaultToAlter = x.Column(nullable: true, defaultValue: 1) - }, - constraints: x => - { - x.PrimaryKey( - name: "PK_CreatedTable", - columns: t => t.Id); + Assert.Empty(model.Tables); + Assert.Empty(model.Sequences); }); + + AssertSql( + @"-- I <3 DDL"); } - protected virtual Task AssertFirstMigrationAsync(DbConnection connection) + private class Person { - AssertFirstMigration(connection); - - return Task.FromResult(0); + public int Id { get; set; } + public int AnotherId { get; set; } + public string? Name { get; set; } + public int Age { get; set; } } - protected virtual void AssertFirstMigration(DbConnection connection) + protected virtual DbContext CreateContext() => Fixture.CreateContext(); + + protected virtual string DelimitIdentifier(string unquotedIdentifier) + => _sqlGenerationHelper?.DelimitIdentifier(unquotedIdentifier) + ?? throw new InvalidOperationException( + $"No ISqlGenerationHelper singleton was found, consider overriding {nameof(DelimitIdentifier)}"); + + protected virtual IRelationalTypeMappingSource TypeMappingSource + => _typeMappingSource + ?? throw new InvalidOperationException( + $"No IRelationalTypeMappingSource singleton was found, consider overriding {nameof(TypeMappingSource)}"); + + protected virtual Task Test( + Action buildSourceAction, + Action buildTargetAction, + Action? asserter) + => Test(b => { }, buildSourceAction, buildTargetAction, asserter); + + protected virtual Task Test( + Action buildCommonAction, + Action buildSourceAction, + Action buildTargetAction, + Action? asserter) { + // Build the source and target models. Add current/latest product version if one wasn't set. + var sourceModelBuilder = CreateConventionlessModelBuilder(); + buildCommonAction(sourceModelBuilder); + buildSourceAction(sourceModelBuilder); + var sourceModel = sourceModelBuilder.FinalizeModel(); + + var targetModelBuilder = CreateConventionlessModelBuilder(); + buildCommonAction(targetModelBuilder); + buildTargetAction(targetModelBuilder); + var targetModel = targetModelBuilder.FinalizeModel(); + + var context = CreateContext(); + var serviceProvider = ((IInfrastructure)context).Instance; + var modelDiffer = serviceProvider.GetRequiredService(); + + var operations = modelDiffer.GetDifferences(sourceModel, targetModel); + + return Test(sourceModel, targetModel, operations, asserter); } - protected virtual void BuildSecondMigration(MigrationBuilder migrationBuilder) + protected virtual Task Test( + Action buildSourceAction, + MigrationOperation operation, + Action? asserter) + => Test(buildSourceAction, new[] { operation }, asserter); + + protected virtual Task Test( + Action buildSourceAction, + IReadOnlyList operations, + Action? asserter) { - migrationBuilder.DropColumn( - name: "ColumnWithDefaultToDrop", - table: "CreatedTable"); - migrationBuilder.AlterColumn( - name: "ColumnWithDefaultToAlter", - table: "CreatedTable", - nullable: true); + var sourceModelBuilder = CreateConventionlessModelBuilder(); + buildSourceAction(sourceModelBuilder); + if (sourceModelBuilder.Model.GetProductVersion() is null) + { + sourceModelBuilder.Model.SetProductVersion(ProductInfo.GetVersion()); + } + + var sourceModel = sourceModelBuilder.FinalizeModel(); + + return Test(sourceModel, targetModel: null, operations, asserter); } - protected virtual Task AssertSecondMigrationAsync(DbConnection connection) + protected virtual async Task Test( + IModel sourceModel, + IModel? targetModel, + IReadOnlyList operations, + Action? asserter) { - AssertSecondMigration(connection); + var context = CreateContext(); + var serviceProvider = ((IInfrastructure)context).Instance; + var migrationsSqlGenerator = serviceProvider.GetRequiredService(); + var modelDiffer = serviceProvider.GetRequiredService(); + var migrationsCommandExecutor = serviceProvider.GetRequiredService(); + var connection = serviceProvider.GetRequiredService(); + var databaseModelFactory = serviceProvider.GetRequiredService(); - return Task.FromResult(0); + try + { + // Apply migrations to get to the source state, and do a scaffolding snapshot for later comparison. + // Suspending event recording, we're not interested in the SQL of this part + using (Fixture.TestSqlLoggerFactory.SuspendRecordingEvents()) + { + await migrationsCommandExecutor.ExecuteNonQueryAsync( + migrationsSqlGenerator.Generate(modelDiffer.GetDifferences(null, sourceModel), sourceModel), + connection); + } + + // Apply migrations to get from source to target, then reverse-engineer and execute the + // test-provided assertions on the resulting database model + await migrationsCommandExecutor.ExecuteNonQueryAsync( + migrationsSqlGenerator.Generate(operations, targetModel), connection); + + var scaffoldedModel = databaseModelFactory.Create( + context.Database.GetDbConnection(), + new DatabaseModelFactoryOptions()); + + asserter?.Invoke(scaffoldedModel); + } + finally + { + using var _ = Fixture.TestSqlLoggerFactory.SuspendRecordingEvents(); + Fixture.TestStore.Clean(context); + } } - protected virtual void AssertSecondMigration(DbConnection connection) + protected virtual Task TestThrows( + Action buildSourceAction, + Action buildTargetAction) + where T : Exception + => TestThrows(b => { }, buildSourceAction, buildTargetAction); + + protected virtual Task TestThrows( + Action buildCommonAction, + Action buildSourceAction, + Action buildTargetAction) + where T : Exception + => Assert.ThrowsAsync(() => Test(buildCommonAction, buildSourceAction, buildTargetAction, asserter: null)); + + protected virtual void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); + + public abstract class MigrationsFixtureBase : SharedStoreFixtureBase { + public abstract TestHelpers TestHelpers { get; } + public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; } - private void SetSql(string value) => Sql = value.Replace(ProductInfo.GetVersion(), "7.0.0-test"); + protected virtual ModelBuilder CreateConventionlessModelBuilder(bool sensitiveDataLoggingEnabled = false) + { + var conventionSet = new ConventionSet(); + + var dependencies = Fixture.TestHelpers.CreateContextServices().GetRequiredService(); + conventionSet.ModelFinalizedConventions.Add(new TypeMappingConvention(dependencies)); + + return new ModelBuilder(conventionSet); + } } } diff --git a/test/EFCore.Relational.Specification.Tests/TestUtilities/TestSqlLoggerFactory.cs b/test/EFCore.Relational.Specification.Tests/TestUtilities/TestSqlLoggerFactory.cs index c3efe60d4bb..afab379cd8f 100644 --- a/test/EFCore.Relational.Specification.Tests/TestUtilities/TestSqlLoggerFactory.cs +++ b/test/EFCore.Relational.Specification.Tests/TestUtilities/TestSqlLoggerFactory.cs @@ -119,7 +119,8 @@ protected override void UnsafeLog( base.UnsafeLog(logLevel, eventId, message, state, exception); } - if (message != null + if (!IsRecordingSuspended + && message != null && eventId.Id != RelationalEventId.CommandExecuting.Id) { var structure = (IReadOnlyList>)state; diff --git a/test/EFCore.Relational.Tests/Migrations/MigrationSqlGeneratorTest.cs b/test/EFCore.Relational.Tests/Migrations/MigrationSqlGeneratorTest.cs deleted file mode 100644 index 593a9b31b21..00000000000 --- a/test/EFCore.Relational.Tests/Migrations/MigrationSqlGeneratorTest.cs +++ /dev/null @@ -1,433 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.EntityFrameworkCore.Migrations.Operations; -using Microsoft.EntityFrameworkCore.TestUtilities; -using Xunit; - -namespace Microsoft.EntityFrameworkCore.Migrations -{ - public class MigrationSqlGeneratorTest : MigrationSqlGeneratorTestBase - { - public override void AddColumnOperation_with_defaultValue() - { - base.AddColumnOperation_with_defaultValue(); - - AssertSql( - @"ALTER TABLE ""dbo"".""People"" ADD ""Name"" varchar(30) NOT NULL DEFAULT 'John Doe'; -"); - } - - public override void AddColumnOperation_with_defaultValueSql() - { - base.AddColumnOperation_with_defaultValueSql(); - - AssertSql( - @"ALTER TABLE ""People"" ADD ""Birthday"" date NULL DEFAULT (CURRENT_TIMESTAMP); -"); - } - - public override void AddColumnOperation_without_column_type() - { - base.AddColumnOperation_without_column_type(); - - AssertSql( - @"ALTER TABLE ""People"" ADD ""Alias"" just_string(max) NOT NULL; -"); - } - - public override void AddColumnOperation_with_maxLength() - { - base.AddColumnOperation_with_maxLength(); - - AssertSql( - @"ALTER TABLE ""Person"" ADD ""Name"" just_string(30) NULL; -"); - } - - public override void AddColumnOperation_with_maxLength_on_derived() - { - base.AddColumnOperation_with_maxLength_on_derived(); - - AssertSql( - @"ALTER TABLE ""Person"" ADD ""Name"" just_string(30) NULL; -"); - } - - public override void AddColumnOperation_with_shared_column() - { - base.AddColumnOperation_with_shared_column(); - - AssertSql( - @"ALTER TABLE ""Base"" ADD ""Foo"" just_string(max) NULL; -"); - } - - public override void AddForeignKeyOperation_with_name() - { - base.AddForeignKeyOperation_with_name(); - - AssertSql( - @"ALTER TABLE ""dbo"".""People"" ADD CONSTRAINT ""FK_People_Companies"" FOREIGN KEY (""EmployerId1"", ""EmployerId2"") REFERENCES ""hr"".""Companies"" (""Id1"", ""Id2"") ON DELETE CASCADE; -"); - } - - public override void AddForeignKeyOperation_without_name() - { - base.AddForeignKeyOperation_without_name(); - - AssertSql( - @"ALTER TABLE ""People"" ADD FOREIGN KEY (""SpouseId"") REFERENCES ""People"" (""Id""); -"); - } - - public override void AddForeignKeyOperation_without_principal_columns() - { - base.AddForeignKeyOperation_without_principal_columns(); - - AssertSql( - @"ALTER TABLE ""People"" ADD FOREIGN KEY (""SpouseId"") REFERENCES ""People""; -"); - } - - public override void AddPrimaryKeyOperation_with_name() - { - base.AddPrimaryKeyOperation_with_name(); - - AssertSql( - @"ALTER TABLE ""dbo"".""People"" ADD CONSTRAINT ""PK_People"" PRIMARY KEY (""Id1"", ""Id2""); -"); - } - - public override void AddPrimaryKeyOperation_without_name() - { - base.AddPrimaryKeyOperation_without_name(); - - AssertSql( - @"ALTER TABLE ""People"" ADD PRIMARY KEY (""Id""); -"); - } - - public override void AddUniqueConstraintOperation_with_name() - { - base.AddUniqueConstraintOperation_with_name(); - - AssertSql( - @"ALTER TABLE ""dbo"".""People"" ADD CONSTRAINT ""AK_People_DriverLicense"" UNIQUE (""DriverLicense_State"", ""DriverLicense_Number""); -"); - } - - public override void AddUniqueConstraintOperation_without_name() - { - base.AddUniqueConstraintOperation_without_name(); - - AssertSql( - @"ALTER TABLE ""People"" ADD UNIQUE (""SSN""); -"); - } - - public override void CreateCheckConstraintOperation_with_name() - { - base.CreateCheckConstraintOperation_with_name(); - - AssertSql( - @"ALTER TABLE ""dbo"".""People"" ADD CONSTRAINT ""CK_People_DriverLicense"" CHECK (DriverLicense_Number > 0); -"); - } - - public override void AlterSequenceOperation_with_minValue_and_maxValue() - { - base.AlterSequenceOperation_with_minValue_and_maxValue(); - - AssertSql( - @"ALTER SEQUENCE ""dbo"".""EntityFrameworkHiLoSequence"" INCREMENT BY 1 MINVALUE 2 MAXVALUE 816 CYCLE; -"); - } - - public override void AlterSequenceOperation_without_minValue_and_maxValue() - { - base.AlterSequenceOperation_without_minValue_and_maxValue(); - - AssertSql( - @"ALTER SEQUENCE ""EntityFrameworkHiLoSequence"" INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE; -"); - } - - public override void CreateIndexOperation_unique() - { - base.CreateIndexOperation_unique(); - - AssertSql( - @"CREATE UNIQUE INDEX ""IX_People_Name"" ON ""dbo"".""People"" (""FirstName"", ""LastName""); -"); - } - - public override void CreateIndexOperation_nonunique() - { - base.CreateIndexOperation_nonunique(); - - AssertSql( - @"CREATE INDEX ""IX_People_Name"" ON ""People"" (""Name""); -"); - } - - public override void CreateIndexOperation_with_where_clauses() - { - base.CreateIndexOperation_with_where_clauses(); - - AssertSql( - @"CREATE INDEX ""IX_People_Name"" ON ""People"" (""Name"") WHERE [Id] > 2; -"); - } - - public override void CreateSequenceOperation_with_minValue_and_maxValue() - { - base.CreateSequenceOperation_with_minValue_and_maxValue(); - - AssertSql( - @"CREATE SEQUENCE ""dbo"".""EntityFrameworkHiLoSequence"" START WITH 3 INCREMENT BY 1 MINVALUE 2 MAXVALUE 816 CYCLE; -"); - } - - public override void CreateSequenceOperation_with_minValue_and_maxValue_not_long() - { - base.CreateSequenceOperation_with_minValue_and_maxValue_not_long(); - - AssertSql( - @"CREATE SEQUENCE ""dbo"".""EntityFrameworkHiLoSequence"" AS default_int_mapping START WITH 3 INCREMENT BY 1 MINVALUE 2 MAXVALUE 816 CYCLE; -"); - } - - public override void CreateSequenceOperation_without_minValue_and_maxValue() - { - base.CreateSequenceOperation_without_minValue_and_maxValue(); - - AssertSql( - @"CREATE SEQUENCE ""EntityFrameworkHiLoSequence"" START WITH 3 INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE; -"); - } - - public override void CreateTableOperation() - { - base.CreateTableOperation(); - - AssertSql( - @"CREATE TABLE ""dbo"".""People"" ( - ""Id"" default_int_mapping NOT NULL, - ""EmployerId"" default_int_mapping NULL, - ""SSN"" char(11) NULL, - PRIMARY KEY (""Id""), - UNIQUE (""SSN""), - CHECK (SSN > 0), - FOREIGN KEY (""EmployerId"") REFERENCES ""Companies"" (""Id"") -); -"); - } - - public override void CreateTableOperation_no_key() - { - base.CreateTableOperation_no_key(); - - AssertSql( - @"CREATE TABLE ""Anonymous"" ( - ""Value"" default_int_mapping NOT NULL -); -"); - } - - public override void DropColumnOperation() - { - base.DropColumnOperation(); - - AssertSql( - @"ALTER TABLE ""dbo"".""People"" DROP COLUMN ""LuckyNumber""; -"); - } - - public override void DropForeignKeyOperation() - { - base.DropForeignKeyOperation(); - - AssertSql( - @"ALTER TABLE ""dbo"".""People"" DROP CONSTRAINT ""FK_People_Companies""; -"); - } - - public override void DropPrimaryKeyOperation() - { - base.DropPrimaryKeyOperation(); - - AssertSql( - @"ALTER TABLE ""dbo"".""People"" DROP CONSTRAINT ""PK_People""; -"); - } - - public override void DropSequenceOperation() - { - base.DropSequenceOperation(); - - AssertSql( - @"DROP SEQUENCE ""dbo"".""EntityFrameworkHiLoSequence""; -"); - } - - public override void DropTableOperation() - { - base.DropTableOperation(); - - AssertSql( - @"DROP TABLE ""dbo"".""People""; -"); - } - - public override void DropUniqueConstraintOperation() - { - base.DropUniqueConstraintOperation(); - - AssertSql( - @"ALTER TABLE ""dbo"".""People"" DROP CONSTRAINT ""AK_People_SSN""; -"); - } - - public override void DropCheckConstraintOperation() - { - base.DropCheckConstraintOperation(); - - AssertSql( - @"ALTER TABLE ""dbo"".""People"" DROP CONSTRAINT ""CK_People_SSN""; -"); - } - - public override void SqlOperation() - { - base.SqlOperation(); - - AssertSql( - @"-- I <3 DDL -"); - } - - [ConditionalFact] - public void Generate_doesnt_batch_by_default() - { - Generate( - new SqlOperation { Sql = "SELECT 1;" }, - new SqlOperation { Sql = "SELECT 2;" }); - - AssertSql( - @"SELECT 1; -GO - -SELECT 2; -"); - } - - public override void InsertDataOperation() - { - base.InsertDataOperation(); - - AssertSql( - @"INSERT INTO ""People"" (""Id"", ""Full Name"") -VALUES (0, NULL); -INSERT INTO ""People"" (""Id"", ""Full Name"") -VALUES (1, 'Daenerys Targaryen'); -INSERT INTO ""People"" (""Id"", ""Full Name"") -VALUES (2, 'John Snow'); -INSERT INTO ""People"" (""Id"", ""Full Name"") -VALUES (3, 'Arya Stark'); -INSERT INTO ""People"" (""Id"", ""Full Name"") -VALUES (4, 'Harry Strickland'); -"); - } - - public override void DeleteDataOperation_simple_key() - { - base.DeleteDataOperation_simple_key(); - - // TODO remove rowcount - AssertSql( - @"DELETE FROM ""People"" -WHERE ""Id"" = 2; -SELECT provider_specific_rowcount(); - -DELETE FROM ""People"" -WHERE ""Id"" = 4; -SELECT provider_specific_rowcount(); - -"); - } - - public override void DeleteDataOperation_composite_key() - { - base.DeleteDataOperation_composite_key(); - - // TODO remove rowcount - AssertSql( - @"DELETE FROM ""People"" -WHERE ""First Name"" = 'Hodor' AND ""Last Name"" IS NULL; -SELECT provider_specific_rowcount(); - -DELETE FROM ""People"" -WHERE ""First Name"" = 'Daenerys' AND ""Last Name"" = 'Targaryen'; -SELECT provider_specific_rowcount(); - -"); - } - - public override void UpdateDataOperation_simple_key() - { - base.UpdateDataOperation_simple_key(); - - // TODO remove rowcount - AssertSql( - @"UPDATE ""People"" SET ""Full Name"" = 'Daenerys Stormborn' -WHERE ""Id"" = 1; -SELECT provider_specific_rowcount(); - -UPDATE ""People"" SET ""Full Name"" = 'Homeless Harry Strickland' -WHERE ""Id"" = 4; -SELECT provider_specific_rowcount(); - -"); - } - - public override void UpdateDataOperation_composite_key() - { - base.UpdateDataOperation_composite_key(); - - // TODO remove rowcount - AssertSql( - @"UPDATE ""People"" SET ""First Name"" = 'Hodor' -WHERE ""Id"" = 0 AND ""Last Name"" IS NULL; -SELECT provider_specific_rowcount(); - -UPDATE ""People"" SET ""First Name"" = 'Harry' -WHERE ""Id"" = 4 AND ""Last Name"" = 'Strickland'; -SELECT provider_specific_rowcount(); - -"); - } - - public override void UpdateDataOperation_multiple_columns() - { - base.UpdateDataOperation_multiple_columns(); - - // TODO remove rowcount - AssertSql( - @"UPDATE ""People"" SET ""First Name"" = 'Daenerys', ""Nickname"" = 'Dany' -WHERE ""Id"" = 1; -SELECT provider_specific_rowcount(); - -UPDATE ""People"" SET ""First Name"" = 'Harry', ""Nickname"" = 'Homeless' -WHERE ""Id"" = 4; -SELECT provider_specific_rowcount(); - -"); - } - - public MigrationSqlGeneratorTest() - : base(RelationalTestHelpers.Instance) - { - } - } -} diff --git a/test/EFCore.Relational.Tests/Migrations/MigrationSqlGeneratorTestBase.cs b/test/EFCore.Relational.Tests/Migrations/MigrationSqlGeneratorTestBase.cs new file mode 100644 index 00000000000..c79fb418b04 --- /dev/null +++ b/test/EFCore.Relational.Tests/Migrations/MigrationSqlGeneratorTestBase.cs @@ -0,0 +1,168 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Microsoft.EntityFrameworkCore.Migrations +{ + public abstract class MigrationSqlGeneratorTestBase + { + protected static string EOL => Environment.NewLine; + + protected virtual string Sql { get; set; } + + [ConditionalFact] + public virtual void AddColumnOperation_without_column_type() + => Generate( + new AddColumnOperation + { + Table = "People", + Name = "Alias", + ClrType = typeof(string) + }); + + [ConditionalFact] + public virtual void AddColumnOperation_with_unicode_overridden() + => Generate( + modelBuilder => modelBuilder.Entity("Person").Property("Name").IsUnicode(false), + new AddColumnOperation + { + Table = "Person", + Name = "Name", + ClrType = typeof(string), + IsUnicode = true, + IsNullable = true + }); + + [ConditionalFact] + public virtual void AddColumnOperation_with_unicode_no_model() + => Generate( + new AddColumnOperation + { + Table = "Person", + Name = "Name", + ClrType = typeof(string), + IsUnicode = false, + IsNullable = true + }); + + [ConditionalFact] + public virtual void AddColumnOperation_with_fixed_length_no_model() + => Generate( + new AddColumnOperation + { + Table = "Person", + Name = "Name", + ClrType = typeof(string), + IsUnicode = false, + IsNullable = true, + IsFixedLength = true, + MaxLength = 100 + }); + + [ConditionalFact] + public virtual void AddColumnOperation_with_maxLength_overridden() + => Generate( + modelBuilder => modelBuilder.Entity("Person").Property("Name").HasMaxLength(30), + new AddColumnOperation + { + Table = "Person", + Name = "Name", + ClrType = typeof(string), + MaxLength = 32, + IsNullable = true + }); + + [ConditionalFact] + public virtual void AddColumnOperation_with_maxLength_no_model() + => Generate( + new AddColumnOperation + { + Table = "Person", + Name = "Name", + ClrType = typeof(string), + MaxLength = 30, + IsNullable = true + }); + + [ConditionalFact] + public virtual void AddForeignKeyOperation_without_principal_columns() + => Generate( + new AddForeignKeyOperation + { + Table = "People", + Columns = new[] { "SpouseId" }, + PrincipalTable = "People" + }); + + [ConditionalFact] + public virtual void AlterColumnOperation_without_column_type() + => Generate( + new AlterColumnOperation + { + Table = "People", + Name = "LuckyNumber", + ClrType = typeof(int) + }); + + [ConditionalFact] + public virtual void RenameTableOperation_legacy() + => Generate( + new RenameTableOperation + { + Name = "People", + Schema = "dbo", + NewName = "Person" + }); + + [ConditionalFact] + public virtual void RenameTableOperation() + => Generate( + modelBuilder => modelBuilder.HasAnnotation(CoreAnnotationNames.ProductVersion, "2.1.0"), + new RenameTableOperation + { + Name = "People", + Schema = "dbo", + NewName = "Person", + NewSchema = "dbo" + }); + + [ConditionalFact] + public virtual void SqlOperation() + => Generate( + new SqlOperation { Sql = "-- I <3 DDL" }); + + protected TestHelpers TestHelpers { get; } + + protected MigrationSqlGeneratorTestBase(TestHelpers testHelpers) + { + TestHelpers = testHelpers; + } + + protected virtual void Generate(params MigrationOperation[] operation) + => Generate(_ => { }, operation); + + protected virtual void Generate(Action buildAction, params MigrationOperation[] operation) + { + var modelBuilder = TestHelpers.CreateConventionBuilder(); + modelBuilder.Model.RemoveAnnotation(CoreAnnotationNames.ProductVersion); + buildAction(modelBuilder); + + var batch = TestHelpers.CreateContextServices().GetRequiredService() + .Generate(operation, modelBuilder.Model); + + Sql = string.Join( + "GO" + EOL + EOL, + batch.Select(b => b.CommandText)); + } + + protected void AssertSql(string expected) + => Assert.Equal(expected, Sql, ignoreLineEndingDifferences: true); + } +} diff --git a/test/EFCore.Specification.Tests/TestUtilities/ListLoggerFactory.cs b/test/EFCore.Specification.Tests/TestUtilities/ListLoggerFactory.cs index b3194df56df..479c998f075 100644 --- a/test/EFCore.Specification.Tests/TestUtilities/ListLoggerFactory.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/ListLoggerFactory.cs @@ -31,6 +31,7 @@ public ListLoggerFactory(Func shouldLogCategory) public virtual void Clear() => Logger.Clear(); public CancellationToken CancelQuery() => Logger.CancelOnNextLogEntry(); + public virtual IDisposable SuspendRecordingEvents() => Logger.SuspendRecordingEvents(); public void SetTestOutputHelper(ITestOutputHelper testOutputHelper) { @@ -68,6 +69,7 @@ protected class ListLogger : ILogger { private readonly object _sync = new object(); private CancellationTokenSource _cancellationTokenSource; + protected bool IsRecordingSuspended { get; private set; } public ITestOutputHelper TestOutputHelper { get; set; } @@ -98,6 +100,12 @@ protected virtual void UnsafeClear() _cancellationTokenSource = null; } + public IDisposable SuspendRecordingEvents() + { + IsRecordingSuspended = true; + return new RecordingSuspensionHandle(this); + } + public void Log( LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { @@ -122,7 +130,10 @@ protected virtual void UnsafeLog( TestOutputHelper?.WriteLine(message + Environment.NewLine); } - LoggedEvents.Add((logLevel, eventId, message, state, exception)); + if (!IsRecordingSuspended) + { + LoggedEvents.Add((logLevel, eventId, message, state, exception)); + } } public bool IsEnabled(LogLevel logLevel) => true; @@ -130,6 +141,13 @@ protected virtual void UnsafeLog( public IDisposable BeginScope(object state) => null; public IDisposable BeginScope(TState state) => null; + + private class RecordingSuspensionHandle : IDisposable + { + private readonly ListLogger _logger; + public RecordingSuspensionHandle(ListLogger logger) => _logger = logger; + public void Dispose() => _logger.IsRecordingSuspended = false; + } } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/MigrationsInfrastructureSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/MigrationsInfrastructureSqlServerTest.cs new file mode 100644 index 00000000000..e8f37bf90d5 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/MigrationsInfrastructureSqlServerTest.cs @@ -0,0 +1,1347 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Identity30.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.TestModels.AspNetIdentity; +using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; + +// ReSharper disable InconsistentNaming +namespace Microsoft.EntityFrameworkCore +{ + [SqlServerCondition(SqlServerCondition.IsNotSqlAzure | SqlServerCondition.IsNotCI)] + public class MigrationsInfrastructureSqlServerTest + : MigrationsInfrastructureTestBase + { + public MigrationsInfrastructureSqlServerTest(MigrationsInfrastructureSqlServerFixture fixture) + : base(fixture) + { + } + + public override void Can_generate_migration_from_initial_database_to_initial() + { + base.Can_generate_migration_from_initial_database_to_initial(); + + Assert.Equal( + @"IF OBJECT_ID(N'[__EFMigrationsHistory]') IS NULL +BEGIN + CREATE TABLE [__EFMigrationsHistory] ( + [MigrationId] nvarchar(150) NOT NULL, + [ProductVersion] nvarchar(32) NOT NULL, + CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId]) + ); +END; + +GO + +", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Can_generate_no_migration_script() + { + base.Can_generate_no_migration_script(); + + Assert.Equal( + @"IF OBJECT_ID(N'[__EFMigrationsHistory]') IS NULL +BEGIN + CREATE TABLE [__EFMigrationsHistory] ( + [MigrationId] nvarchar(150) NOT NULL, + [ProductVersion] nvarchar(32) NOT NULL, + CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId]) + ); +END; + +GO + +", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Can_generate_up_scripts() + { + base.Can_generate_up_scripts(); + + Assert.Equal( + @"IF OBJECT_ID(N'[__EFMigrationsHistory]') IS NULL +BEGIN + CREATE TABLE [__EFMigrationsHistory] ( + [MigrationId] nvarchar(150) NOT NULL, + [ProductVersion] nvarchar(32) NOT NULL, + CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId]) + ); +END; + +GO + +CREATE TABLE [Table1] ( + [Id] int NOT NULL, + [Foo] int NOT NULL, + CONSTRAINT [PK_Table1] PRIMARY KEY ([Id]) +); + +GO + +INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'00000000000001_Migration1', N'7.0.0-test'); + +GO + +EXEC sp_rename N'[Table1].[Foo]', N'Bar', N'COLUMN'; + +GO + +INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'00000000000002_Migration2', N'7.0.0-test'); + +GO + +CREATE DATABASE TransactionSuppressed; + +GO + +DROP DATABASE TransactionSuppressed; + +GO + +INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'00000000000003_Migration3', N'7.0.0-test'); + +GO + +", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Can_generate_one_up_script() + { + base.Can_generate_one_up_script(); + + Assert.Equal( + @"EXEC sp_rename N'[Table1].[Foo]', N'Bar', N'COLUMN'; + +GO + +INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'00000000000002_Migration2', N'7.0.0-test'); + +GO + +", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Can_generate_up_script_using_names() + { + base.Can_generate_up_script_using_names(); + + Assert.Equal( + @"EXEC sp_rename N'[Table1].[Foo]', N'Bar', N'COLUMN'; + +GO + +INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) +VALUES (N'00000000000002_Migration2', N'7.0.0-test'); + +GO + +", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Can_generate_idempotent_up_scripts() + { + base.Can_generate_idempotent_up_scripts(); + + Assert.Equal( + @"IF OBJECT_ID(N'[__EFMigrationsHistory]') IS NULL +BEGIN + CREATE TABLE [__EFMigrationsHistory] ( + [MigrationId] nvarchar(150) NOT NULL, + [ProductVersion] nvarchar(32) NOT NULL, + CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId]) + ); +END; + +GO + +IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'00000000000001_Migration1') +BEGIN + CREATE TABLE [Table1] ( + [Id] int NOT NULL, + [Foo] int NOT NULL, + CONSTRAINT [PK_Table1] PRIMARY KEY ([Id]) + ); +END; + +GO + +IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'00000000000001_Migration1') +BEGIN + INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) + VALUES (N'00000000000001_Migration1', N'7.0.0-test'); +END; + +GO + +IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'00000000000002_Migration2') +BEGIN + EXEC sp_rename N'[Table1].[Foo]', N'Bar', N'COLUMN'; +END; + +GO + +IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'00000000000002_Migration2') +BEGIN + INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) + VALUES (N'00000000000002_Migration2', N'7.0.0-test'); +END; + +GO + +IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'00000000000003_Migration3') +BEGIN + CREATE DATABASE TransactionSuppressed; +END; + +GO + +IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'00000000000003_Migration3') +BEGIN + DROP DATABASE TransactionSuppressed; +END; + +GO + +IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'00000000000003_Migration3') +BEGIN + INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) + VALUES (N'00000000000003_Migration3', N'7.0.0-test'); +END; + +GO + +", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Can_generate_down_scripts() + { + base.Can_generate_down_scripts(); + + Assert.Equal( + @"EXEC sp_rename N'[Table1].[Bar]', N'Foo', N'COLUMN'; + +GO + +DELETE FROM [__EFMigrationsHistory] +WHERE [MigrationId] = N'00000000000002_Migration2'; + +GO + +DROP TABLE [Table1]; + +GO + +DELETE FROM [__EFMigrationsHistory] +WHERE [MigrationId] = N'00000000000001_Migration1'; + +GO + +", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Can_generate_idempotent_down_scripts() + { + base.Can_generate_idempotent_down_scripts(); + + Assert.Equal( + @"IF EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'00000000000002_Migration2') +BEGIN + EXEC sp_rename N'[Table1].[Bar]', N'Foo', N'COLUMN'; +END; + +GO + +IF EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'00000000000002_Migration2') +BEGIN + DELETE FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'00000000000002_Migration2'; +END; + +GO + +IF EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'00000000000001_Migration1') +BEGIN + DROP TABLE [Table1]; +END; + +GO + +IF EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'00000000000001_Migration1') +BEGIN + DELETE FROM [__EFMigrationsHistory] + WHERE [MigrationId] = N'00000000000001_Migration1'; +END; + +GO + +", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Can_generate_one_down_script() + { + base.Can_generate_one_down_script(); + + Assert.Equal( + @"EXEC sp_rename N'[Table1].[Bar]', N'Foo', N'COLUMN'; + +GO + +DELETE FROM [__EFMigrationsHistory] +WHERE [MigrationId] = N'00000000000002_Migration2'; + +GO + +", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Can_generate_down_script_using_names() + { + base.Can_generate_down_script_using_names(); + + Assert.Equal( + @"EXEC sp_rename N'[Table1].[Bar]', N'Foo', N'COLUMN'; + +GO + +DELETE FROM [__EFMigrationsHistory] +WHERE [MigrationId] = N'00000000000002_Migration2'; + +GO + +", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Can_get_active_provider() + { + base.Can_get_active_provider(); + + Assert.Equal("Microsoft.EntityFrameworkCore.SqlServer", ActiveProvider); + } + + [ConditionalFact] + public async Task Empty_Migration_Creates_Database() + { + using var context = new BloggingContext( + Fixture.TestStore.AddProviderOptions( + new DbContextOptionsBuilder().EnableServiceProviderCaching(false)).Options); + var creator = (SqlServerDatabaseCreator)context.GetService(); + creator.RetryTimeout = TimeSpan.FromMinutes(10); + + await context.Database.MigrateAsync(); + + Assert.True(creator.Exists()); + } + + private class BloggingContext : DbContext + { + public BloggingContext(DbContextOptions options) + : base(options) + { + } + + // ReSharper disable once UnusedMember.Local + public DbSet Blogs { get; set; } + + // ReSharper disable once ClassNeverInstantiated.Local + public class Blog + { + // ReSharper disable UnusedMember.Local + public int Id { get; set; } + + public string Name { get; set; } + // ReSharper restore UnusedMember.Local + } + } + + [DbContext(typeof(BloggingContext))] + [Migration("00000000000000_Empty")] + public class EmptyMigration : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + } + } + + public override void Can_diff_against_2_2_model() + { + using var context = new ModelSnapshot22.BloggingContext(); + DiffSnapshot(new BloggingContextModelSnapshot22(), context); + } + + public class BloggingContextModelSnapshot22 : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.4-servicing-10062") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity( + "ModelSnapshot22.Blog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("Blogs"); + + b.HasData( + new { Id = 1, Name = "HalfADonkey" }); + }); + + modelBuilder.Entity( + "ModelSnapshot22.Post", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("BlogId"); + + b.Property("Content"); + + b.Property("EditDate"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("BlogId"); + + b.ToTable("Post"); + }); + + modelBuilder.Entity( + "ModelSnapshot22.Post", b => + { + b.HasOne("ModelSnapshot22.Blog", "Blog") + .WithMany("Posts") + .HasForeignKey("BlogId"); + }); +#pragma warning restore 612, 618 + } + } + + public override void Can_diff_against_2_1_ASP_NET_Identity_model() + { + using var context = new ApplicationDbContext(); + DiffSnapshot(new AspNetIdentity21ModelSnapshot(), context); + } + + public class AspNetIdentity21ModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasMaxLength(128); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider") + .HasMaxLength(128); + + b.Property("Name") + .HasMaxLength(128); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } + + public override void Can_diff_against_2_2_ASP_NET_Identity_model() + { + using var context = new ApplicationDbContext(); + DiffSnapshot(new AspNetIdentity22ModelSnapshot(), context); + } + + public class AspNetIdentity22ModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.0-preview1") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasMaxLength(128); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider") + .HasMaxLength(128); + + b.Property("Name") + .HasMaxLength(128); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } + + public override void Can_diff_against_3_0_ASP_NET_Identity_model() + { + using var context = new ApplicationDbContext(); + DiffSnapshot(new AspNetIdentity30ModelSnapshot(), context); + } + + public class AspNetIdentity30ModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Name") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } + + public class MigrationsInfrastructureSqlServerFixture : MigrationsFixtureBase + { + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; + + public MigrationsInfrastructureSqlServerFixture() + { + ((SqlServerTestStore)TestStore).ExecuteNonQuery( + @"USE master +IF EXISTS(select * from sys.databases where name='TransactionSuppressed') +DROP DATABASE TransactionSuppressed"); + } + + public override MigrationsContext CreateContext() + { + var options = AddOptions( + new DbContextOptionsBuilder() + .UseSqlServer(TestStore.ConnectionString, b => b.ApplyConfiguration())) + .UseInternalServiceProvider(ServiceProvider) + .Options; + return new MigrationsContext(options); + } + } + } +} + +namespace ModelSnapshot22 +{ + public class Blog + { + public int Id { get; set; } + public string Name { get; set; } + + public ICollection Posts { get; set; } + } + + public class Post + { + public int Id { get; set; } + public string Title { get; set; } + public string Content { get; set; } + public DateTime EditDate { get; set; } + + public Blog Blog { get; set; } + } + + public class BloggingContext : DbContext + { + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0"); + + public DbSet Blogs { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().HasData( + new Blog { Id = 1, Name = "HalfADonkey" }); + } + } +} + +namespace Identity30.Data +{ + public class ApplicationDbContext : IdentityDbContext + { + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0"); + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.Entity( + b => + { + b.HasIndex(u => u.NormalizedUserName).HasName("UserNameIndex").IsUnique(); + b.HasIndex(u => u.NormalizedEmail).HasName("EmailIndex"); + b.ToTable("AspNetUsers"); + }); + + builder.Entity>( + b => + { + b.ToTable("AspNetUserClaims"); + }); + + builder.Entity>( + b => + { + b.ToTable("AspNetUserLogins"); + }); + + builder.Entity>( + b => + { + b.ToTable("AspNetUserTokens"); + }); + + builder.Entity( + b => + { + b.HasIndex(r => r.NormalizedName).HasName("RoleNameIndex").IsUnique(); + b.ToTable("AspNetRoles"); + }); + + builder.Entity>( + b => + { + b.ToTable("AspNetRoleClaims"); + }); + + builder.Entity>( + b => + { + b.ToTable("AspNetUserRoles"); + }); + } + } +} diff --git a/test/EFCore.SqlServer.FunctionalTests/MigrationsSqlServerFixture.cs b/test/EFCore.SqlServer.FunctionalTests/MigrationsSqlServerFixture.cs deleted file mode 100644 index 59fc8ef9ba6..00000000000 --- a/test/EFCore.SqlServer.FunctionalTests/MigrationsSqlServerFixture.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.EntityFrameworkCore.TestUtilities; - -namespace Microsoft.EntityFrameworkCore -{ - public class MigrationsSqlServerFixture : MigrationsFixtureBase - { - protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; - - public MigrationsSqlServerFixture() - { - ((SqlServerTestStore)TestStore).ExecuteNonQuery( - @"USE master -IF EXISTS(select * from sys.databases where name='TransactionSuppressed') -DROP DATABASE TransactionSuppressed"); - } - - public override MigrationsContext CreateContext() - { - var options = AddOptions( - new DbContextOptionsBuilder() - .UseSqlServer(TestStore.ConnectionString, b => b.ApplyConfiguration())) - .UseInternalServiceProvider(ServiceProvider) - .Options; - return new MigrationsContext(options); - } - } -} diff --git a/test/EFCore.SqlServer.FunctionalTests/MigrationsSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/MigrationsSqlServerTest.cs index 5723ef1aac3..a2a461b5418 100644 --- a/test/EFCore.SqlServer.FunctionalTests/MigrationsSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/MigrationsSqlServerTest.cs @@ -2,1422 +2,1567 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; -using System.Data.Common; +using System.Linq; using System.Threading.Tasks; -using Identity30.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.EntityFrameworkCore.TestModels.AspNetIdentity; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Scaffolding; +using Microsoft.EntityFrameworkCore.Scaffolding.Metadata.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Scaffolding.Internal; using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.Extensions.DependencyInjection; using Xunit; +using Xunit.Abstractions; + +#nullable enable -// ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore { - [SqlServerCondition(SqlServerCondition.IsNotSqlAzure | SqlServerCondition.IsNotCI)] - public class MigrationsSqlServerTest : MigrationsTestBase + public class MigrationsSqlServerTest : MigrationsTestBase { - public MigrationsSqlServerTest(MigrationsSqlServerFixture fixture) + protected static string EOL => Environment.NewLine; + + public MigrationsSqlServerTest(MigrationsSqlServerFixture fixture, ITestOutputHelper testOutputHelper) : base(fixture) { + Fixture.TestSqlLoggerFactory.Clear(); + //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } - public override void Can_generate_migration_from_initial_database_to_initial() + public override async Task Create_table() { - base.Can_generate_migration_from_initial_database_to_initial(); - - Assert.Equal( - @"IF OBJECT_ID(N'[__EFMigrationsHistory]') IS NULL -BEGIN - CREATE TABLE [__EFMigrationsHistory] ( - [MigrationId] nvarchar(150) NOT NULL, - [ProductVersion] nvarchar(32) NOT NULL, - CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId]) - ); -END; - -GO + await base.Create_table(); -", - Sql, - ignoreLineEndingDifferences: true); + AssertSql( + @"CREATE TABLE [People] ( + [Id] int NOT NULL, + [Name] nvarchar(max) NULL, + CONSTRAINT [PK_People] PRIMARY KEY ([Id]) +);"); } - public override void Can_generate_no_migration_script() + public override async Task Create_table_all_settings() { - base.Can_generate_no_migration_script(); - - Assert.Equal( - @"IF OBJECT_ID(N'[__EFMigrationsHistory]') IS NULL -BEGIN - CREATE TABLE [__EFMigrationsHistory] ( - [MigrationId] nvarchar(150) NOT NULL, - [ProductVersion] nvarchar(32) NOT NULL, - CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId]) - ); -END; - -GO - -", - Sql, - ignoreLineEndingDifferences: true); + await base.Create_table_all_settings(); + + AssertSql( + @"IF SCHEMA_ID(N'dbo2') IS NULL EXEC(N'CREATE SCHEMA [dbo2];');", + // + @"CREATE TABLE [dbo2].[People] ( + [CustomId] int NOT NULL, + [EmployerId] int NOT NULL, + [SSN] nvarchar(11) NOT NULL, + CONSTRAINT [PK_People] PRIMARY KEY ([CustomId]), + CONSTRAINT [AK_People_SSN] UNIQUE ([SSN]), + CONSTRAINT [CK_SSN] CHECK ([SSN] > 0), + CONSTRAINT [FK_People_Employers_EmployerId] FOREIGN KEY ([EmployerId]) REFERENCES [Employers] ([Id]) ON DELETE NO ACTION +); +EXEC sp_addextendedproperty 'MS_Description', N'Table comment', 'SCHEMA', N'dbo2', 'TABLE', N'People'; +EXEC sp_addextendedproperty 'MS_Description', N'Employer ID comment', 'SCHEMA', N'dbo2', 'TABLE', N'People', 'COLUMN', N'EmployerId';"); } - public override void Can_generate_up_scripts() + public override async Task Create_table_no_key() { - base.Can_generate_up_scripts(); + await base.Create_table_no_key(); - Assert.Equal( - @"IF OBJECT_ID(N'[__EFMigrationsHistory]') IS NULL -BEGIN - CREATE TABLE [__EFMigrationsHistory] ( - [MigrationId] nvarchar(150) NOT NULL, - [ProductVersion] nvarchar(32) NOT NULL, - CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId]) - ); -END; + AssertSql( + @"CREATE TABLE [Anonymous] ( + [SomeColumn] int NOT NULL +);"); + } -GO + public override async Task Create_table_with_comments() + { + await base.Create_table_with_comments(); -CREATE TABLE [Table1] ( + AssertSql( + @"CREATE TABLE [People] ( [Id] int NOT NULL, - [Foo] int NOT NULL, - CONSTRAINT [PK_Table1] PRIMARY KEY ([Id]) + [Name] nvarchar(max) NULL ); - -GO - -INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) -VALUES (N'00000000000001_Migration1', N'7.0.0-test'); - -GO - -EXEC sp_rename N'[Table1].[Foo]', N'Bar', N'COLUMN'; - -GO - -INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) -VALUES (N'00000000000002_Migration2', N'7.0.0-test'); - -GO - -CREATE DATABASE TransactionSuppressed; - -GO - -DROP DATABASE TransactionSuppressed; - -GO - -INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) -VALUES (N'00000000000003_Migration3', N'7.0.0-test'); - -GO - -", - Sql, - ignoreLineEndingDifferences: true); +DECLARE @defaultSchema AS sysname; +SET @defaultSchema = SCHEMA_NAME(); +EXEC sp_addextendedproperty 'MS_Description', N'Table comment', 'SCHEMA', @defaultSchema, 'TABLE', N'People'; +EXEC sp_addextendedproperty 'MS_Description', N'Column comment', 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'Name';"); } - public override void Can_generate_one_up_script() + public override async Task Create_table_with_multiline_comments() { - base.Can_generate_one_up_script(); - - Assert.Equal( - @"EXEC sp_rename N'[Table1].[Foo]', N'Bar', N'COLUMN'; - -GO + await base.Create_table_with_multiline_comments(); -INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) -VALUES (N'00000000000002_Migration2', N'7.0.0-test'); - -GO - -", - Sql, - ignoreLineEndingDifferences: true); + AssertSql( + @"CREATE TABLE [People] ( + [Id] int NOT NULL, + [Name] nvarchar(max) NULL +); +DECLARE @defaultSchema AS sysname; +SET @defaultSchema = SCHEMA_NAME(); +EXEC sp_addextendedproperty 'MS_Description', N'This is a multi-line +table comment. +More information can +be found in the docs.', 'SCHEMA', @defaultSchema, 'TABLE', N'People'; +EXEC sp_addextendedproperty 'MS_Description', N'This is a multi-line +column comment. +More information can +be found in the docs.', 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'Name';"); } - public override void Can_generate_up_script_using_names() + public override async Task Drop_table() { - base.Can_generate_up_script_using_names(); - - Assert.Equal( - @"EXEC sp_rename N'[Table1].[Foo]', N'Bar', N'COLUMN'; - -GO - -INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) -VALUES (N'00000000000002_Migration2', N'7.0.0-test'); + await base.Drop_table(); -GO - -", - Sql, - ignoreLineEndingDifferences: true); + AssertSql( + @"DROP TABLE [People];"); } - public override void Can_generate_idempotent_up_scripts() + public override async Task Alter_table_add_comment() { - base.Can_generate_idempotent_up_scripts(); - - Assert.Equal( - @"IF OBJECT_ID(N'[__EFMigrationsHistory]') IS NULL -BEGIN - CREATE TABLE [__EFMigrationsHistory] ( - [MigrationId] nvarchar(150) NOT NULL, - [ProductVersion] nvarchar(32) NOT NULL, - CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId]) - ); -END; - -GO - -IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'00000000000001_Migration1') -BEGIN - CREATE TABLE [Table1] ( - [Id] int NOT NULL, - [Foo] int NOT NULL, - CONSTRAINT [PK_Table1] PRIMARY KEY ([Id]) - ); -END; - -GO - -IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'00000000000001_Migration1') -BEGIN - INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) - VALUES (N'00000000000001_Migration1', N'7.0.0-test'); -END; - -GO + await base.Alter_table_add_comment(); -IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'00000000000002_Migration2') -BEGIN - EXEC sp_rename N'[Table1].[Foo]', N'Bar', N'COLUMN'; -END; - -GO - -IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'00000000000002_Migration2') -BEGIN - INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) - VALUES (N'00000000000002_Migration2', N'7.0.0-test'); -END; - -GO - -IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'00000000000003_Migration3') -BEGIN - CREATE DATABASE TransactionSuppressed; -END; - -GO - -IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'00000000000003_Migration3') -BEGIN - DROP DATABASE TransactionSuppressed; -END; + AssertSql( + @"DECLARE @defaultSchema AS sysname; +SET @defaultSchema = SCHEMA_NAME(); +EXEC sp_addextendedproperty 'MS_Description', N'Table comment', 'SCHEMA', @defaultSchema, 'TABLE', N'People';"); + } -GO + public override async Task Alter_table_add_comment_non_default_schema() + { + await base.Alter_table_add_comment_non_default_schema(); -IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'00000000000003_Migration3') -BEGIN - INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) - VALUES (N'00000000000003_Migration3', N'7.0.0-test'); -END; + AssertSql( + @"EXEC sp_addextendedproperty 'MS_Description', N'Table comment', 'SCHEMA', N'SomeOtherSchema', 'TABLE', N'People';"); + } -GO + public override async Task Alter_table_change_comment() + { + await base.Alter_table_change_comment(); -", - Sql, - ignoreLineEndingDifferences: true); + AssertSql( + @"DECLARE @defaultSchema AS sysname; +SET @defaultSchema = SCHEMA_NAME(); +EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema, 'TABLE', N'People'; +EXEC sp_addextendedproperty 'MS_Description', N'Table comment2', 'SCHEMA', @defaultSchema, 'TABLE', N'People';"); } - public override void Can_generate_down_scripts() + public override async Task Alter_table_remove_comment() { - base.Can_generate_down_scripts(); + await base.Alter_table_remove_comment(); - Assert.Equal( - @"EXEC sp_rename N'[Table1].[Bar]', N'Foo', N'COLUMN'; - -GO + AssertSql( + @"DECLARE @defaultSchema AS sysname; +SET @defaultSchema = SCHEMA_NAME(); +EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema, 'TABLE', N'People';"); + } -DELETE FROM [__EFMigrationsHistory] -WHERE [MigrationId] = N'00000000000002_Migration2'; + public override async Task Rename_table() + { + await base.Rename_table(); -GO + AssertSql( + @"EXEC sp_rename N'[People]', N'people';"); + } -DROP TABLE [Table1]; + public override async Task Rename_table_with_primary_key() + { + await base.Rename_table_with_primary_key(); + + AssertSql( + @"ALTER TABLE [People] DROP CONSTRAINT [PK_People];", + // + @"EXEC sp_rename N'[People]', N'people';", + // + @"ALTER TABLE [people] ADD CONSTRAINT [PK_people] PRIMARY KEY ([Id]);"); + } -GO + public override async Task Move_table() + { + await base.Move_table(); -DELETE FROM [__EFMigrationsHistory] -WHERE [MigrationId] = N'00000000000001_Migration1'; + AssertSql( + @"IF SCHEMA_ID(N'TestTableSchema') IS NULL EXEC(N'CREATE SCHEMA [TestTableSchema];');", + // + @"ALTER SCHEMA [TestTableSchema] TRANSFER [TestTable];"); + } -GO + [ConditionalFact] + public virtual async Task Move_table_into_default_schema() + { + await Test( + builder => builder.Entity("TestTable") + .ToTable("TestTable", "TestTableSchema") + .Property("Id"), + builder => builder.Entity("TestTable") + .Property("Id"), + model => + { + var table = Assert.Single(model.Tables); + Assert.Equal("dbo", table.Schema); + Assert.Equal("TestTable", table.Name); + }); -", - Sql, - ignoreLineEndingDifferences: true); + AssertSql( + @"DECLARE @defaultSchema sysname = SCHEMA_NAME(); +EXEC(N'ALTER SCHEMA [' + @defaultSchema + N'] TRANSFER [TestTableSchema].[TestTable];');"); } - public override void Can_generate_idempotent_down_scripts() + public override async Task Create_schema() { - base.Can_generate_idempotent_down_scripts(); + await base.Create_schema(); + + AssertSql( + @"IF SCHEMA_ID(N'SomeOtherSchema') IS NULL EXEC(N'CREATE SCHEMA [SomeOtherSchema];');", + // + @"CREATE TABLE [SomeOtherSchema].[People] ( + [Id] int NOT NULL +);"); + } - Assert.Equal( - @"IF EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'00000000000002_Migration2') -BEGIN - EXEC sp_rename N'[Table1].[Bar]', N'Foo', N'COLUMN'; -END; + [ConditionalFact] + public virtual async Task Create_schema_dbo_is_ignored() + { + await Test( + builder => { }, + builder => builder.Entity("People") + .ToTable("People", "dbo") + .Property("Id"), + model => Assert.Equal("dbo", Assert.Single(model.Tables).Schema)); + + AssertSql( + @"CREATE TABLE [dbo].[People] ( + [Id] int NOT NULL +);"); + } -GO + public override async Task Add_column_with_defaultValue_string() + { + await base.Add_column_with_defaultValue_string(); -IF EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'00000000000002_Migration2') -BEGIN - DELETE FROM [__EFMigrationsHistory] - WHERE [MigrationId] = N'00000000000002_Migration2'; -END; + AssertSql( + @"ALTER TABLE [People] ADD [Name] nvarchar(max) NOT NULL DEFAULT N'John Doe';"); + } -GO + public override async Task Add_column_with_defaultValue_datetime() + { + await base.Add_column_with_defaultValue_datetime(); -IF EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'00000000000001_Migration1') -BEGIN - DROP TABLE [Table1]; -END; + AssertSql( + @"ALTER TABLE [People] ADD [Birthday] datetime2 NOT NULL DEFAULT '2015-04-12T17:05:00.0000000';"); + } -GO + [ConditionalFact] + public virtual async Task Add_column_with_defaultValue_datetime_store_type() + { + await Test( + builder => builder.Entity("People").Property("Id"), + builder => { }, + builder => builder.Entity("People").Property("Birthday") + .HasColumnType("datetime") + .HasDefaultValue(new DateTime(2019, 1, 1)), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns, c => c.Name == "Birthday"); + Assert.Contains("2019", column.DefaultValueSql); + }); -IF EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'00000000000001_Migration1') -BEGIN - DELETE FROM [__EFMigrationsHistory] - WHERE [MigrationId] = N'00000000000001_Migration1'; -END; + AssertSql( + @"ALTER TABLE [People] ADD [Birthday] datetime NOT NULL DEFAULT '2019-01-01T00:00:00.000';"); + } -GO + [ConditionalFact] + public virtual async Task Add_column_with_defaultValue_smalldatetime_store_type() + { + await Test( + builder => builder.Entity("People").Property("Id"), + builder => { }, + builder => builder.Entity("People").Property("Birthday") + .HasColumnType("smalldatetime") + .HasDefaultValue(new DateTime(2019, 1, 1)), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns, c => c.Name == "Birthday"); + Assert.Contains("2019", column.DefaultValueSql); + }); -", - Sql, - ignoreLineEndingDifferences: true); + AssertSql( + @"ALTER TABLE [People] ADD [Birthday] smalldatetime NOT NULL DEFAULT '2019-01-01T00:00:00';"); } - public override void Can_generate_one_down_script() + [ConditionalFact] + public virtual async Task Add_column_with_rowversion() { - base.Can_generate_one_down_script(); + await Test( + builder => builder.Entity("People").Property("Id"), + builder => { }, + builder => builder.Entity("People").Property("RowVersion").IsRowVersion(), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns, c => c.Name == "RowVersion"); + Assert.Equal("rowversion", column.StoreType); + Assert.True(column.IsRowVersion()); + }); - Assert.Equal( - @"EXEC sp_rename N'[Table1].[Bar]', N'Foo', N'COLUMN'; + AssertSql( + @"ALTER TABLE [People] ADD [RowVersion] rowversion NULL;"); + } -GO + public override async Task Add_column_with_defaultValueSql() + { + await base.Add_column_with_defaultValueSql(); -DELETE FROM [__EFMigrationsHistory] -WHERE [MigrationId] = N'00000000000002_Migration2'; + AssertSql( + @"ALTER TABLE [People] ADD [Birthday] date NULL DEFAULT (CURRENT_TIMESTAMP);"); + } -GO + public override async Task Add_column_with_computedSql() + { + await base.Add_column_with_computedSql(); -", - Sql, - ignoreLineEndingDifferences: true); + AssertSql( + @"ALTER TABLE [People] ADD [FullName] AS FirstName + ' ' + LastName;"); } - public override void Can_generate_down_script_using_names() + public override async Task Add_column_with_required() { - base.Can_generate_down_script_using_names(); + await base.Add_column_with_required(); - Assert.Equal( - @"EXEC sp_rename N'[Table1].[Bar]', N'Foo', N'COLUMN'; + AssertSql( + @"ALTER TABLE [People] ADD [Name] nvarchar(max) NOT NULL DEFAULT N'';"); + } -GO + public override async Task Add_column_with_ansi() + { + await base.Add_column_with_ansi(); -DELETE FROM [__EFMigrationsHistory] -WHERE [MigrationId] = N'00000000000002_Migration2'; + AssertSql( + @"ALTER TABLE [People] ADD [Name] varchar(max) NULL;"); + } -GO + public override async Task Add_column_with_max_length() + { + await base.Add_column_with_max_length(); -", - Sql, - ignoreLineEndingDifferences: true); + AssertSql( + @"ALTER TABLE [People] ADD [Name] nvarchar(30) NULL;"); } - public override void Can_get_active_provider() + public override async Task Add_column_with_max_length_on_derived() { - base.Can_get_active_provider(); + await base.Add_column_with_max_length_on_derived(); - Assert.Equal("Microsoft.EntityFrameworkCore.SqlServer", ActiveProvider); + Assert.Empty(Fixture.TestSqlLoggerFactory.SqlStatements); } - protected override async Task AssertFirstMigrationAsync(DbConnection connection) + public override async Task Add_column_with_fixed_length() { - var sql = await GetDatabaseSchemaAsync(connection); - Assert.Equal( - @" -CreatedTable - Id int NOT NULL - ColumnWithDefaultToDrop int NULL DEFAULT ((0)) - ColumnWithDefaultToAlter int NULL DEFAULT ((1)) + await base.Add_column_with_fixed_length(); -Foos - Id int NOT NULL -", - sql, - ignoreLineEndingDifferences: true); + AssertSql( + @"ALTER TABLE [People] ADD [Name] nchar(100) NULL;"); } - protected override async Task AssertSecondMigrationAsync(DbConnection connection) + public override async Task Add_column_with_comment() { - var sql = await GetDatabaseSchemaAsync(connection); - Assert.Equal( - @" -CreatedTable - Id int NOT NULL - ColumnWithDefaultToAlter int NULL + await base.Add_column_with_comment(); -Foos - Id int NOT NULL -", - sql, - ignoreLineEndingDifferences: true); + AssertSql( + @"ALTER TABLE [People] ADD [FullName] nvarchar(max) NULL; +DECLARE @defaultSchema AS sysname; +SET @defaultSchema = SCHEMA_NAME(); +EXEC sp_addextendedproperty 'MS_Description', N'My comment', 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'FullName';"); } - private async Task GetDatabaseSchemaAsync(DbConnection connection) + public override async Task Add_column_shared() { - var builder = new IndentedStringBuilder(); + await base.Add_column_shared(); - var command = connection.CreateCommand(); - command.CommandText = @" - SELECT - t.name, - c.Name, - TYPE_NAME(c.user_type_id), - c.is_nullable, - d.Definition - FROM sys.objects t - LEFT JOIN sys.columns c ON c.object_id = t.object_id - LEFT JOIN sys.default_constraints d ON d.parent_column_id = c.column_id AND d.parent_object_id = t.object_id - WHERE t.type = 'U' - ORDER BY t.name, c.column_id;"; + AssertSql( + @"ALTER TABLE [Base] ADD [Foo] nvarchar(max) NULL;"); + } - using (var reader = await command.ExecuteReaderAsync()) - { - var first = true; - string lastTable = null; - while (await reader.ReadAsync()) + [ConditionalFact] + public virtual async Task Add_column_identity() + { + await Test( + builder => builder.Entity("People").Property("Id"), + builder => { }, + builder => builder.Entity("People").Property("IdentityColumn").UseIdentityColumn(), + model => { - var currentTable = reader.GetString(0); - if (currentTable != lastTable) - { - if (first) - { - first = false; - } - else - { - builder.DecrementIndent(); - } - - builder - .AppendLine() - .AppendLine(currentTable) - .IncrementIndent(); - - lastTable = currentTable; - } - - builder - .Append(reader[1]) // Name - .Append(" ") - .Append(reader[2]) // Type - .Append(" ") - .Append(reader.GetBoolean(3) ? "NULL" : "NOT NULL"); - - if (!await reader.IsDBNullAsync(4)) - { - builder - .Append(" DEFAULT ") - .Append(reader[4]); - } - - builder.AppendLine(); - } - } + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns, c => c.Name == "IdentityColumn"); + Assert.Equal(ValueGenerated.OnAdd, column.ValueGenerated); + }); - return builder.ToString(); + AssertSql( + @"ALTER TABLE [People] ADD [IdentityColumn] int NOT NULL IDENTITY;"); } [ConditionalFact] - public async Task Empty_Migration_Creates_Database() + public virtual async Task Add_column_identity_seed_increment() { - using var context = new BloggingContext( - Fixture.TestStore.AddProviderOptions( - new DbContextOptionsBuilder().EnableServiceProviderCaching(false)).Options); - var creator = (SqlServerDatabaseCreator)context.GetService(); - creator.RetryTimeout = TimeSpan.FromMinutes(10); - - await context.Database.MigrateAsync(); + await Test( + builder => builder.Entity("People").Property("Id"), + builder => { }, + builder => builder.Entity("People").Property("IdentityColumn").UseIdentityColumn(100, 5), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns, c => c.Name == "IdentityColumn"); + Assert.Equal(ValueGenerated.OnAdd, column.ValueGenerated); + // TODO: Do we not reverse-engineer identity facets? + // Assert.Equal(100, column[SqlServerAnnotationNames.IdentitySeed]); + // Assert.Equal(5, column[SqlServerAnnotationNames.IdentityIncrement]); + }); - Assert.True(creator.Exists()); + AssertSql( + @"ALTER TABLE [People] ADD [IdentityColumn] int NOT NULL IDENTITY(100, 5);"); } - private class BloggingContext : DbContext + public override async Task Alter_column_change_type() { - public BloggingContext(DbContextOptions options) - : base(options) - { - } - - // ReSharper disable once UnusedMember.Local - public DbSet Blogs { get; set; } - - // ReSharper disable once ClassNeverInstantiated.Local - public class Blog - { - // ReSharper disable UnusedMember.Local - public int Id { get; set; } - - public string Name { get; set; } - // ReSharper restore UnusedMember.Local - } + await base.Alter_column_change_type(); + + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'SomeColumn'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [SomeColumn] bigint NOT NULL;"); } - [DbContext(typeof(BloggingContext))] - [Migration("00000000000000_Empty")] - public class EmptyMigration : Migration + public override async Task Alter_column_make_required() { - protected override void Up(MigrationBuilder migrationBuilder) - { - } + await base.Alter_column_make_required(); + + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'SomeColumn'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [SomeColumn] nvarchar(max) NOT NULL;"); } - public override void Can_diff_against_2_2_model() + [ConditionalFact] + public override async Task Alter_column_make_required_with_index() { - using var context = new ModelSnapshot22.BloggingContext(); - DiffSnapshot(new BloggingContextModelSnapshot22(), context); + await base.Alter_column_make_required_with_index(); + + AssertSql( + @"DROP INDEX [IX_People_SomeColumn] ON [People]; +DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'SomeColumn'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [SomeColumn] nvarchar(450) NOT NULL; +CREATE INDEX [IX_People_SomeColumn] ON [People] ([SomeColumn]);"); } - public class BloggingContextModelSnapshot22 : ModelSnapshot + [ConditionalFact] + public override async Task Alter_column_make_required_with_composite_index() { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.4-servicing-10062") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity( - "ModelSnapshot22.Blog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Name"); - - b.HasKey("Id"); - - b.ToTable("Blogs"); - - b.HasData( - new { Id = 1, Name = "HalfADonkey" }); - }); - - modelBuilder.Entity( - "ModelSnapshot22.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("BlogId"); - - b.Property("Content"); - - b.Property("EditDate"); - - b.Property("Title"); - - b.HasKey("Id"); - - b.HasIndex("BlogId"); - - b.ToTable("Post"); - }); - - modelBuilder.Entity( - "ModelSnapshot22.Post", b => - { - b.HasOne("ModelSnapshot22.Blog", "Blog") - .WithMany("Posts") - .HasForeignKey("BlogId"); - }); -#pragma warning restore 612, 618 - } + await base.Alter_column_make_required_with_composite_index(); + + AssertSql( + @"DROP INDEX [IX_People_FirstName_LastName] ON [People]; +DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'FirstName'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [FirstName] nvarchar(450) NOT NULL; +CREATE INDEX [IX_People_FirstName_LastName] ON [People] ([FirstName], [LastName]);"); } - public override void Can_diff_against_2_1_ASP_NET_Identity_model() + [ConditionalFact] + public override async Task Alter_column_make_computed() { - using var context = new ApplicationDbContext(); - DiffSnapshot(new AspNetIdentity21ModelSnapshot(), context); + await base.Alter_column_make_computed(); + + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'FullName'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] DROP COLUMN [FullName]; +ALTER TABLE [People] ADD [FullName] AS FirstName + ' ' + LastName;"); } - public class AspNetIdentity21ModelSnapshot : ModelSnapshot + public override async Task Alter_column_change_computed() { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.0") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken(); - - b.Property("Name") - .HasMaxLength(256); - - b.Property("NormalizedName") - .HasMaxLength(256); + await base.Alter_column_change_computed(); + + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'FullName'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] DROP COLUMN [FullName]; +ALTER TABLE [People] ADD [FullName] AS FirstName + ', ' + LastName;"); + } - b.HasKey("Id"); + [ConditionalFact] + public override async Task Alter_column_add_comment() + { + await base.Alter_column_add_comment(); + + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'Id'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [Id] int NOT NULL; +DECLARE @defaultSchema AS sysname; +SET @defaultSchema = SCHEMA_NAME(); +EXEC sp_addextendedproperty 'MS_Description', N'Some comment', 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'Id';"); + } - b.HasIndex("NormalizedName") - .IsUnique() - .HasName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); + [ConditionalFact] + public override async Task Alter_column_change_comment() + { + await base.Alter_column_change_comment(); + + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'Id'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [Id] int NOT NULL; +DECLARE @defaultSchema AS sysname; +SET @defaultSchema = SCHEMA_NAME(); +EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'Id'; +EXEC sp_addextendedproperty 'MS_Description', N'Some comment2', 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'Id';"); + } - b.ToTable("AspNetRoles"); - }); + [ConditionalFact] + public override async Task Alter_column_remove_comment() + { + await base.Alter_column_remove_comment(); + + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'Id'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [Id] int NOT NULL; +DECLARE @defaultSchema AS sysname; +SET @defaultSchema = SCHEMA_NAME(); +EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'Id';"); + } - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + [ConditionalFact] + public virtual async Task Alter_column_make_required_with_index_with_included_properties() + { + await Test( + builder => builder.Entity( + "People", e => { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClaimType"); - - b.Property("ClaimValue"); - - b.Property("RoleId") - .IsRequired(); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); + e.Property("Id"); + e.Property("SomeColumn"); + e.Property("SomeOtherColumn"); + e.HasIndex("SomeColumn").IncludeProperties("SomeOtherColumn"); + }), + builder => { }, + builder => builder.Entity("People").Property("SomeColumn").IsRequired(), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns, c => c.Name == "SomeColumn"); + Assert.False(column.IsNullable); + var index = Assert.Single(table.Indexes); + // TODO: This is a scaffolding bug, #19351 + Assert.Equal(2, index.Columns.Count); + Assert.Contains(table.Columns.Single(c => c.Name == "SomeColumn"), index.Columns); + Assert.Contains(table.Columns.Single(c => c.Name == "SomeOtherColumn"), index.Columns); + }); - b.ToTable("AspNetRoleClaims"); - }); + AssertSql( + @"DROP INDEX [IX_People_SomeColumn] ON [People]; +DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'SomeColumn'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [SomeColumn] nvarchar(450) NOT NULL; +CREATE INDEX [IX_People_SomeColumn] ON [People] ([SomeColumn]) INCLUDE ([SomeOtherColumn]);"); + } - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUser", b => + [ConditionalFact] + [SqlServerCondition(SqlServerCondition.SupportsMemoryOptimized)] + public virtual async Task Alter_column_memoryOptimized_with_index() + { + await Test( + builder => builder.Entity( + "People", e => { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("AccessFailedCount"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken(); - - b.Property("Email") - .HasMaxLength(256); - - b.Property("EmailConfirmed"); - - b.Property("LockoutEnabled"); - - b.Property("LockoutEnd"); - - b.Property("NormalizedEmail") - .HasMaxLength(256); - - b.Property("NormalizedUserName") - .HasMaxLength(256); - - b.Property("PasswordHash"); - - b.Property("PhoneNumber"); - - b.Property("PhoneNumberConfirmed"); - - b.Property("SecurityStamp"); - - b.Property("TwoFactorEnabled"); - - b.Property("UserName") - .HasMaxLength(256); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); + e.IsMemoryOptimized(); + e.Property("Id"); + e.Property("Name"); + e.HasKey("Id").IsClustered(false); + e.HasIndex("Name").IsClustered(false); + }), + builder => { }, + builder => builder.Entity("People").Property("Name").HasMaxLength(30), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns, c => c.Name == "Name"); + Assert.Equal("nvarchar(30)", column.StoreType); + }); - b.ToTable("AspNetUsers"); - }); + AssertSql( + @"ALTER TABLE [People] DROP INDEX [IX_People_Name]; +DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'Name'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(30) NULL; +ALTER TABLE [People] ADD INDEX [IX_People_Name] NONCLUSTERED ([Name]);"); + } - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + [ConditionalFact] + public virtual async Task Alter_column_with_index_no_narrowing() + { + await Test( + builder => builder.Entity( + "People", e => { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClaimType"); - - b.Property("ClaimValue"); - - b.Property("UserId") - .IsRequired(); - - b.HasKey("Id"); - - b.HasIndex("UserId"); + e.Property("Id"); + e.Property("Name"); + e.HasIndex("Name"); + }), + builder => builder.Entity("People").Property("Name").IsRequired(), + builder => builder.Entity("People").Property("Name").IsRequired(false), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns, c => c.Name == "Name"); + Assert.True(column.IsNullable); + }); - b.ToTable("AspNetUserClaims"); - }); + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'Name'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NULL;"); + } - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + [ConditionalFact] + public virtual async Task Alter_column_with_index_included_column() + { + await Test( + builder => builder.Entity( + "People", e => { - b.Property("LoginProvider") - .HasMaxLength(128); - - b.Property("ProviderKey") - .HasMaxLength(128); + e.Property("Id"); + e.Property("Name"); + e.Property("FirstName"); + e.Property("LastName"); + e.HasIndex("FirstName", "LastName").IncludeProperties("Name"); + }), + builder => { }, + builder => builder.Entity("People").Property("Name").HasMaxLength(30), + model => + { + var table = Assert.Single(model.Tables); + var index = Assert.Single(table.Indexes); + // TODO: This is a scaffolding bug, #19351 + Assert.Equal(3, index.Columns.Count); + Assert.Contains(table.Columns.Single(c => c.Name == "Name"), index.Columns); + Assert.Contains(table.Columns.Single(c => c.Name == "FirstName"), index.Columns); + Assert.Contains(table.Columns.Single(c => c.Name == "LastName"), index.Columns); + }); - b.Property("ProviderDisplayName"); + AssertSql( + @"DROP INDEX [IX_People_FirstName_LastName] ON [People]; +DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'Name'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(30) NULL; +CREATE INDEX [IX_People_FirstName_LastName] ON [People] ([FirstName], [LastName]) INCLUDE ([Name]);"); + } - b.Property("UserId") - .IsRequired(); + [ConditionalFact] + public virtual async Task Alter_column_add_identity() + { + var ex = await TestThrows( + builder => builder.Entity("People").Property("SomeColumn"), + builder => builder.Entity("People").Property("SomeColumn").UseIdentityColumn()); - b.HasKey("LoginProvider", "ProviderKey"); + Assert.Equal(SqlServerStrings.AlterIdentityColumn, ex.Message); + } - b.HasIndex("UserId"); + [ConditionalFact] + public virtual async Task Alter_column_remove_identity() + { + var ex = await TestThrows( + builder => builder.Entity("People").Property("SomeColumn").UseIdentityColumn(), + builder => builder.Entity("People").Property("SomeColumn")); - b.ToTable("AspNetUserLogins"); - }); + Assert.Equal(SqlServerStrings.AlterIdentityColumn, ex.Message); + } - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserRole", b => + [ConditionalFact] + public virtual async Task Alter_column_change_type_with_identity() + { + await Test( + builder => builder.Entity( + "People", e => { - b.Property("UserId"); - - b.Property("RoleId"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserToken", b => + e.Property("Id"); + e.Property("IdentityColumn").UseIdentityColumn(); + }), + builder => builder.Entity( + "People", e => { - b.Property("UserId"); - - b.Property("LoginProvider") - .HasMaxLength(128); - - b.Property("Name") - .HasMaxLength(128); - - b.Property("Value"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); + e.Property("Id"); + e.Property("IdentityColumn").UseIdentityColumn(); + }), + model => + { + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns, c => c.Name == "IdentityColumn"); + Assert.Equal("bigint", column.StoreType); + Assert.Equal(ValueGenerated.OnAdd, column.ValueGenerated); + }); - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'IdentityColumn'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [IdentityColumn] bigint NOT NULL;"); } - public override void Can_diff_against_2_2_ASP_NET_Identity_model() + public override async Task Drop_column() { - using var context = new ApplicationDbContext(); - DiffSnapshot(new AspNetIdentity22ModelSnapshot(), context); + await base.Drop_column(); + + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'SomeColumn'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] DROP COLUMN [SomeColumn];"); } - public class AspNetIdentity22ModelSnapshot : ModelSnapshot + public override async Task Drop_column_primary_key() { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.0-preview1") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken(); - - b.Property("Name") - .HasMaxLength(256); - - b.Property("NormalizedName") - .HasMaxLength(256); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClaimType"); - - b.Property("ClaimValue"); - - b.Property("RoleId") - .IsRequired(); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("AccessFailedCount"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken(); - - b.Property("Email") - .HasMaxLength(256); - - b.Property("EmailConfirmed"); - - b.Property("LockoutEnabled"); - - b.Property("LockoutEnd"); - - b.Property("NormalizedEmail") - .HasMaxLength(256); - - b.Property("NormalizedUserName") - .HasMaxLength(256); + await base.Drop_column_primary_key(); + + AssertSql( + @"ALTER TABLE [People] DROP CONSTRAINT [PK_People];", + // + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'Id'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] DROP COLUMN [Id];"); + } - b.Property("PasswordHash"); + public override async Task Rename_column() + { + await base.Rename_column(); - b.Property("PhoneNumber"); + AssertSql( + @"EXEC sp_rename N'[People].[SomeColumn]', N'somecolumn', N'COLUMN';"); + } - b.Property("PhoneNumberConfirmed"); + public override async Task Create_index() + { + await base.Create_index(); + + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'FirstName'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [FirstName] nvarchar(450) NULL;", + // + @"CREATE INDEX [IX_People_FirstName] ON [People] ([FirstName]);"); + } - b.Property("SecurityStamp"); + public override async Task Create_index_unique() + { + await base.Create_index_unique(); + + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'LastName'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [LastName] nvarchar(450) NULL;", + // + @"DECLARE @var1 sysname; +SELECT @var1 = [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'[People]') AND [c].[name] = N'FirstName'); +IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var1 + '];'); +ALTER TABLE [People] ALTER COLUMN [FirstName] nvarchar(450) NULL;", + // + @"CREATE UNIQUE INDEX [IX_People_FirstName_LastName] ON [People] ([FirstName], [LastName]);"); + } - b.Property("TwoFactorEnabled"); + public override async Task Create_index_with_filter() + { + await base.Create_index_with_filter(); + + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'Name'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NULL;", + // + @"CREATE INDEX [IX_People_Name] ON [People] ([Name]) WHERE [Name] IS NOT NULL;"); + } - b.Property("UserName") - .HasMaxLength(256); + public override async Task Create_unique_index_with_filter() + { + await base.Create_unique_index_with_filter(); + + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'Name'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NULL;", + // + @"CREATE UNIQUE INDEX [IX_People_Name] ON [People] ([Name]) WHERE [Name] IS NOT NULL AND [Name] <> '';"); + } - b.HasKey("Id"); + [ConditionalFact] + public virtual async Task Create_index_clustered() + { + await Test( + builder => builder.Entity("People").Property("FirstName"), + builder => { }, + builder => builder.Entity("People").HasIndex("FirstName").IsClustered(), + model => + { + var table = Assert.Single(model.Tables); + var index = Assert.Single(table.Indexes); + Assert.True((bool)index[SqlServerAnnotationNames.Clustered]); + Assert.False(index.IsUnique); + }); - b.HasIndex("NormalizedEmail") - .HasName("EmailIndex"); + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'FirstName'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [FirstName] nvarchar(450) NULL;", + // + @"CREATE CLUSTERED INDEX [IX_People_FirstName] ON [People] ([FirstName]);"); + } - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); + [ConditionalFact] + public virtual async Task Create_index_unique_clustered() + { + await Test( + builder => builder.Entity("People").Property("FirstName"), + builder => { }, + builder => builder.Entity("People").HasIndex("FirstName") + .IsUnique() + .IsClustered(), + model => + { + var table = Assert.Single(model.Tables); + var index = Assert.Single(table.Indexes); + Assert.True((bool)index[SqlServerAnnotationNames.Clustered]); + Assert.True(index.IsUnique); + }); - b.ToTable("AspNetUsers"); - }); + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'FirstName'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [FirstName] nvarchar(450) NULL;", + // + @"CREATE UNIQUE CLUSTERED INDEX [IX_People_FirstName] ON [People] ([FirstName]);"); + } - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + [ConditionalFact] + public virtual async Task Create_index_with_include() + { + await Test( + builder => builder.Entity( + "People", e => { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClaimType"); - - b.Property("ClaimValue"); - - b.Property("UserId") - .IsRequired(); - - b.HasKey("Id"); - - b.HasIndex("UserId"); + e.Property("Id"); + e.Property("FirstName"); + e.Property("LastName"); + e.Property("Name"); + }), + builder => { }, + builder => builder.Entity("People").HasIndex("Name") + .IncludeProperties("FirstName", "LastName"), + model => + { + var table = Assert.Single(model.Tables); + var index = Assert.Single(table.Indexes); + // TODO: This is a scaffolding bug, #19351 + Assert.Equal(3, index.Columns.Count); + Assert.Contains(table.Columns.Single(c => c.Name == "Name"), index.Columns); + Assert.Contains(table.Columns.Single(c => c.Name == "FirstName"), index.Columns); + Assert.Contains(table.Columns.Single(c => c.Name == "LastName"), index.Columns); + }); - b.ToTable("AspNetUserClaims"); - }); + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'Name'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NULL;", + // + @"CREATE INDEX [IX_People_Name] ON [People] ([Name]) INCLUDE ([FirstName], [LastName]);"); + } - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + [ConditionalFact] + public virtual async Task Create_index_with_include_and_filter() + { + await Test( + builder => builder.Entity( + "People", e => { - b.Property("LoginProvider") - .HasMaxLength(128); - - b.Property("ProviderKey") - .HasMaxLength(128); - - b.Property("ProviderDisplayName"); - - b.Property("UserId") - .IsRequired(); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); + e.Property("Id"); + e.Property("FirstName"); + e.Property("LastName"); + e.Property("Name"); + }), + builder => { }, + builder => builder.Entity("People").HasIndex("Name") + .IncludeProperties("FirstName", "LastName") + .HasFilter("[Name] IS NOT NULL"), + model => + { + var table = Assert.Single(model.Tables); + var index = Assert.Single(table.Indexes); + Assert.Equal("([Name] IS NOT NULL)", index.Filter); + // TODO: This is a scaffolding bug, #19351 + Assert.Equal(3, index.Columns.Count); + Assert.Contains(table.Columns.Single(c => c.Name == "Name"), index.Columns); + Assert.Contains(table.Columns.Single(c => c.Name == "FirstName"), index.Columns); + Assert.Contains(table.Columns.Single(c => c.Name == "LastName"), index.Columns); + }); - b.ToTable("AspNetUserLogins"); - }); + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'Name'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NULL;", + // + @"CREATE INDEX [IX_People_Name] ON [People] ([Name]) INCLUDE ([FirstName], [LastName]) WHERE [Name] IS NOT NULL;"); + } - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserRole", b => + [ConditionalFact] + public virtual async Task Create_index_unique_with_include() + { + await Test( + builder => builder.Entity( + "People", e => { - b.Property("UserId"); - - b.Property("RoleId"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); + e.Property("Id"); + e.Property("FirstName"); + e.Property("LastName"); + e.Property("Name").IsRequired(); + }), + builder => { }, + builder => builder.Entity("People").HasIndex("Name") + .IsUnique() + .IncludeProperties("FirstName", "LastName"), + model => + { + var table = Assert.Single(model.Tables); + var index = Assert.Single(table.Indexes); + Assert.True(index.IsUnique); + // TODO: This is a scaffolding bug, #19351 + Assert.Equal(3, index.Columns.Count); + Assert.Contains(table.Columns.Single(c => c.Name == "Name"), index.Columns); + Assert.Contains(table.Columns.Single(c => c.Name == "FirstName"), index.Columns); + Assert.Contains(table.Columns.Single(c => c.Name == "LastName"), index.Columns); + }); - b.ToTable("AspNetUserRoles"); - }); + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'Name'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NOT NULL;", + // + @"CREATE UNIQUE INDEX [IX_People_Name] ON [People] ([Name]) INCLUDE ([FirstName], [LastName]);"); + } - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserToken", b => + [ConditionalFact] + public virtual async Task Create_index_unique_with_include_and_filter() + { + await Test( + builder => builder.Entity( + "People", e => { - b.Property("UserId"); - - b.Property("LoginProvider") - .HasMaxLength(128); - - b.Property("Name") - .HasMaxLength(128); - - b.Property("Value"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); + e.Property("Id"); + e.Property("FirstName"); + e.Property("LastName"); + e.Property("Name").IsRequired(); + }), + builder => { }, + builder => builder.Entity("People").HasIndex("Name") + .IsUnique() + .IncludeProperties("FirstName", "LastName") + .HasFilter("[Name] IS NOT NULL"), + model => + { + var table = Assert.Single(model.Tables); + var index = Assert.Single(table.Indexes); + Assert.True(index.IsUnique); + Assert.Equal("([Name] IS NOT NULL)", index.Filter); + // TODO: This is a scaffolding bug, #19351 + Assert.Equal(3, index.Columns.Count); + Assert.Contains(table.Columns.Single(c => c.Name == "Name"), index.Columns); + Assert.Contains(table.Columns.Single(c => c.Name == "FirstName"), index.Columns); + Assert.Contains(table.Columns.Single(c => c.Name == "LastName"), index.Columns); + }); - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'Name'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NOT NULL;", + // + @"CREATE UNIQUE INDEX [IX_People_Name] ON [People] ([Name]) INCLUDE ([FirstName], [LastName]) WHERE [Name] IS NOT NULL;"); } - public override void Can_diff_against_3_0_ASP_NET_Identity_model() + [ConditionalFact(Skip = "#19668, Online index operations can only be performed in Enterprise edition of SQL Server")] + [SqlServerCondition(SqlServerCondition.SupportsOnlineIndexes)] + public virtual async Task Create_index_unique_with_include_and_filter_online() { - using var context = new ApplicationDbContext(); - DiffSnapshot(new AspNetIdentity30ModelSnapshot(), context); + await Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("FirstName"); + e.Property("LastName"); + e.Property("Name").IsRequired(); + }), + builder => { }, + builder => builder.Entity("People").HasIndex("Name") + .IsUnique() + .IncludeProperties("FirstName", "LastName") + .HasFilter("[Name] IS NOT NULL") + .IsCreatedOnline(), + model => + { + var table = Assert.Single(model.Tables); + var index = Assert.Single(table.Indexes); + Assert.True(index.IsUnique); + Assert.Equal("([Name] IS NOT NULL)", index.Filter); + // TODO: This is a scaffolding bug, #17083 + Assert.Equal(3, index.Columns.Count); + Assert.Contains(table.Columns.Single(c => c.Name == "Name"), index.Columns); + Assert.Contains(table.Columns.Single(c => c.Name == "FirstName"), index.Columns); + Assert.Contains(table.Columns.Single(c => c.Name == "LastName"), index.Columns); + // TODO: Online index not scaffolded? + }); + + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'Name'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NOT NULL;", + // + @"CREATE UNIQUE INDEX [IX_People_Name] ON [People] ([Name]) INCLUDE ([FirstName], [LastName]) WHERE [Name] IS NOT NULL WITH (ONLINE = ON);"); } - public class AspNetIdentity30ModelSnapshot : ModelSnapshot + [ConditionalFact] + [SqlServerCondition(SqlServerCondition.SupportsMemoryOptimized)] + public virtual async Task Create_index_memoryOptimized_unique_nullable() { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityRole", b => + await Test( + builder => builder.Entity( + "People", e => { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.Property("NormalizedName") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); + e.Property("Id"); + e.Property("Name"); + e.IsMemoryOptimized().HasKey("Id").IsClustered(false); + }), + builder => { }, + builder => builder.Entity("People").HasIndex("Name").IsUnique(), + model => + { + var table = Assert.Single(model.Tables); + var index = Assert.Single(table.Indexes); + Assert.Same(table.Columns.Single(c => c.Name == "Name"), Assert.Single(index.Columns)); + Assert.False(index.IsUnique); + }); - b.ToTable("AspNetRoles"); - }); + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'Name'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NULL;", + // + @"ALTER TABLE [People] ADD INDEX [IX_People_Name] ([Name]);"); + } - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + [ConditionalFact] + [SqlServerCondition(SqlServerCondition.SupportsMemoryOptimized)] + public virtual async Task Create_index_memoryOptimized_unique_nullable_with_filter() + { + await Test( + builder => builder.Entity( + "People", e => { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); + e.Property("Id"); + e.Property("Name"); + e.IsMemoryOptimized().HasKey("Id").IsClustered(false); + }), + builder => { }, + builder => builder.Entity("People").HasIndex("Name").IsUnique().HasFilter("[Name] IS NOT NULL AND <> ''"), + model => + { + var table = Assert.Single(model.Tables); + var index = Assert.Single(table.Indexes); + Assert.Same(table.Columns.Single(c => c.Name == "Name"), Assert.Single(index.Columns)); + Assert.False(index.IsUnique); + Assert.Null(index.Filter); + }); - b.ToTable("AspNetRoleClaims"); - }); + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'Name'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NULL;", + // + @"ALTER TABLE [People] ADD INDEX [IX_People_Name] ([Name]);"); + } - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUser", b => + [ConditionalFact] + [SqlServerCondition(SqlServerCondition.SupportsMemoryOptimized)] + public virtual async Task Create_index_memoryOptimized_unique_nonclustered_not_nullable() + { + await Test( + builder => builder.Entity( + "People", e => { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); + e.Property("Name").IsRequired(); + e.IsMemoryOptimized().HasKey("Name").IsClustered(false); + }), + builder => { }, + builder => builder.Entity("People").HasIndex("Name").IsUnique().IsClustered(false), + model => + { + var table = Assert.Single(model.Tables); + var index = Assert.Single(table.Indexes); + Assert.Same(table.Columns.Single(c => c.Name == "Name"), Assert.Single(index.Columns)); + Assert.True(index.IsUnique); + }); - b.Property("Email") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); + AssertSql( + @"ALTER TABLE [People] ADD INDEX [IX_People_Name] UNIQUE NONCLUSTERED ([Name]);"); + } - b.Property("EmailConfirmed") - .HasColumnType("bit"); + public override async Task Drop_index() + { + await base.Drop_index(); - b.Property("LockoutEnabled") - .HasColumnType("bit"); + AssertSql( + @"DROP INDEX [IX_People_SomeField] ON [People];"); + } - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); + public override async Task Rename_index() + { + await base.Rename_index(); - b.Property("NormalizedEmail") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); + AssertSql( + @"EXEC sp_rename N'[People].[Foo]', N'foo', N'INDEX';"); + } - b.Property("NormalizedUserName") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); + public override async Task Add_primary_key() + { + await base.Add_primary_key(); + + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'SomeField'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [SomeField] nvarchar(450) NOT NULL;", + // + @"ALTER TABLE [People] ADD CONSTRAINT [PK_People] PRIMARY KEY ([SomeField]);"); + } - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); + public override async Task Add_primary_key_with_name() + { + await base.Add_primary_key_with_name(); + + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'SomeField'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [SomeField] nvarchar(450) NOT NULL;", + // + @"ALTER TABLE [People] ADD CONSTRAINT [PK_Foo] PRIMARY KEY ([SomeField]);"); + } - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); + public override async Task Add_primary_key_composite_with_name() + { + await base.Add_primary_key_composite_with_name(); + + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'SomeField2'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [SomeField2] nvarchar(450) NOT NULL;", + // + @"DECLARE @var1 sysname; +SELECT @var1 = [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'[People]') AND [c].[name] = N'SomeField1'); +IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var1 + '];'); +ALTER TABLE [People] ALTER COLUMN [SomeField1] nvarchar(450) NOT NULL;", + // + @"ALTER TABLE [People] ADD CONSTRAINT [PK_Foo] PRIMARY KEY ([SomeField1], [SomeField2]);"); + } - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); + [ConditionalFact] + public virtual async Task Add_primary_key_nonclustered() + { + await Test( + builder => builder.Entity("People").Property("SomeField"), + builder => { }, + builder => builder.Entity("People").HasKey("SomeField").IsClustered(false), + model => + { + var table = Assert.Single(model.Tables); + var primaryKey = table.PrimaryKey; + Assert.NotNull(primaryKey); + Assert.False((bool)primaryKey![SqlServerAnnotationNames.Clustered]); + }); - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'SomeField'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [SomeField] nvarchar(450) NOT NULL;", + // + @"ALTER TABLE [People] ADD CONSTRAINT [PK_People] PRIMARY KEY NONCLUSTERED ([SomeField]);"); + } - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); + public override async Task Drop_primary_key() + { + await base.Drop_primary_key(); - b.Property("UserName") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); + AssertSql( + @"ALTER TABLE [People] DROP CONSTRAINT [PK_People];"); + } - b.HasKey("Id"); + public override async Task Add_foreign_key() + { + await base.Add_foreign_key(); - b.HasIndex("NormalizedEmail") - .HasName("EmailIndex"); + AssertSql( + @"ALTER TABLE [Orders] ADD CONSTRAINT [FK_Orders_Customers_CustomerId] FOREIGN KEY ([CustomerId]) REFERENCES [Customers] ([Id]) ON DELETE NO ACTION;"); + } - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); + public override async Task Add_foreign_key_with_name() + { + await base.Add_foreign_key_with_name(); - b.ToTable("AspNetUsers"); - }); + AssertSql( + @"ALTER TABLE [Orders] ADD CONSTRAINT [FK_Foo] FOREIGN KEY ([CustomerId]) REFERENCES [Customers] ([Id]) ON DELETE NO ACTION;"); + } - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + public override async Task Drop_foreign_key() + { + await base.Drop_foreign_key(); - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); + AssertSql( + @"ALTER TABLE [Orders] DROP CONSTRAINT [FK_Orders_Customers_CustomerId];"); + } - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); + public override async Task Add_unique_constraint() + { + await base.Add_unique_constraint(); - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); + AssertSql( + @"ALTER TABLE [People] ADD CONSTRAINT [AK_People_AlternateKeyColumn] UNIQUE ([AlternateKeyColumn]);"); + } - b.HasKey("Id"); + public override async Task Add_unique_constraint_composite_with_name() + { + await base.Add_unique_constraint_composite_with_name(); - b.HasIndex("UserId"); + AssertSql( + @"ALTER TABLE [People] ADD CONSTRAINT [AK_Foo] UNIQUE ([AlternateKeyColumn1], [AlternateKeyColumn2]);"); + } - b.ToTable("AspNetUserClaims"); - }); + public override async Task Drop_unique_constraint() + { + await base.Drop_unique_constraint(); - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); + AssertSql( + @"ALTER TABLE [People] DROP CONSTRAINT [AK_People_AlternateKeyColumn];"); + } - b.Property("ProviderKey") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); + public override async Task Add_check_constraint_with_name() + { + await base.Add_check_constraint_with_name(); - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); + AssertSql( + @"ALTER TABLE [People] ADD CONSTRAINT [CK_Foo] CHECK ([DriverLicense] > 0);"); + } - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); + public override async Task Drop_check_constraint() + { + await base.Drop_check_constraint(); - b.HasKey("LoginProvider", "ProviderKey"); + AssertSql( + @"ALTER TABLE [People] DROP CONSTRAINT [CK_Foo];"); + } - b.HasIndex("UserId"); + public override async Task Create_sequence() + { + await base.Create_sequence(); - b.ToTable("AspNetUserLogins"); - }); + AssertSql( + @"CREATE SEQUENCE [TestSequence] AS int START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE;"); + } - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); + public override async Task Create_sequence_all_settings() + { + await base.Create_sequence_all_settings(); - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); + AssertSql( + @"IF SCHEMA_ID(N'dbo2') IS NULL EXEC(N'CREATE SCHEMA [dbo2];');", + // + @"CREATE SEQUENCE [dbo2].[TestSequence] START WITH 3 INCREMENT BY 2 MINVALUE 2 MAXVALUE 916 CYCLE;"); + } - b.HasKey("UserId", "RoleId"); + public override async Task Alter_sequence_all_settings() + { + await base.Alter_sequence_all_settings(); - b.HasIndex("RoleId"); + AssertSql( + @"ALTER SEQUENCE [foo] INCREMENT BY 2 MINVALUE -5 MAXVALUE 10 CYCLE;", + // + @"ALTER SEQUENCE [foo] RESTART WITH -3;"); + } - b.ToTable("AspNetUserRoles"); - }); + public override async Task Alter_sequence_increment_by() + { + await base.Alter_sequence_increment_by(); - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); + AssertSql( + @"ALTER SEQUENCE [foo] INCREMENT BY 2 NO MINVALUE NO MAXVALUE NO CYCLE;"); + } - b.Property("LoginProvider") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); + public override async Task Drop_sequence() + { + await base.Drop_sequence(); - b.Property("Name") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); + AssertSql( + @"DROP SEQUENCE [TestSequence];"); + } - b.Property("Value") - .HasColumnType("nvarchar(max)"); + public override async Task Rename_sequence() + { + await base.Rename_sequence(); - b.HasKey("UserId", "LoginProvider", "Name"); + AssertSql( + @"EXEC sp_rename N'[TestSequence]', N'testsequence';"); + } - b.ToTable("AspNetUserTokens"); - }); + public override async Task Move_sequence() + { + await base.Move_sequence(); - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } + AssertSql( + @"IF SCHEMA_ID(N'TestSequenceSchema') IS NULL EXEC(N'CREATE SCHEMA [TestSequenceSchema];');", + // + @"ALTER SCHEMA [TestSequenceSchema] TRANSFER [TestSequence];"); } - } -} - -namespace ModelSnapshot22 -{ - public class Blog - { - public int Id { get; set; } - public string Name { get; set; } - public ICollection Posts { get; set; } - } + [ConditionalFact] + public virtual async Task Move_sequence_into_default_schema() + { + await Test( + builder => builder.HasSequence("TestSequence", "TestSequenceSchema"), + builder => builder.HasSequence("TestSequence"), + model => + { + var sequence = Assert.Single(model.Sequences); + Assert.Equal("dbo", sequence.Schema); + Assert.Equal("TestSequence", sequence.Name); + }); - public class Post - { - public int Id { get; set; } - public string Title { get; set; } - public string Content { get; set; } - public DateTime EditDate { get; set; } + AssertSql( + @"DECLARE @defaultSchema sysname = SCHEMA_NAME(); +EXEC(N'ALTER SCHEMA [' + @defaultSchema + N'] TRANSFER [TestSequenceSchema].[TestSequence];');"); + } - public Blog Blog { get; set; } - } + public override async Task InsertDataOperation() + { + await base.InsertDataOperation(); + + AssertSql( + @"IF EXISTS (SELECT * FROM [sys].[identity_columns] WHERE [name] IN (N'Id', N'Name') AND [object_id] = OBJECT_ID(N'[Person]')) + SET IDENTITY_INSERT [Person] ON; +INSERT INTO [Person] ([Id], [Name]) +VALUES (1, N'Daenerys Targaryen'), +(2, N'John Snow'), +(3, N'Arya Stark'), +(4, N'Harry Strickland'), +(5, NULL); +IF EXISTS (SELECT * FROM [sys].[identity_columns] WHERE [name] IN (N'Id', N'Name') AND [object_id] = OBJECT_ID(N'[Person]')) + SET IDENTITY_INSERT [Person] OFF;"); + } - public class BloggingContext : DbContext - { - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0"); + public override async Task DeleteDataOperation_simple_key() + { + await base.DeleteDataOperation_simple_key(); - public DbSet Blogs { get; set; } + // TODO remove rowcount + AssertSql( + @"DELETE FROM [Person] +WHERE [Id] = 2; +SELECT @@ROWCOUNT;"); + } - protected override void OnModelCreating(ModelBuilder modelBuilder) + public override async Task DeleteDataOperation_composite_key() { - modelBuilder.Entity().HasData( - new Blog { Id = 1, Name = "HalfADonkey" }); - } - } -} + await base.DeleteDataOperation_composite_key(); -namespace Identity30.Data -{ - public class ApplicationDbContext : IdentityDbContext - { - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0"); + // TODO remove rowcount + AssertSql( + @"DELETE FROM [Person] +WHERE [Id] = 2 AND [AnotherId] = 12; +SELECT @@ROWCOUNT;"); + } - protected override void OnModelCreating(ModelBuilder builder) + public override async Task UpdateDataOperation_simple_key() { - base.OnModelCreating(builder); + await base.UpdateDataOperation_simple_key(); - builder.Entity( - b => - { - b.HasIndex(u => u.NormalizedUserName).HasName("UserNameIndex").IsUnique(); - b.HasIndex(u => u.NormalizedEmail).HasName("EmailIndex"); - b.ToTable("AspNetUsers"); - }); + // TODO remove rowcount + AssertSql( + @"UPDATE [Person] SET [Name] = N'Another John Snow' +WHERE [Id] = 2; +SELECT @@ROWCOUNT;"); + } - builder.Entity>( - b => - { - b.ToTable("AspNetUserClaims"); - }); + public override async Task UpdateDataOperation_composite_key() + { + await base.UpdateDataOperation_composite_key(); - builder.Entity>( - b => - { - b.ToTable("AspNetUserLogins"); - }); + // TODO remove rowcount + AssertSql( + @"UPDATE [Person] SET [Name] = N'Another John Snow' +WHERE [Id] = 2 AND [AnotherId] = 11; +SELECT @@ROWCOUNT;"); + } - builder.Entity>( - b => - { - b.ToTable("AspNetUserTokens"); - }); + public override async Task UpdateDataOperation_multiple_columns() + { + await base.UpdateDataOperation_multiple_columns(); - builder.Entity( - b => - { - b.HasIndex(r => r.NormalizedName).HasName("RoleNameIndex").IsUnique(); - b.ToTable("AspNetRoles"); - }); + // TODO remove rowcount + AssertSql( + @"UPDATE [Person] SET [Age] = 21, [Name] = N'Another John Snow' +WHERE [Id] = 2; +SELECT @@ROWCOUNT;"); + } - builder.Entity>( - b => - { - b.ToTable("AspNetRoleClaims"); - }); + public class MigrationsSqlServerFixture : MigrationsFixtureBase + { + protected override string StoreName { get; } = nameof(MigrationsSqlServerTest); + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; + public override TestHelpers TestHelpers => SqlServerTestHelpers.Instance; - builder.Entity>( - b => - { - b.ToTable("AspNetUserRoles"); - }); + protected override IServiceCollection AddServices(IServiceCollection serviceCollection) + => base.AddServices(serviceCollection) + .AddScoped(); } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerMigrationSqlGeneratorTest.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerMigrationSqlGeneratorTest.cs deleted file mode 100644 index 66efc57d565..00000000000 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerMigrationSqlGeneratorTest.cs +++ /dev/null @@ -1,2073 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Migrations.Operations; -using Microsoft.EntityFrameworkCore.SqlServer.Internal; -using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; -using Microsoft.EntityFrameworkCore.TestUtilities; -using Xunit; - -// ReSharper disable InconsistentNaming -namespace Microsoft.EntityFrameworkCore -{ - public class SqlServerMigrationSqlGeneratorTest : MigrationSqlGeneratorTestBase - { - public override void CreateTableOperation() - { - base.CreateTableOperation(); - - AssertSql( - @"CREATE TABLE [dbo].[People] ( - [Id] int NOT NULL, - [EmployerId] int NULL, - [SSN] char(11) NULL, - PRIMARY KEY ([Id]), - UNIQUE ([SSN]), - CHECK (SSN > 0), - FOREIGN KEY ([EmployerId]) REFERENCES [Companies] ([Id]) -); -EXEC sp_addextendedproperty 'MS_Description', N'Table comment', 'SCHEMA', N'dbo', 'TABLE', N'People'; -EXEC sp_addextendedproperty 'MS_Description', N'Employer ID comment', 'SCHEMA', N'dbo', 'TABLE', N'People', 'COLUMN', N'EmployerId'; -"); - } - - [ConditionalFact] - public void CreateTableOperation_default_schema_with_comments() - { - Generate( - new CreateTableOperation - { - Name = "People", - Columns = - { - new AddColumnOperation - { - Name = "Id", - Table = "People", - ClrType = typeof(int), - IsNullable = false, - Comment = "ID comment" - }, - new AddColumnOperation - { - Name = "Name", - Table = "People", - ClrType = typeof(string), - IsNullable = false, - Comment = "Name comment" - } - }, - Comment = "Table comment" - }); - - AssertSql( - @"CREATE TABLE [People] ( - [Id] int NOT NULL, - [Name] nvarchar(max) NOT NULL -); -DECLARE @defaultSchema AS sysname; -SET @defaultSchema = SCHEMA_NAME(); -EXEC sp_addextendedproperty 'MS_Description', N'Table comment', 'SCHEMA', @defaultSchema, 'TABLE', N'People'; -EXEC sp_addextendedproperty 'MS_Description', N'ID comment', 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'Id'; -EXEC sp_addextendedproperty 'MS_Description', N'Name comment', 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'Name'; -"); - } - - [ConditionalFact] - public void CreateTableOperation_default_schema_with_column_comments() - { - Generate( - new CreateTableOperation - { - Name = "People", - Columns = - { - new AddColumnOperation - { - Name = "Id", - Table = "People", - ClrType = typeof(int), - IsNullable = false, - Comment = "ID comment" - }, - new AddColumnOperation - { - Name = "Name", - Table = "People", - ClrType = typeof(string), - IsNullable = false, - Comment = "Name comment" - } - } - }); - - AssertSql( - @"CREATE TABLE [People] ( - [Id] int NOT NULL, - [Name] nvarchar(max) NOT NULL -); -DECLARE @defaultSchema AS sysname; -SET @defaultSchema = SCHEMA_NAME(); -EXEC sp_addextendedproperty 'MS_Description', N'ID comment', 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'Id'; -EXEC sp_addextendedproperty 'MS_Description', N'Name comment', 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'Name'; -"); - } - - public override void CreateTableOperation_no_key() - { - base.CreateTableOperation_no_key(); - - AssertSql( - @"CREATE TABLE [Anonymous] ( - [Value] int NOT NULL -); -"); - } - - public override void CreateIndexOperation_with_filter_where_clause() - { - base.CreateIndexOperation_with_filter_where_clause(); - - AssertSql( - @"CREATE INDEX [IX_People_Name] ON [People] ([Name]) WHERE [Name] IS NOT NULL; -"); - } - - public override void CreateIndexOperation_with_filter_where_clause_and_is_unique() - { - base.CreateIndexOperation_with_filter_where_clause_and_is_unique(); - - AssertSql( - @"CREATE UNIQUE INDEX [IX_People_Name] ON [People] ([Name]) WHERE [Name] IS NOT NULL AND <> ''; -"); - } - - [ConditionalFact] - public void AlterTableOperation_with_new_comment() - { - Generate( - new AlterTableOperation - { - Name = "People", - Schema = "dbo", - Comment = "My Comment" - }); - - AssertSql( - @"EXEC sp_addextendedproperty 'MS_Description', N'My Comment', 'SCHEMA', N'dbo', 'TABLE', N'People'; -"); - } - - [ConditionalFact] - public void AlterTableOperation_with_different_comment_to_existing() - { - Generate( - modelBuilder => modelBuilder - .HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0") - .Entity( - "Person", x => - { - x.HasComment("My Comment"); - }), - new AlterTableOperation - { - Schema = "dbo", - Name = "People", - Comment = "My Comment 2", - OldTable = new TableOperation { Comment = "My Comment" } - }); - - AssertSql( - @"EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', N'dbo', 'TABLE', N'People'; -EXEC sp_addextendedproperty 'MS_Description', N'My Comment 2', 'SCHEMA', N'dbo', 'TABLE', N'People'; -"); - } - - [ConditionalFact] - public void AlterTableOperation_removing_comment() - { - Generate( - modelBuilder => modelBuilder - .HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0") - .Entity( - "Person", x => - { - x.HasComment("My Comment"); - }), - new AlterTableOperation - { - Schema = "dbo", - Name = "People", - OldTable = new TableOperation { Comment = "My Comment" } - }); - - AssertSql( - @"EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', N'dbo', 'TABLE', N'People'; -"); - } - - [ConditionalFact] - public virtual void AddColumnOperation_with_computedSql() - { - Generate( - new AddColumnOperation - { - Table = "People", - Name = "FullName", - ClrType = typeof(string), - ComputedColumnSql = "FirstName + ' ' + LastName" - }); - - AssertSql( - @"ALTER TABLE [People] ADD [FullName] AS FirstName + ' ' + LastName; -"); - } - - [ConditionalFact] - public void AddColumnOperation_with_computed_column_SQL() - { - Generate( - new AddColumnOperation - { - Table = "People", - Name = "Birthday", - ClrType = typeof(DateTime), - ColumnType = "date", - IsNullable = true, - ComputedColumnSql = "CURRENT_TIMESTAMP" - }); - - AssertSql( - @"ALTER TABLE [People] ADD [Birthday] AS CURRENT_TIMESTAMP; -"); - } - - [ConditionalFact] - public virtual void AddColumnOperation_identity() - { - Generate( - new AddColumnOperation - { - Table = "People", - Name = "Id", - ClrType = typeof(int), - ColumnType = "int", - DefaultValue = 0, - IsNullable = false, - [SqlServerAnnotationNames.Identity] = "1, 1" - }); - - AssertSql( - @"ALTER TABLE [People] ADD [Id] int NOT NULL IDENTITY; -"); - } - - [ConditionalFact] - public virtual void AddColumnOperation_identity_legacy() - { - Generate( - new AddColumnOperation - { - Table = "People", - Name = "Id", - ClrType = typeof(int), - ColumnType = "int", - DefaultValue = 0, - IsNullable = false, - [SqlServerAnnotationNames.ValueGenerationStrategy] = - SqlServerValueGenerationStrategy.IdentityColumn - }); - - AssertSql( - @"ALTER TABLE [People] ADD [Id] int NOT NULL IDENTITY; -"); - } - - [ConditionalFact] - public virtual void AddColumnOperation_identity_seed_increment() - { - Generate( - new AddColumnOperation - { - Table = "People", - Name = "Id", - ClrType = typeof(int), - ColumnType = "int", - DefaultValue = 0, - IsNullable = false, - [SqlServerAnnotationNames.Identity] = "100,5" - }); - - AssertSql( - @"ALTER TABLE [People] ADD [Id] int NOT NULL IDENTITY(100,5); -"); - } - - public override void AddColumnOperation_without_column_type() - { - base.AddColumnOperation_without_column_type(); - - AssertSql( - @"ALTER TABLE [People] ADD [Alias] nvarchar(max) NOT NULL; -"); - } - - public override void AddColumnOperation_with_unicode_no_model() - { - base.AddColumnOperation_with_unicode_no_model(); - - AssertSql( - @"ALTER TABLE [Person] ADD [Name] varchar(max) NULL; -"); - } - - public override void AddColumnOperation_with_maxLength() - { - base.AddColumnOperation_with_maxLength(); - - AssertSql( - @"ALTER TABLE [Person] ADD [Name] nvarchar(30) NULL; -"); - } - - [ConditionalFact] - public virtual void AddColumnOperation_datetime_with_defaultValue() - { - Generate( - new AddColumnOperation - { - Table = "People", - Schema = "dbo", - Name = "Birthday", - ClrType = typeof(DateTime), - ColumnType = "datetime", - IsNullable = false, - DefaultValue = new DateTime(2019, 1, 1) - }); - - AssertSql( - @"ALTER TABLE [dbo].[People] ADD [Birthday] datetime NOT NULL DEFAULT '2019-01-01T00:00:00.000'; -"); - } - - [ConditionalFact] - public virtual void AddColumnOperation_smalldatetime_with_defaultValue() - { - Generate( - new AddColumnOperation - { - Table = "People", - Schema = "dbo", - Name = "Birthday", - ClrType = typeof(DateTime), - ColumnType = "smalldatetime", - IsNullable = false, - DefaultValue = new DateTime(2019, 1, 1) - }); - - AssertSql( - @"ALTER TABLE [dbo].[People] ADD [Birthday] smalldatetime NOT NULL DEFAULT '2019-01-01T00:00:00'; -"); - } - - public override void AddColumnOperation_with_maxLength_overridden() - { - base.AddColumnOperation_with_maxLength_overridden(); - - AssertSql( - @"ALTER TABLE [Person] ADD [Name] nvarchar(32) NULL; -"); - } - - public override void AddColumnOperation_with_maxLength_on_derived() - { - base.AddColumnOperation_with_maxLength_on_derived(); - - AssertSql( - @"ALTER TABLE [Person] ADD [Name] nvarchar(30) NULL; -"); - } - - public override void AddColumnOperation_with_ansi() - { - base.AddColumnOperation_with_ansi(); - - AssertSql( - @"ALTER TABLE [Person] ADD [Name] varchar(max) NULL; -"); - } - - public override void AddColumnOperation_with_unicode_overridden() - { - base.AddColumnOperation_with_unicode_overridden(); - - AssertSql( - @"ALTER TABLE [Person] ADD [Name] nvarchar(max) NULL; -"); - } - - public override void AddColumnOperation_with_shared_column() - { - base.AddColumnOperation_with_shared_column(); - - AssertSql( - @"ALTER TABLE [Base] ADD [Foo] nvarchar(max) NULL; -"); - } - - [ConditionalFact] - public virtual void AddColumnOperation_with_rowversion_overridden() - { - Generate( - modelBuilder => modelBuilder.Entity("Person").Property("RowVersion"), - new AddColumnOperation - { - Table = "Person", - Name = "RowVersion", - ClrType = typeof(byte[]), - IsRowVersion = true, - IsNullable = true - }); - - AssertSql( - @"ALTER TABLE [Person] ADD [RowVersion] rowversion NULL; -"); - } - - [ConditionalFact] - public virtual void AddColumnOperation_with_rowversion_no_model() - { - Generate( - new AddColumnOperation - { - Table = "Person", - Name = "RowVersion", - ClrType = typeof(byte[]), - IsRowVersion = true, - IsNullable = true - }); - - AssertSql( - @"ALTER TABLE [Person] ADD [RowVersion] rowversion NULL; -"); - } - - [ConditionalFact] - public virtual void AddColumnOperation_with_comment() - { - Generate( - new AddColumnOperation - { - Table = "People", - Name = "FullName", - ClrType = typeof(string), - Comment = "My comment" - }); - - AssertSql( - @"ALTER TABLE [People] ADD [FullName] nvarchar(max) NOT NULL; -DECLARE @defaultSchema AS sysname; -SET @defaultSchema = SCHEMA_NAME(); -EXEC sp_addextendedproperty 'MS_Description', N'My comment', 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'FullName'; -"); - } - - [ConditionalFact] - public virtual void AddColumnOperation_with_comment_non_default_schema() - { - Generate( - new AddColumnOperation - { - Schema = "my", - Table = "People", - Name = "FullName", - ClrType = typeof(string), - Comment = "My comment" - }); - - AssertSql( - @"ALTER TABLE [my].[People] ADD [FullName] nvarchar(max) NOT NULL; -EXEC sp_addextendedproperty 'MS_Description', N'My comment', 'SCHEMA', N'my', 'TABLE', N'People', 'COLUMN', N'FullName'; -"); - } - - [ConditionalFact] - public virtual void AddPrimaryKeyOperation_nonclustered() - { - Generate( - new AddPrimaryKeyOperation - { - Table = "People", - Columns = new[] { "Id" }, - [SqlServerAnnotationNames.Clustered] = false - }); - - AssertSql( - @"ALTER TABLE [People] ADD PRIMARY KEY NONCLUSTERED ([Id]); -"); - } - - public override void AlterColumnOperation() - { - base.AlterColumnOperation(); - - AssertSql( - @"DECLARE @var0 sysname; -SELECT @var0 = [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'[dbo].[People]') AND [c].[name] = N'LuckyNumber'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [dbo].[People] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [dbo].[People] ALTER COLUMN [LuckyNumber] int NOT NULL; -ALTER TABLE [dbo].[People] ADD DEFAULT 7 FOR [LuckyNumber]; -"); - } - - public override void AlterColumnOperation_without_column_type() - { - base.AlterColumnOperation_without_column_type(); - - AssertSql( - @"DECLARE @var0 sysname; -SELECT @var0 = [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'[People]') AND [c].[name] = N'LuckyNumber'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [People] ALTER COLUMN [LuckyNumber] int NOT NULL; -"); - } - - [ConditionalFact] - public virtual void AlterColumnOperation_with_identity() - { - Generate( - new AlterColumnOperation - { - Table = "People", - Name = "Id", - ClrType = typeof(int), - [SqlServerAnnotationNames.Identity] = "1, 1" - }); - - AssertSql( - @"DECLARE @var0 sysname; -SELECT @var0 = [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'[People]') AND [c].[name] = N'Id'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [People] ALTER COLUMN [Id] int NOT NULL; -"); - } - - [ConditionalFact] - public virtual void AlterColumnOperation_with_identity_legacy() - { - Generate( - new AlterColumnOperation - { - Table = "People", - Name = "Id", - ClrType = typeof(int), - [SqlServerAnnotationNames.ValueGenerationStrategy] = - SqlServerValueGenerationStrategy.IdentityColumn - }); - - AssertSql( - @"DECLARE @var0 sysname; -SELECT @var0 = [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'[People]') AND [c].[name] = N'Id'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [People] ALTER COLUMN [Id] int NOT NULL; -"); - } - - [ConditionalFact] - public void AlterColumnOperation_computed() - { - Generate( - new AlterColumnOperation - { - Table = "People", - Name = "FullName", - ClrType = typeof(string), - ComputedColumnSql = "[FirstName] + ' ' + [LastName]" - }); - - AssertSql( - @"DECLARE @var0 sysname; -SELECT @var0 = [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'[People]') AND [c].[name] = N'FullName'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [People] DROP COLUMN [FullName]; -ALTER TABLE [People] ADD [FullName] AS [FirstName] + ' ' + [LastName]; -"); - } - - [ConditionalFact] - public virtual void AlterColumnOperation_computed_with_index() - { - Generate( - modelBuilder => modelBuilder - .HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0") - .Entity( - "Person", x => - { - x.Property("FullName").HasComputedColumnSql("[FirstName] + ' ' + [LastName]"); - x.HasKey("FullName"); - x.HasIndex("FullName"); - }), - new AlterColumnOperation - { - Table = "Person", - Name = "FullName", - ClrType = typeof(string), - ComputedColumnSql = "[FirstName] + ' ' + [LastName]", - OldColumn = new ColumnOperation { ClrType = typeof(string), ComputedColumnSql = "[LastName] + ', ' + [FirstName]" } - }); - - AssertSql( - @"DROP INDEX [IX_Person_FullName] ON [Person]; -DECLARE @var0 sysname; -SELECT @var0 = [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'[Person]') AND [c].[name] = N'FullName'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [Person] DROP COLUMN [FullName]; -ALTER TABLE [Person] ADD [FullName] AS [FirstName] + ' ' + [LastName]; -CREATE INDEX [IX_Person_FullName] ON [Person] ([FullName]); -"); - } - - [ConditionalFact] - public virtual void AlterColumnOperation_memoryOptimized_with_index() - { - Generate( - modelBuilder => modelBuilder - .HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0") - .Entity( - "Person", x => - { - x.IsMemoryOptimized(); - x.Property("Name"); - x.HasKey("Name"); - x.HasIndex("Name"); - }), - new AlterColumnOperation - { - Table = "Person", - Name = "Name", - ClrType = typeof(string), - MaxLength = 30, - OldColumn = new ColumnOperation { ClrType = typeof(string) } - }); - - AssertSql( - @"ALTER TABLE [Person] DROP INDEX [IX_Person_Name]; -DECLARE @var0 sysname; -SELECT @var0 = [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'[Person]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [Person] ALTER COLUMN [Name] nvarchar(30) NOT NULL; -ALTER TABLE [Person] ADD INDEX [IX_Person_Name] NONCLUSTERED ([Name]); -"); - } - - [ConditionalFact] - public virtual void AlterColumnOperation_with_index_no_narrowing() - { - Generate( - modelBuilder => modelBuilder - .HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0") - .Entity( - "Person", x => - { - x.Property("Name"); - x.HasKey("Name"); - x.HasIndex("Name"); - }), - new AlterColumnOperation - { - Table = "Person", - Name = "Name", - ClrType = typeof(string), - IsNullable = true, - OldColumn = new ColumnOperation { ClrType = typeof(string), IsNullable = false } - }); - - AssertSql( - @"DECLARE @var0 sysname; -SELECT @var0 = [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'[Person]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [Person] ALTER COLUMN [Name] nvarchar(450) NULL; -"); - } - - [ConditionalFact] - public virtual void AlterColumnOperation_with_index() - { - Generate( - modelBuilder => modelBuilder - .HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0") - .Entity( - "Person", x => - { - x.Property("Name").HasMaxLength(30); - x.HasKey("Name"); - x.HasIndex("Name"); - }), - new AlterColumnOperation - { - Table = "Person", - Name = "Name", - ClrType = typeof(string), - MaxLength = 30, - IsNullable = true, - OldColumn = new ColumnOperation { ClrType = typeof(string), IsNullable = true } - }); - - AssertSql( - @"DROP INDEX [IX_Person_Name] ON [Person]; -DECLARE @var0 sysname; -SELECT @var0 = [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'[Person]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [Person] ALTER COLUMN [Name] nvarchar(30) NULL; -CREATE INDEX [IX_Person_Name] ON [Person] ([Name]); -"); - } - - [ConditionalFact] - public virtual void AlterColumnOperation_with_index_included_column() - { - Generate( - modelBuilder => modelBuilder - .HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0") - .Entity( - "Person", x => - { - x.Property("Name").HasMaxLength(30); - x.Property("FirstName"); - x.Property("LastName"); - x.HasKey("Name"); - x.HasIndex("FirstName", "LastName").IncludeProperties("Name"); - }), - new AlterColumnOperation - { - Table = "Person", - Name = "Name", - ClrType = typeof(string), - MaxLength = 30, - IsNullable = true, - OldColumn = new ColumnOperation { ClrType = typeof(string), IsNullable = true } - }); - - AssertSql( - @"DROP INDEX [IX_Person_FirstName_LastName] ON [Person]; -DECLARE @var0 sysname; -SELECT @var0 = [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'[Person]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [Person] ALTER COLUMN [Name] nvarchar(30) NULL; -CREATE INDEX [IX_Person_FirstName_LastName] ON [Person] ([FirstName], [LastName]) INCLUDE ([Name]); -"); - } - - [ConditionalFact] - public virtual void AlterColumnOperation_with_index_no_included() - { - Generate( - modelBuilder => modelBuilder - .HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0") - .Entity( - "Person", x => - { - x.Property("Name").HasMaxLength(30); - x.Property("FirstName"); - x.Property("LastName"); - x.HasIndex("FirstName", "LastName"); - }), - new AlterColumnOperation - { - Table = "Person", - Name = "Name", - ClrType = typeof(string), - MaxLength = 30, - IsNullable = true, - OldColumn = new ColumnOperation { ClrType = typeof(string), IsNullable = true } - }); - - AssertSql( - @"DECLARE @var0 sysname; -SELECT @var0 = [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'[Person]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [Person] ALTER COLUMN [Name] nvarchar(30) NULL; -"); - } - - [ConditionalFact] - public virtual void AlterColumnOperation_with_index_no_oldColumn() - { - Generate( - modelBuilder => modelBuilder - .HasAnnotation(CoreAnnotationNames.ProductVersion, "1.0.0-rtm") - .Entity( - "Person", x => - { - x.Property("Name").HasMaxLength(30); - x.HasIndex("Name"); - }), - new AlterColumnOperation - { - Table = "Person", - Name = "Name", - ClrType = typeof(string), - MaxLength = 30, - IsNullable = true, - OldColumn = new ColumnOperation() - }); - - AssertSql( - @"DECLARE @var0 sysname; -SELECT @var0 = [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'[Person]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [Person] ALTER COLUMN [Name] nvarchar(30) NULL; -"); - } - - [ConditionalFact] - public virtual void AlterColumnOperation_with_composite_index() - { - Generate( - modelBuilder => modelBuilder - .HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0") - .Entity( - "Person", x => - { - x.Property("FirstName").IsRequired(); - x.Property("LastName"); - x.HasKey("LastName"); - x.HasIndex("FirstName", "LastName"); - }), - new AlterColumnOperation - { - Table = "Person", - Name = "FirstName", - ClrType = typeof(string), - IsNullable = false, - OldColumn = new ColumnOperation { ClrType = typeof(string), IsNullable = true } - }); - - AssertSql( - @"DROP INDEX [IX_Person_FirstName_LastName] ON [Person]; -DECLARE @var0 sysname; -SELECT @var0 = [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'[Person]') AND [c].[name] = N'FirstName'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [Person] ALTER COLUMN [FirstName] nvarchar(450) NOT NULL; -CREATE INDEX [IX_Person_FirstName_LastName] ON [Person] ([FirstName], [LastName]); -"); - } - - [ConditionalFact] - public virtual void AlterColumnOperation_with_added_index() - { - Generate( - modelBuilder => modelBuilder - .HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0") - .Entity( - "Person", x => - { - x.Property("Name").HasMaxLength(30); - x.HasIndex("Name"); - }), - new AlterColumnOperation - { - Table = "Person", - Name = "Name", - ClrType = typeof(string), - MaxLength = 30, - IsNullable = true, - OldColumn = new ColumnOperation { ClrType = typeof(string), IsNullable = true } - }, - new CreateIndexOperation - { - Name = "IX_Person_Name", - Table = "Person", - Columns = new[] { "Name" } - }); - - AssertSql( - @"DECLARE @var0 sysname; -SELECT @var0 = [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'[Person]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [Person] ALTER COLUMN [Name] nvarchar(30) NULL; -GO - -CREATE INDEX [IX_Person_Name] ON [Person] ([Name]); -"); - } - - [ConditionalFact] - public virtual void AlterColumnOperation_with_added_online_index() - { - Generate( - modelBuilder => modelBuilder - .HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0") - .Entity( - "Person", x => - { - x.Property("Name").HasMaxLength(30); - x.HasIndex("Name").IsCreatedOnline(); - }), - new AlterColumnOperation - { - Table = "Person", - Name = "Name", - ClrType = typeof(string), - MaxLength = 30, - IsNullable = true, - OldColumn = new ColumnOperation { ClrType = typeof(string), IsNullable = true } - }, - new CreateIndexOperation - { - Name = "IX_Person_Name", - Table = "Person", - Columns = new[] { "Name" }, - [SqlServerAnnotationNames.CreatedOnline] = true - }); - - AssertSql( - @"DECLARE @var0 sysname; -SELECT @var0 = [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'[Person]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [Person] ALTER COLUMN [Name] nvarchar(30) NULL; -GO - -CREATE INDEX [IX_Person_Name] ON [Person] ([Name]) WITH (ONLINE = ON); -"); - } - - [ConditionalFact] - public virtual void AlterColumnOperation_identity() - { - Generate( - modelBuilder => modelBuilder.HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0"), - new AlterColumnOperation - { - Table = "Person", - Name = "Id", - ClrType = typeof(long), - [SqlServerAnnotationNames.Identity] = "1, 1", - OldColumn = new ColumnOperation { ClrType = typeof(int), [SqlServerAnnotationNames.Identity] = "1, 1" } - }); - - AssertSql( - @"DECLARE @var0 sysname; -SELECT @var0 = [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'[Person]') AND [c].[name] = N'Id'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [Person] ALTER COLUMN [Id] bigint NOT NULL; -"); - } - - [ConditionalFact] - public virtual void AlterColumnOperation_identity_legacy() - { - Generate( - modelBuilder => modelBuilder.HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0"), - new AlterColumnOperation - { - Table = "Person", - Name = "Id", - ClrType = typeof(long), - [SqlServerAnnotationNames.ValueGenerationStrategy] = SqlServerValueGenerationStrategy.IdentityColumn, - OldColumn = new ColumnOperation - { - ClrType = typeof(int), - [SqlServerAnnotationNames.ValueGenerationStrategy] = SqlServerValueGenerationStrategy.IdentityColumn - } - }); - - AssertSql( - @"DECLARE @var0 sysname; -SELECT @var0 = [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'[Person]') AND [c].[name] = N'Id'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [Person] ALTER COLUMN [Id] bigint NOT NULL; -"); - } - - [ConditionalFact] - public virtual void AlterColumnOperation_add_identity() - { - var ex = Assert.Throws( - () => Generate( - modelBuilder => modelBuilder.HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0"), - new AlterColumnOperation - { - Table = "Person", - Name = "Id", - ClrType = typeof(int), - [SqlServerAnnotationNames.Identity] = "1, 1", - OldColumn = new ColumnOperation { ClrType = typeof(int) } - })); - - Assert.Equal(SqlServerStrings.AlterIdentityColumn, ex.Message); - } - - [ConditionalFact] - public virtual void AlterColumnOperation_add_identity_legacy() - { - var ex = Assert.Throws( - () => Generate( - modelBuilder => modelBuilder.HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0"), - new AlterColumnOperation - { - Table = "Person", - Name = "Id", - ClrType = typeof(int), - [SqlServerAnnotationNames.ValueGenerationStrategy] = SqlServerValueGenerationStrategy.IdentityColumn, - OldColumn = new ColumnOperation { ClrType = typeof(int) } - })); - - Assert.Equal(SqlServerStrings.AlterIdentityColumn, ex.Message); - } - - [ConditionalFact] - public virtual void AlterColumnOperation_remove_identity() - { - var ex = Assert.Throws( - () => Generate( - modelBuilder => modelBuilder.HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0"), - new AlterColumnOperation - { - Table = "Person", - Name = "Id", - ClrType = typeof(int), - OldColumn = new ColumnOperation { ClrType = typeof(int), [SqlServerAnnotationNames.Identity] = "1, 1" } - })); - - Assert.Equal(SqlServerStrings.AlterIdentityColumn, ex.Message); - } - - [ConditionalFact] - public virtual void AlterColumnOperation_remove_identity_legacy() - { - var ex = Assert.Throws( - () => Generate( - modelBuilder => modelBuilder.HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0"), - new AlterColumnOperation - { - Table = "Person", - Name = "Id", - ClrType = typeof(int), - OldColumn = new ColumnOperation - { - ClrType = typeof(int), - [SqlServerAnnotationNames.ValueGenerationStrategy] = SqlServerValueGenerationStrategy.IdentityColumn - } - })); - - Assert.Equal(SqlServerStrings.AlterIdentityColumn, ex.Message); - } - - [ConditionalFact] - public void AlterColumnOperation_with_new_comment() - { - Generate( - new AlterColumnOperation - { - Table = "People", - Schema = "dbo", - Name = "LuckyNumber", - ClrType = typeof(int), - ColumnType = "int", - IsNullable = false, - Comment = "My Comment" - }); - - AssertSql( - @"DECLARE @var0 sysname; -SELECT @var0 = [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'[dbo].[People]') AND [c].[name] = N'LuckyNumber'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [dbo].[People] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [dbo].[People] ALTER COLUMN [LuckyNumber] int NOT NULL; -EXEC sp_addextendedproperty 'MS_Description', N'My Comment', 'SCHEMA', N'dbo', 'TABLE', N'People', 'COLUMN', N'LuckyNumber'; -"); - } - - [ConditionalFact] - public void AlterColumnOperation_with_different_comment_to_existing() - { - Generate( - modelBuilder => modelBuilder - .HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0") - .Entity( - "Person", x => - { - x.Property("Name").HasComment("My Comment"); - }), - new AlterColumnOperation - { - Table = "People", - Schema = "dbo", - Name = "Name", - ClrType = typeof(string), - IsNullable = false, - Comment = "My Comment 2", - OldColumn = new ColumnOperation - { - ClrType = typeof(string), - IsNullable = true, - Comment = "My Comment" - } - }); - - AssertSql( - @"DECLARE @var0 sysname; -SELECT @var0 = [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'[dbo].[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [dbo].[People] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [dbo].[People] ALTER COLUMN [Name] nvarchar(max) NOT NULL; -EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', N'dbo', 'TABLE', N'People', 'COLUMN', N'Name'; -EXEC sp_addextendedproperty 'MS_Description', N'My Comment 2', 'SCHEMA', N'dbo', 'TABLE', N'People', 'COLUMN', N'Name'; -"); - } - - [ConditionalFact] - public void AlterColumnOperation_removing_comment() - { - Generate( - modelBuilder => modelBuilder - .HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0") - .Entity( - "Person", x => - { - x.Property("Name").HasComment("My Comment"); - }), - new AlterColumnOperation - { - Table = "People", - Schema = "dbo", - Name = "Name", - ClrType = typeof(string), - IsNullable = false, - OldColumn = new ColumnOperation - { - ClrType = typeof(string), - IsNullable = true, - Comment = "My Comment" - } - }); - - AssertSql( - @"DECLARE @var0 sysname; -SELECT @var0 = [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'[dbo].[People]') AND [c].[name] = N'Name'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [dbo].[People] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [dbo].[People] ALTER COLUMN [Name] nvarchar(max) NOT NULL; -EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', N'dbo', 'TABLE', N'People', 'COLUMN', N'Name'; -"); - } - - [ConditionalFact] - public virtual void CreateDatabaseOperation() - { - Generate( - new SqlServerCreateDatabaseOperation { Name = "Northwind" }); - - AssertSql( - @"CREATE DATABASE [Northwind]; -GO - -IF SERVERPROPERTY('EngineEdition') <> 5 -BEGIN - ALTER DATABASE [Northwind] SET READ_COMMITTED_SNAPSHOT ON; -END; -"); - } - - [ConditionalFact] - public virtual void CreateDatabaseOperation_with_filename() - { - Generate( - new SqlServerCreateDatabaseOperation { Name = "Northwind", FileName = "Narf.mdf" }); - - var expectedFile = Path.GetFullPath("Narf.mdf"); - var expectedLog = Path.GetFullPath("Narf_log.ldf"); - - AssertSql( - $@"CREATE DATABASE [Northwind] -ON (NAME = N'Narf', FILENAME = N'{expectedFile}') -LOG ON (NAME = N'Narf_log', FILENAME = N'{expectedLog}'); -GO - -IF SERVERPROPERTY('EngineEdition') <> 5 -BEGIN - ALTER DATABASE [Northwind] SET READ_COMMITTED_SNAPSHOT ON; -END; -"); - } - - [ConditionalFact] - public virtual void CreateDatabaseOperation_with_filename_and_datadirectory() - { - var baseDirectory = AppDomain.CurrentDomain.BaseDirectory; - - Generate( - new SqlServerCreateDatabaseOperation { Name = "Northwind", FileName = "|DataDirectory|Narf.mdf" }); - - var expectedFile = Path.Combine(baseDirectory, "Narf.mdf"); - var expectedLog = Path.Combine(baseDirectory, "Narf_log.ldf"); - - AssertSql( - $@"CREATE DATABASE [Northwind] -ON (NAME = N'Narf', FILENAME = N'{expectedFile}') -LOG ON (NAME = N'Narf_log', FILENAME = N'{expectedLog}'); -GO - -IF SERVERPROPERTY('EngineEdition') <> 5 -BEGIN - ALTER DATABASE [Northwind] SET READ_COMMITTED_SNAPSHOT ON; -END; -"); - } - - [ConditionalFact] - public virtual void CreateDatabaseOperation_with_filename_and_custom_datadirectory() - { - var dataDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data"); - - AppDomain.CurrentDomain.SetData("DataDirectory", dataDirectory); - - Generate( - new SqlServerCreateDatabaseOperation { Name = "Northwind", FileName = "|DataDirectory|Narf.mdf" }); - - AppDomain.CurrentDomain.SetData("DataDirectory", null); - - var expectedFile = Path.Combine(dataDirectory, "Narf.mdf"); - var expectedLog = Path.Combine(dataDirectory, "Narf_log.ldf"); - - AssertSql( - $@"CREATE DATABASE [Northwind] -ON (NAME = N'Narf', FILENAME = N'{expectedFile}') -LOG ON (NAME = N'Narf_log', FILENAME = N'{expectedLog}'); -GO - -IF SERVERPROPERTY('EngineEdition') <> 5 -BEGIN - ALTER DATABASE [Northwind] SET READ_COMMITTED_SNAPSHOT ON; -END; -"); - } - - [ConditionalFact] - public virtual void AlterDatabaseOperation_memory_optimized() - { - Generate( - new AlterDatabaseOperation { [SqlServerAnnotationNames.MemoryOptimized] = true }); - - Assert.Contains( - "CONTAINS MEMORY_OPTIMIZED_DATA;", - Sql); - } - - public override void CreateIndexOperation_nonunique() - { - base.CreateIndexOperation_nonunique(); - - AssertSql( - @"CREATE INDEX [IX_People_Name] ON [People] ([Name]); -"); - } - - public override void CreateIndexOperation_unique() - { - base.CreateIndexOperation_unique(); - - AssertSql( - @"CREATE UNIQUE INDEX [IX_People_Name] ON [dbo].[People] ([FirstName], [LastName]) WHERE [FirstName] IS NOT NULL AND [LastName] IS NOT NULL; -"); - } - - [ConditionalFact] - public virtual void CreateIndexOperation_unique_non_legacy() - { - Generate( - modelBuilder => modelBuilder.HasAnnotation(CoreAnnotationNames.ProductVersion, "2.0.0"), - new CreateIndexOperation - { - Name = "IX_People_Name", - Table = "People", - Schema = "dbo", - Columns = new[] { "FirstName", "LastName" }, - IsUnique = true - }); - - AssertSql( - @"CREATE UNIQUE INDEX [IX_People_Name] ON [dbo].[People] ([FirstName], [LastName]); -"); - } - - [ConditionalFact] - public virtual void CreateIndexOperation_clustered() - { - Generate( - new CreateIndexOperation - { - Name = "IX_People_Name", - Table = "People", - Columns = new[] { "Name" }, - [SqlServerAnnotationNames.Clustered] = true - }); - - AssertSql( - @"CREATE CLUSTERED INDEX [IX_People_Name] ON [People] ([Name]); -"); - } - - [ConditionalFact] - public virtual void CreateIndexOperation_unique_clustered() - { - Generate( - new CreateIndexOperation - { - Name = "IX_People_Name", - Table = "People", - Columns = new[] { "Name" }, - IsUnique = true, - [SqlServerAnnotationNames.Clustered] = true - }); - - AssertSql( - @"CREATE UNIQUE CLUSTERED INDEX [IX_People_Name] ON [People] ([Name]); -"); - } - - [ConditionalFact] - public virtual void CreateIndexOperation_with_include() - { - Generate( - new CreateIndexOperation - { - Name = "IX_People_Name", - Table = "People", - Columns = new[] { "Name" }, - [SqlServerAnnotationNames.Include] = new[] { "FirstName", "LastName" } - }); - - AssertSql( - @"CREATE INDEX [IX_People_Name] ON [People] ([Name]) INCLUDE ([FirstName], [LastName]); -"); - } - - [ConditionalFact] - public virtual void CreateIndexOperation_with_include_and_filter() - { - Generate( - new CreateIndexOperation - { - Name = "IX_People_Name", - Table = "People", - Columns = new[] { "Name" }, - Filter = "[Name] IS NOT NULL AND <> ''", - [SqlServerAnnotationNames.Include] = new[] { "FirstName", "LastName" } - }); - - AssertSql( - @"CREATE INDEX [IX_People_Name] ON [People] ([Name]) INCLUDE ([FirstName], [LastName]) WHERE [Name] IS NOT NULL AND <> ''; -"); - } - - [ConditionalFact] - public virtual void CreateIndexOperation_unique_with_include() - { - Generate( - new CreateIndexOperation - { - Name = "IX_People_Name", - Table = "People", - Columns = new[] { "Name" }, - IsUnique = true, - [SqlServerAnnotationNames.Include] = new[] { "FirstName", "LastName" } - }); - - AssertSql( - @"CREATE UNIQUE INDEX [IX_People_Name] ON [People] ([Name]) INCLUDE ([FirstName], [LastName]) WHERE [Name] IS NOT NULL; -"); - } - - [ConditionalFact] - public virtual void CreateIndexOperation_unique_with_include_and_filter() - { - Generate( - new CreateIndexOperation - { - Name = "IX_People_Name", - Table = "People", - Columns = new[] { "Name" }, - IsUnique = true, - Filter = "[Name] IS NOT NULL AND <> ''", - [SqlServerAnnotationNames.Include] = new[] { "FirstName", "LastName" } - }); - - AssertSql( - @"CREATE UNIQUE INDEX [IX_People_Name] ON [People] ([Name]) INCLUDE ([FirstName], [LastName]) WHERE [Name] IS NOT NULL AND <> ''; -"); - } - - [ConditionalFact] - public virtual void CreateIndexOperation_unique_with_include_and_filter_online() - { - Generate( - new CreateIndexOperation - { - Name = "IX_People_Name", - Table = "People", - Columns = new[] { "Name" }, - IsUnique = true, - Filter = "[Name] IS NOT NULL AND <> ''", - [SqlServerAnnotationNames.Include] = new[] { "FirstName", "LastName" }, - [SqlServerAnnotationNames.CreatedOnline] = true - }); - - AssertSql( - @"CREATE UNIQUE INDEX [IX_People_Name] ON [People] ([Name]) INCLUDE ([FirstName], [LastName]) WHERE [Name] IS NOT NULL AND <> '' WITH (ONLINE = ON); -"); - } - - [ConditionalFact] - public virtual void CreateIndexOperation_unique_with_include_non_legacy() - { - Generate( - modelBuilder => modelBuilder.HasAnnotation(CoreAnnotationNames.ProductVersion, "2.0.0"), - new CreateIndexOperation - { - Name = "IX_People_Name", - Table = "People", - Columns = new[] { "Name" }, - IsUnique = true, - [SqlServerAnnotationNames.Include] = new[] { "FirstName", "LastName" } - }); - - AssertSql( - @"CREATE UNIQUE INDEX [IX_People_Name] ON [People] ([Name]) INCLUDE ([FirstName], [LastName]); -"); - } - - [ConditionalFact] - public virtual void CreateIndexOperation_unique_bound_null() - { - Generate( - modelBuilder => modelBuilder.Entity("People").Property("Name"), - new CreateIndexOperation - { - Name = "IX_People_Name", - Table = "People", - Columns = new[] { "Name" }, - IsUnique = true - }); - - AssertSql( - @"CREATE UNIQUE INDEX [IX_People_Name] ON [People] ([Name]) WHERE [Name] IS NOT NULL; -"); - } - - [ConditionalFact] - public virtual void CreateIndexOperation_unique_bound_not_null() - { - Generate( - modelBuilder => modelBuilder.Entity( - "People", x => - { - x.Property("Name").IsRequired(); - x.HasKey("Name"); - }), - new CreateIndexOperation - { - Name = "IX_People_Name", - Table = "People", - Columns = new[] { "Name" }, - IsUnique = true - }); - - AssertSql( - @"CREATE UNIQUE INDEX [IX_People_Name] ON [People] ([Name]); -"); - } - - [ConditionalFact] - public virtual void CreateIndexOperation_memoryOptimized_unique_nullable() - { - Generate( - modelBuilder => modelBuilder.Entity( - "People", x => - { - x.ToTable("People", "dbo").IsMemoryOptimized().Property("Name"); - x.Property("Id"); - x.HasKey("Id"); - }), - new CreateIndexOperation - { - Name = "IX_People_Name", - Schema = "dbo", - Table = "People", - Columns = new[] { "Name" }, - IsUnique = true - }); - - AssertSql( - @"ALTER TABLE [dbo].[People] ADD INDEX [IX_People_Name] ([Name]); -"); - } - - [ConditionalFact] - public virtual void CreateIndexOperation_memoryOptimized_unique_nullable_with_filter() - { - Generate( - modelBuilder => modelBuilder.Entity( - "People", x => - { - x.IsMemoryOptimized().Property("Name"); - x.Property("Id"); - x.HasKey("Id"); - }), - new CreateIndexOperation - { - Name = "IX_People_Name", - Table = "People", - Columns = new[] { "Name" }, - IsUnique = true, - Filter = "[Name] IS NOT NULL AND <> ''" - }); - - AssertSql( - @"ALTER TABLE [People] ADD INDEX [IX_People_Name] ([Name]); -"); - } - - [ConditionalFact] - public virtual void CreateIndexOperation_memoryOptimized_unique_nonclustered_not_nullable() - { - Generate( - modelBuilder => modelBuilder.Entity( - "People", x => - { - x.IsMemoryOptimized().Property("Name").IsRequired(); - x.HasKey("Name"); - }), - new CreateIndexOperation - { - Name = "IX_People_Name", - Table = "People", - Columns = new[] { "Name" }, - IsUnique = true, - [SqlServerAnnotationNames.Clustered] = false - }); - - AssertSql( - @"ALTER TABLE [People] ADD INDEX [IX_People_Name] UNIQUE NONCLUSTERED ([Name]); -"); - } - - [ConditionalFact] - public virtual void CreateSchemaOperation() - { - Generate( - new EnsureSchemaOperation { Name = "my" }); - - AssertSql( - @"IF SCHEMA_ID(N'my') IS NULL EXEC(N'CREATE SCHEMA [my];'); -"); - } - - [ConditionalFact] - public virtual void CreateSchemaOperation_dbo() - { - Generate( - new EnsureSchemaOperation { Name = "dbo" }); - - AssertSql(""); - } - - public override void DropColumnOperation() - { - base.DropColumnOperation(); - - AssertSql( - @"DECLARE @var0 sysname; -SELECT @var0 = [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'[dbo].[People]') AND [c].[name] = N'LuckyNumber'); -IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [dbo].[People] DROP CONSTRAINT [' + @var0 + '];'); -ALTER TABLE [dbo].[People] DROP COLUMN [LuckyNumber]; -"); - } - - [ConditionalFact] - public virtual void DropDatabaseOperation() - { - Generate( - new SqlServerDropDatabaseOperation { Name = "Northwind" }); - - AssertSql( - @"IF SERVERPROPERTY('EngineEdition') <> 5 -BEGIN - ALTER DATABASE [Northwind] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; -END; -GO - -DROP DATABASE [Northwind]; -"); - } - - public override void DropIndexOperation() - { - base.DropIndexOperation(); - - AssertSql( - @"DROP INDEX [IX_People_Name] ON [dbo].[People]; -"); - } - - [ConditionalFact] - public virtual void DropIndexOperation_memoryOptimized() - { - Generate( - modelBuilder => modelBuilder.Entity( - "People", x => - { - x.IsMemoryOptimized(); - x.Property("Id"); - x.HasKey("Id"); - }), - new DropIndexOperation { Name = "IX_People_Name", Table = "People" }); - - AssertSql( - @"ALTER TABLE [People] DROP INDEX [IX_People_Name]; -"); - } - - [ConditionalFact] - public virtual void MoveSequenceOperation_legacy() - { - Generate( - new RenameSequenceOperation - { - Name = "EntityFrameworkHiLoSequence", - Schema = "dbo", - NewSchema = "my" - }); - - AssertSql( - @"ALTER SCHEMA [my] TRANSFER [dbo].[EntityFrameworkHiLoSequence]; -"); - } - - [ConditionalFact] - public virtual void MoveSequenceOperation() - { - Generate( - modelBuilder => modelBuilder.HasAnnotation(CoreAnnotationNames.ProductVersion, "2.1.0"), - new RenameSequenceOperation - { - Name = "EntityFrameworkHiLoSequence", - Schema = "dbo", - NewName = "EntityFrameworkHiLoSequence", - NewSchema = "my" - }); - - AssertSql( - @"ALTER SCHEMA [my] TRANSFER [dbo].[EntityFrameworkHiLoSequence]; -"); - } - - [ConditionalFact] - public virtual void MoveSequenceOperation_into_default() - { - Generate( - modelBuilder => modelBuilder.HasAnnotation(CoreAnnotationNames.ProductVersion, "2.1.0"), - new RenameSequenceOperation - { - Name = "EntityFrameworkHiLoSequence", - Schema = "dbo", - NewName = "EntityFrameworkHiLoSequence" - }); - - AssertSql( - @"DECLARE @defaultSchema sysname = SCHEMA_NAME(); -EXEC(N'ALTER SCHEMA [' + @defaultSchema + N'] TRANSFER [dbo].[EntityFrameworkHiLoSequence];'); -"); - } - - [ConditionalFact] - public virtual void MoveTableOperation_legacy() - { - Generate( - new RenameTableOperation - { - Name = "People", - Schema = "dbo", - NewSchema = "hr" - }); - - AssertSql( - @"ALTER SCHEMA [hr] TRANSFER [dbo].[People]; -"); - } - - [ConditionalFact] - public virtual void MoveTableOperation() - { - Generate( - modelBuilder => modelBuilder.HasAnnotation(CoreAnnotationNames.ProductVersion, "2.1.0"), - new RenameTableOperation - { - Name = "People", - Schema = "dbo", - NewName = "People", - NewSchema = "hr" - }); - - AssertSql( - @"ALTER SCHEMA [hr] TRANSFER [dbo].[People]; -"); - } - - [ConditionalFact] - public virtual void MoveTableOperation_into_default() - { - Generate( - modelBuilder => modelBuilder.HasAnnotation(CoreAnnotationNames.ProductVersion, "2.1.0"), - new RenameTableOperation - { - Name = "People", - Schema = "dbo", - NewName = "People" - }); - - AssertSql( - @"DECLARE @defaultSchema sysname = SCHEMA_NAME(); -EXEC(N'ALTER SCHEMA [' + @defaultSchema + N'] TRANSFER [dbo].[People];'); -"); - } - - [ConditionalFact] - public virtual void RenameColumnOperation() - { - Generate( - new RenameColumnOperation - { - Table = "People", - Schema = "dbo", - Name = "Name", - NewName = "FullName" - }); - - AssertSql( - @"EXEC sp_rename N'[dbo].[People].[Name]', N'FullName', N'COLUMN'; -"); - } - - [ConditionalFact] - public virtual void RenameIndexOperation() - { - Generate( - new RenameIndexOperation - { - Table = "People", - Schema = "dbo", - Name = "IX_People_Name", - NewName = "IX_People_FullName" - }); - - AssertSql( - @"EXEC sp_rename N'[dbo].[People].[IX_People_Name]', N'IX_People_FullName', N'INDEX'; -"); - } - - [ConditionalFact] - public virtual void RenameIndexOperations_throws_when_no_table() - { - var migrationBuilder = new MigrationBuilder("SqlServer"); - - migrationBuilder.RenameIndex( - name: "IX_OldIndex", - newName: "IX_NewIndex"); - - var ex = Assert.Throws( - () => Generate(migrationBuilder.Operations.ToArray())); - - Assert.Equal(SqlServerStrings.IndexTableRequired, ex.Message); - } - - [ConditionalFact] - public virtual void RenameSequenceOperation_legacy() - { - Generate( - new RenameSequenceOperation - { - Name = "EntityFrameworkHiLoSequence", - Schema = "dbo", - NewName = "MySequence" - }); - - AssertSql( - @"EXEC sp_rename N'[dbo].[EntityFrameworkHiLoSequence]', N'MySequence'; -"); - } - - [ConditionalFact] - public virtual void RenameSequenceOperation() - { - Generate( - modelBuilder => modelBuilder.HasAnnotation(CoreAnnotationNames.ProductVersion, "2.1.0"), - new RenameSequenceOperation - { - Name = "EntityFrameworkHiLoSequence", - Schema = "dbo", - NewName = "MySequence", - NewSchema = "dbo" - }); - - AssertSql( - @"EXEC sp_rename N'[dbo].[EntityFrameworkHiLoSequence]', N'MySequence'; -"); - } - - [ConditionalFact] - public override void RenameTableOperation_legacy() - { - base.RenameTableOperation_legacy(); - - AssertSql( - @"EXEC sp_rename N'[dbo].[People]', N'Person'; -"); - } - - [ConditionalFact] - public override void RenameTableOperation() - { - base.RenameTableOperation(); - - AssertSql( - @"EXEC sp_rename N'[dbo].[People]', N'Person'; -"); - } - - [ConditionalFact] - public virtual void SqlOperation_handles_backslash() - { - Generate( - new SqlOperation { Sql = @"-- Multiline \" + EOL + "comment" }); - - AssertSql( - @"-- Multiline comment -"); - } - - [ConditionalFact] - public virtual void SqlOperation_ignores_sequential_gos() - { - Generate( - new SqlOperation { Sql = "-- Ready set" + EOL + "GO" + EOL + "GO" }); - - AssertSql( - @"-- Ready set -"); - } - - [ConditionalFact] - public virtual void SqlOperation_handles_go() - { - Generate( - new SqlOperation { Sql = "-- I" + EOL + "go" + EOL + "-- Too" }); - - AssertSql( - @"-- I -GO - --- Too -"); - } - - [ConditionalFact] - public virtual void SqlOperation_handles_go_with_count() - { - Generate( - new SqlOperation { Sql = "-- I" + EOL + "GO 2" }); - - AssertSql( - @"-- I -GO - --- I -"); - } - - [ConditionalFact] - public virtual void SqlOperation_ignores_non_go() - { - Generate( - new SqlOperation { Sql = "-- I GO 2" }); - - AssertSql( - @"-- I GO 2 -"); - } - - public override void InsertDataOperation() - { - base.InsertDataOperation(); - - AssertSql( - @"IF EXISTS (SELECT * FROM [sys].[identity_columns] WHERE [name] IN (N'Id', N'Full Name') AND [object_id] = OBJECT_ID(N'[People]')) - SET IDENTITY_INSERT [People] ON; -INSERT INTO [People] ([Id], [Full Name]) -VALUES (0, NULL), -(1, N'Daenerys Targaryen'), -(2, N'John Snow'), -(3, N'Arya Stark'), -(4, N'Harry Strickland'); -IF EXISTS (SELECT * FROM [sys].[identity_columns] WHERE [name] IN (N'Id', N'Full Name') AND [object_id] = OBJECT_ID(N'[People]')) - SET IDENTITY_INSERT [People] OFF; -"); - } - - public override void DeleteDataOperation_simple_key() - { - base.DeleteDataOperation_simple_key(); - - // TODO remove rowcount - AssertSql( - @"DELETE FROM [People] -WHERE [Id] = 2; -SELECT @@ROWCOUNT; - -DELETE FROM [People] -WHERE [Id] = 4; -SELECT @@ROWCOUNT; - -"); - } - - public override void DeleteDataOperation_composite_key() - { - base.DeleteDataOperation_composite_key(); - - // TODO remove rowcount - AssertSql( - @"DELETE FROM [People] -WHERE [First Name] = N'Hodor' AND [Last Name] IS NULL; -SELECT @@ROWCOUNT; - -DELETE FROM [People] -WHERE [First Name] = N'Daenerys' AND [Last Name] = N'Targaryen'; -SELECT @@ROWCOUNT; - -"); - } - - public override void UpdateDataOperation_simple_key() - { - base.UpdateDataOperation_simple_key(); - - // TODO remove rowcount - AssertSql( - @"UPDATE [People] SET [Full Name] = N'Daenerys Stormborn' -WHERE [Id] = 1; -SELECT @@ROWCOUNT; - -UPDATE [People] SET [Full Name] = N'Homeless Harry Strickland' -WHERE [Id] = 4; -SELECT @@ROWCOUNT; - -"); - } - - public override void UpdateDataOperation_composite_key() - { - base.UpdateDataOperation_composite_key(); - - // TODO remove rowcount - AssertSql( - @"UPDATE [People] SET [First Name] = N'Hodor' -WHERE [Id] = 0 AND [Last Name] IS NULL; -SELECT @@ROWCOUNT; - -UPDATE [People] SET [First Name] = N'Harry' -WHERE [Id] = 4 AND [Last Name] = N'Strickland'; -SELECT @@ROWCOUNT; - -"); - } - - public override void UpdateDataOperation_multiple_columns() - { - base.UpdateDataOperation_multiple_columns(); - - // TODO remove rowcount - AssertSql( - @"UPDATE [People] SET [First Name] = N'Daenerys', [Nickname] = N'Dany' -WHERE [Id] = 1; -SELECT @@ROWCOUNT; - -UPDATE [People] SET [First Name] = N'Harry', [Nickname] = N'Homeless' -WHERE [Id] = 4; -SELECT @@ROWCOUNT; - -"); - } - - public SqlServerMigrationSqlGeneratorTest() - : base(SqlServerTestHelpers.Instance) - { - } - } -} diff --git a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerCondition.cs b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerCondition.cs index 5d7e187f74a..48a8592c556 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerCondition.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerCondition.cs @@ -16,6 +16,7 @@ public enum SqlServerCondition SupportsAttach = 1 << 5, SupportsHiddenColumns = 1 << 6, IsNotCI = 1 << 7, - SupportsFullTextSearch = 1 << 8 + SupportsFullTextSearch = 1 << 8, + SupportsOnlineIndexes = 1 << 9 } } diff --git a/test/EFCore.SqlServer.FunctionalTests/config.json b/test/EFCore.SqlServer.FunctionalTests/config.json index 31e1f02ecd7..9b2e2823fd2 100644 --- a/test/EFCore.SqlServer.FunctionalTests/config.json +++ b/test/EFCore.SqlServer.FunctionalTests/config.json @@ -7,7 +7,8 @@ "SupportsOffset": true, "SupportsMemoryOptimized": false, "SupportsHiddenColumns": true, - "SupportsFullTextSearch": true + "SupportsFullTextSearch": true, + "SupportsOnlineIndexes": false } } } diff --git a/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationSqlGeneratorTest.cs b/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationSqlGeneratorTest.cs new file mode 100644 index 00000000000..5b7010ce925 --- /dev/null +++ b/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationSqlGeneratorTest.cs @@ -0,0 +1,553 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.SqlServer.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; +using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; + +namespace Microsoft.EntityFrameworkCore.Migrations +{ + public class SqlServerMigrationSqlGeneratorTest : MigrationSqlGeneratorTestBase + { + [ConditionalFact] + public virtual void AddColumnOperation_identity_legacy() + { + Generate( + new AddColumnOperation + { + Table = "People", + Name = "Id", + ClrType = typeof(int), + ColumnType = "int", + DefaultValue = 0, + IsNullable = false, + [SqlServerAnnotationNames.ValueGenerationStrategy] = + SqlServerValueGenerationStrategy.IdentityColumn + }); + + AssertSql( + @"ALTER TABLE [People] ADD [Id] int NOT NULL IDENTITY; +"); + } + + public override void AddColumnOperation_without_column_type() + { + base.AddColumnOperation_without_column_type(); + + AssertSql( + @"ALTER TABLE [People] ADD [Alias] nvarchar(max) NOT NULL; +"); + } + + public override void AddColumnOperation_with_unicode_no_model() + { + base.AddColumnOperation_with_unicode_no_model(); + + AssertSql( + @"ALTER TABLE [Person] ADD [Name] varchar(max) NULL; +"); + } + + public override void AddColumnOperation_with_maxLength_overridden() + { + base.AddColumnOperation_with_maxLength_overridden(); + + AssertSql( + @"ALTER TABLE [Person] ADD [Name] nvarchar(32) NULL; +"); + } + + public override void AddColumnOperation_with_unicode_overridden() + { + base.AddColumnOperation_with_unicode_overridden(); + + AssertSql( + @"ALTER TABLE [Person] ADD [Name] nvarchar(max) NULL; +"); + } + + [ConditionalFact] + public virtual void AddColumnOperation_with_rowversion_overridden() + { + Generate( + modelBuilder => modelBuilder.Entity("Person").Property("RowVersion"), + new AddColumnOperation + { + Table = "Person", + Name = "RowVersion", + ClrType = typeof(byte[]), + IsRowVersion = true, + IsNullable = true + }); + + AssertSql( + @"ALTER TABLE [Person] ADD [RowVersion] rowversion NULL; +"); + } + + [ConditionalFact] + public virtual void AddColumnOperation_with_rowversion_no_model() + { + Generate( + new AddColumnOperation + { + Table = "Person", + Name = "RowVersion", + ClrType = typeof(byte[]), + IsRowVersion = true, + IsNullable = true + }); + + AssertSql( + @"ALTER TABLE [Person] ADD [RowVersion] rowversion NULL; +"); + } + + public override void AlterColumnOperation_without_column_type() + { + base.AlterColumnOperation_without_column_type(); + + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'LuckyNumber'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [LuckyNumber] int NOT NULL; +"); + } + + [ConditionalFact] + public virtual void AlterColumnOperation_with_identity_legacy() + { + Generate( + new AlterColumnOperation + { + Table = "People", + Name = "Id", + ClrType = typeof(int), + [SqlServerAnnotationNames.ValueGenerationStrategy] = + SqlServerValueGenerationStrategy.IdentityColumn + }); + + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[People]') AND [c].[name] = N'Id'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [People] ALTER COLUMN [Id] int NOT NULL; +"); + } + + [ConditionalFact] + public virtual void AlterColumnOperation_with_index_no_oldColumn() + { + Generate( + modelBuilder => modelBuilder + .HasAnnotation(CoreAnnotationNames.ProductVersion, "1.0.0-rtm") + .Entity( + "Person", x => + { + x.Property("Name").HasMaxLength(30); + x.HasIndex("Name"); + }), + new AlterColumnOperation + { + Table = "Person", + Name = "Name", + ClrType = typeof(string), + MaxLength = 30, + IsNullable = true, + OldColumn = new ColumnOperation() + }); + + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[Person]') AND [c].[name] = N'Name'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [Person] ALTER COLUMN [Name] nvarchar(30) NULL; +"); + } + + [ConditionalFact] + public virtual void AlterColumnOperation_with_added_index() + { + Generate( + modelBuilder => modelBuilder + .HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0") + .Entity( + "Person", x => + { + x.Property("Name").HasMaxLength(30); + x.HasIndex("Name"); + }), + new AlterColumnOperation + { + Table = "Person", + Name = "Name", + ClrType = typeof(string), + MaxLength = 30, + IsNullable = true, + OldColumn = new ColumnOperation { ClrType = typeof(string), IsNullable = true } + }, + new CreateIndexOperation + { + Name = "IX_Person_Name", + Table = "Person", + Columns = new[] { "Name" } + }); + + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[Person]') AND [c].[name] = N'Name'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [Person] ALTER COLUMN [Name] nvarchar(30) NULL; +GO + +CREATE INDEX [IX_Person_Name] ON [Person] ([Name]); +"); + } + + [ConditionalFact] + public virtual void AlterColumnOperation_identity_legacy() + { + Generate( + modelBuilder => modelBuilder.HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0"), + new AlterColumnOperation + { + Table = "Person", + Name = "Id", + ClrType = typeof(long), + [SqlServerAnnotationNames.ValueGenerationStrategy] = SqlServerValueGenerationStrategy.IdentityColumn, + OldColumn = new ColumnOperation + { + ClrType = typeof(int), + [SqlServerAnnotationNames.ValueGenerationStrategy] = SqlServerValueGenerationStrategy.IdentityColumn + } + }); + + AssertSql( + @"DECLARE @var0 sysname; +SELECT @var0 = [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'[Person]') AND [c].[name] = N'Id'); +IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Person] DROP CONSTRAINT [' + @var0 + '];'); +ALTER TABLE [Person] ALTER COLUMN [Id] bigint NOT NULL; +"); + } + + [ConditionalFact] + public virtual void AlterColumnOperation_add_identity_legacy() + { + var ex = Assert.Throws( + () => Generate( + modelBuilder => modelBuilder.HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0"), + new AlterColumnOperation + { + Table = "Person", + Name = "Id", + ClrType = typeof(int), + [SqlServerAnnotationNames.ValueGenerationStrategy] = SqlServerValueGenerationStrategy.IdentityColumn, + OldColumn = new ColumnOperation { ClrType = typeof(int) } + })); + + Assert.Equal(SqlServerStrings.AlterIdentityColumn, ex.Message); + } + + [ConditionalFact] + public virtual void AlterColumnOperation_remove_identity_legacy() + { + var ex = Assert.Throws( + () => Generate( + modelBuilder => modelBuilder.HasAnnotation(CoreAnnotationNames.ProductVersion, "1.1.0"), + new AlterColumnOperation + { + Table = "Person", + Name = "Id", + ClrType = typeof(int), + OldColumn = new ColumnOperation + { + ClrType = typeof(int), + [SqlServerAnnotationNames.ValueGenerationStrategy] = SqlServerValueGenerationStrategy.IdentityColumn + } + })); + + Assert.Equal(SqlServerStrings.AlterIdentityColumn, ex.Message); + } + + [ConditionalFact] + public virtual void CreateDatabaseOperation() + { + Generate( + new SqlServerCreateDatabaseOperation { Name = "Northwind" }); + + AssertSql( + @"CREATE DATABASE [Northwind]; +GO + +IF SERVERPROPERTY('EngineEdition') <> 5 +BEGIN + ALTER DATABASE [Northwind] SET READ_COMMITTED_SNAPSHOT ON; +END; +"); + } + + [ConditionalFact] + public virtual void CreateDatabaseOperation_with_filename() + { + Generate( + new SqlServerCreateDatabaseOperation { Name = "Northwind", FileName = "Narf.mdf" }); + + var expectedFile = Path.GetFullPath("Narf.mdf"); + var expectedLog = Path.GetFullPath("Narf_log.ldf"); + + AssertSql( + $@"CREATE DATABASE [Northwind] +ON (NAME = N'Narf', FILENAME = N'{expectedFile}') +LOG ON (NAME = N'Narf_log', FILENAME = N'{expectedLog}'); +GO + +IF SERVERPROPERTY('EngineEdition') <> 5 +BEGIN + ALTER DATABASE [Northwind] SET READ_COMMITTED_SNAPSHOT ON; +END; +"); + } + + [ConditionalFact] + public virtual void CreateDatabaseOperation_with_filename_and_datadirectory() + { + var baseDirectory = AppDomain.CurrentDomain.BaseDirectory; + + Generate( + new SqlServerCreateDatabaseOperation { Name = "Northwind", FileName = "|DataDirectory|Narf.mdf" }); + + var expectedFile = Path.Combine(baseDirectory, "Narf.mdf"); + var expectedLog = Path.Combine(baseDirectory, "Narf_log.ldf"); + + AssertSql( + $@"CREATE DATABASE [Northwind] +ON (NAME = N'Narf', FILENAME = N'{expectedFile}') +LOG ON (NAME = N'Narf_log', FILENAME = N'{expectedLog}'); +GO + +IF SERVERPROPERTY('EngineEdition') <> 5 +BEGIN + ALTER DATABASE [Northwind] SET READ_COMMITTED_SNAPSHOT ON; +END; +"); + } + + [ConditionalFact] + public virtual void CreateDatabaseOperation_with_filename_and_custom_datadirectory() + { + var dataDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data"); + + AppDomain.CurrentDomain.SetData("DataDirectory", dataDirectory); + + Generate( + new SqlServerCreateDatabaseOperation { Name = "Northwind", FileName = "|DataDirectory|Narf.mdf" }); + + AppDomain.CurrentDomain.SetData("DataDirectory", null); + + var expectedFile = Path.Combine(dataDirectory, "Narf.mdf"); + var expectedLog = Path.Combine(dataDirectory, "Narf_log.ldf"); + + AssertSql( + $@"CREATE DATABASE [Northwind] +ON (NAME = N'Narf', FILENAME = N'{expectedFile}') +LOG ON (NAME = N'Narf_log', FILENAME = N'{expectedLog}'); +GO + +IF SERVERPROPERTY('EngineEdition') <> 5 +BEGIN + ALTER DATABASE [Northwind] SET READ_COMMITTED_SNAPSHOT ON; +END; +"); + } + + [ConditionalFact] + public virtual void AlterDatabaseOperation_memory_optimized() + { + Generate( + new AlterDatabaseOperation { [SqlServerAnnotationNames.MemoryOptimized] = true }); + + Assert.Contains( + "CONTAINS MEMORY_OPTIMIZED_DATA;", + Sql); + } + + + [ConditionalFact] + public virtual void DropDatabaseOperation() + { + Generate( + new SqlServerDropDatabaseOperation { Name = "Northwind" }); + + AssertSql( + @"IF SERVERPROPERTY('EngineEdition') <> 5 +BEGIN + ALTER DATABASE [Northwind] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; +END; +GO + +DROP DATABASE [Northwind]; +"); + } + + [ConditionalFact] + public virtual void MoveSequenceOperation_legacy() + { + Generate( + new RenameSequenceOperation + { + Name = "EntityFrameworkHiLoSequence", + Schema = "dbo", + NewSchema = "my" + }); + + AssertSql( + @"ALTER SCHEMA [my] TRANSFER [dbo].[EntityFrameworkHiLoSequence]; +"); + } + + [ConditionalFact] + public virtual void MoveTableOperation_legacy() + { + Generate( + new RenameTableOperation + { + Name = "People", + Schema = "dbo", + NewSchema = "hr" + }); + + AssertSql( + @"ALTER SCHEMA [hr] TRANSFER [dbo].[People]; +"); + } + + [ConditionalFact] + public virtual void RenameIndexOperations_throws_when_no_table() + { + var migrationBuilder = new MigrationBuilder("SqlServer"); + + migrationBuilder.RenameIndex( + name: "IX_OldIndex", + newName: "IX_NewIndex"); + + var ex = Assert.Throws( + () => Generate(migrationBuilder.Operations.ToArray())); + + Assert.Equal(SqlServerStrings.IndexTableRequired, ex.Message); + } + + [ConditionalFact] + public virtual void RenameSequenceOperation_legacy() + { + Generate( + new RenameSequenceOperation + { + Name = "EntityFrameworkHiLoSequence", + Schema = "dbo", + NewName = "MySequence" + }); + + AssertSql( + @"EXEC sp_rename N'[dbo].[EntityFrameworkHiLoSequence]', N'MySequence'; +"); + } + + [ConditionalFact] + public override void RenameTableOperation_legacy() + { + base.RenameTableOperation_legacy(); + + AssertSql( + @"EXEC sp_rename N'[dbo].[People]', N'Person'; +"); + } + + [ConditionalFact] + public virtual void SqlOperation_handles_backslash() + { + Generate( + new SqlOperation { Sql = @"-- Multiline \" + EOL + "comment" }); + + AssertSql( + @"-- Multiline comment +"); + } + + [ConditionalFact] + public virtual void SqlOperation_ignores_sequential_gos() + { + Generate( + new SqlOperation { Sql = "-- Ready set" + EOL + "GO" + EOL + "GO" }); + + AssertSql( + @"-- Ready set +"); + } + + [ConditionalFact] + public virtual void SqlOperation_handles_go() + { + Generate( + new SqlOperation { Sql = "-- I" + EOL + "go" + EOL + "-- Too" }); + + AssertSql( + @"-- I +GO + +-- Too +"); + } + + [ConditionalFact] + public virtual void SqlOperation_handles_go_with_count() + { + Generate( + new SqlOperation { Sql = "-- I" + EOL + "GO 2" }); + + AssertSql( + @"-- I +GO + +-- I +"); + } + + [ConditionalFact] + public virtual void SqlOperation_ignores_non_go() + { + Generate( + new SqlOperation { Sql = "-- I GO 2" }); + + AssertSql( + @"-- I GO 2 +"); + } + + public SqlServerMigrationSqlGeneratorTest() + : base(SqlServerTestHelpers.Instance) + { + } + } +} diff --git a/test/EFCore.Sqlite.FunctionalTests/MigrationsInfrastructureSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/MigrationsInfrastructureSqliteTest.cs new file mode 100644 index 00000000000..2956dcf1825 --- /dev/null +++ b/test/EFCore.Sqlite.FunctionalTests/MigrationsInfrastructureSqliteTest.cs @@ -0,0 +1,1084 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Data.Common; +using Identity30.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.TestModels.AspNetIdentity; +using Microsoft.EntityFrameworkCore.TestUtilities; +using ModelSnapshot22; +using Xunit; + +namespace Microsoft.EntityFrameworkCore +{ + public class MigrationsInfrastructureSqliteTest + : MigrationsInfrastructureTestBase + { + public MigrationsInfrastructureSqliteTest(MigrationsInfrastructureSqliteFixture fixture) + : base(fixture) + { + } + + public override void Can_generate_migration_from_initial_database_to_initial() + { + base.Can_generate_migration_from_initial_database_to_initial(); + + Assert.Equal( + @"CREATE TABLE IF NOT EXISTS ""__EFMigrationsHistory"" ( + ""MigrationId"" TEXT NOT NULL CONSTRAINT ""PK___EFMigrationsHistory"" PRIMARY KEY, + ""ProductVersion"" TEXT NOT NULL +); + +", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Can_generate_no_migration_script() + { + base.Can_generate_no_migration_script(); + + Assert.Equal( + @"CREATE TABLE IF NOT EXISTS ""__EFMigrationsHistory"" ( + ""MigrationId"" TEXT NOT NULL CONSTRAINT ""PK___EFMigrationsHistory"" PRIMARY KEY, + ""ProductVersion"" TEXT NOT NULL +); + +", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Can_generate_up_scripts() + { + base.Can_generate_up_scripts(); + + Assert.Equal( + @"CREATE TABLE IF NOT EXISTS ""__EFMigrationsHistory"" ( + ""MigrationId"" TEXT NOT NULL CONSTRAINT ""PK___EFMigrationsHistory"" PRIMARY KEY, + ""ProductVersion"" TEXT NOT NULL +); + +CREATE TABLE ""Table1"" ( + ""Id"" INTEGER NOT NULL CONSTRAINT ""PK_Table1"" PRIMARY KEY, + ""Foo"" INTEGER NOT NULL +); + +INSERT INTO ""__EFMigrationsHistory"" (""MigrationId"", ""ProductVersion"") +VALUES ('00000000000001_Migration1', '7.0.0-test'); + +ALTER TABLE ""Table1"" RENAME COLUMN ""Foo"" TO ""Bar""; + +INSERT INTO ""__EFMigrationsHistory"" (""MigrationId"", ""ProductVersion"") +VALUES ('00000000000002_Migration2', '7.0.0-test'); + +INSERT INTO ""__EFMigrationsHistory"" (""MigrationId"", ""ProductVersion"") +VALUES ('00000000000003_Migration3', '7.0.0-test'); + +", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Can_generate_one_up_script() + { + base.Can_generate_one_up_script(); + + Assert.Equal( + @"ALTER TABLE ""Table1"" RENAME COLUMN ""Foo"" TO ""Bar""; + +INSERT INTO ""__EFMigrationsHistory"" (""MigrationId"", ""ProductVersion"") +VALUES ('00000000000002_Migration2', '7.0.0-test'); + +", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Can_generate_up_script_using_names() + { + base.Can_generate_up_script_using_names(); + + Assert.Equal( + @"ALTER TABLE ""Table1"" RENAME COLUMN ""Foo"" TO ""Bar""; + +INSERT INTO ""__EFMigrationsHistory"" (""MigrationId"", ""ProductVersion"") +VALUES ('00000000000002_Migration2', '7.0.0-test'); + +", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Can_generate_idempotent_up_scripts() + { + Assert.Throws(() => base.Can_generate_idempotent_up_scripts()); + } + + public override void Can_generate_down_scripts() + { + base.Can_generate_down_scripts(); + + Assert.Equal( + @"ALTER TABLE ""Table1"" RENAME COLUMN ""Bar"" TO ""Foo""; + +DELETE FROM ""__EFMigrationsHistory"" +WHERE ""MigrationId"" = '00000000000002_Migration2'; + +DROP TABLE ""Table1""; + +DELETE FROM ""__EFMigrationsHistory"" +WHERE ""MigrationId"" = '00000000000001_Migration1'; + +", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Can_generate_one_down_script() + { + base.Can_generate_one_down_script(); + + Assert.Equal( + @"ALTER TABLE ""Table1"" RENAME COLUMN ""Bar"" TO ""Foo""; + +DELETE FROM ""__EFMigrationsHistory"" +WHERE ""MigrationId"" = '00000000000002_Migration2'; + +", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Can_generate_down_script_using_names() + { + base.Can_generate_down_script_using_names(); + + Assert.Equal( + @"ALTER TABLE ""Table1"" RENAME COLUMN ""Bar"" TO ""Foo""; + +DELETE FROM ""__EFMigrationsHistory"" +WHERE ""MigrationId"" = '00000000000002_Migration2'; + +", + Sql, + ignoreLineEndingDifferences: true); + } + + public override void Can_generate_idempotent_down_scripts() + { + Assert.Throws(() => base.Can_generate_idempotent_down_scripts()); + } + + public override void Can_get_active_provider() + { + base.Can_get_active_provider(); + + Assert.Equal("Microsoft.EntityFrameworkCore.Sqlite", ActiveProvider); + } + + public override void Can_diff_against_2_2_model() + { + using var context = new BloggingContext(); + DiffSnapshot(new BloggingContextModelSnapshot22(), context); + } + + public class BloggingContextModelSnapshot22 : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); + + modelBuilder.Entity( + "ModelSnapshot22.Blog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("Blogs"); + }); + + modelBuilder.Entity( + "ModelSnapshot22.Post", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BlogId"); + + b.Property("Content"); + + b.Property("EditDate"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("BlogId"); + + b.ToTable("Post"); + }); + + modelBuilder.Entity( + "ModelSnapshot22.Post", b => + { + b.HasOne("ModelSnapshot22.Blog", "Blog") + .WithMany("Posts") + .HasForeignKey("BlogId"); + }); +#pragma warning restore 612, 618 + } + } + + public class AspNetIdentity21ModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.0"); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasMaxLength(128); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider") + .HasMaxLength(128); + + b.Property("Name") + .HasMaxLength(128); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } + + public override void Can_diff_against_2_1_ASP_NET_Identity_model() + { + using var context = new ApplicationDbContext(); + DiffSnapshot(new AspNetIdentity21ModelSnapshot(), context); + } + + public class AspNetIdentity22ModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.0-preview1"); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasMaxLength(128); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider") + .HasMaxLength(128); + + b.Property("Name") + .HasMaxLength(128); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } + + public override void Can_diff_against_2_2_ASP_NET_Identity_model() + { + using var context = new ApplicationDbContext(); + DiffSnapshot(new AspNetIdentity22ModelSnapshot(), context); + } + + public class AspNetIdentity30ModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.0.0"); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property("Name") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity( + "Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } + + public override void Can_diff_against_3_0_ASP_NET_Identity_model() + { + using var context = new ApplicationDbContext(); + DiffSnapshot(new AspNetIdentity30ModelSnapshot(), context); + } + + public class MigrationsInfrastructureSqliteFixture : MigrationsFixtureBase + { + protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; + } + } +} + +namespace ModelSnapshot22 +{ + public class Blog + { + public int Id { get; set; } + public string Name { get; set; } + + public ICollection Posts { get; set; } + } + + public class Post + { + public int Id { get; set; } + public string Title { get; set; } + public string Content { get; set; } + public DateTime EditDate { get; set; } + + public Blog Blog { get; set; } + } + + public class BloggingContext : DbContext + { + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseSqlite("DataSource=Test.db"); + + public DbSet Blogs { get; set; } + } +} + +namespace Identity30.Data +{ + public class ApplicationDbContext : IdentityDbContext + { + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseSqlite("DataSource=Test.db"); + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.Entity( + b => + { + b.HasIndex(u => u.NormalizedUserName).HasName("UserNameIndex").IsUnique(); + b.HasIndex(u => u.NormalizedEmail).HasName("EmailIndex"); + b.ToTable("AspNetUsers"); + }); + + builder.Entity>( + b => + { + b.ToTable("AspNetUserClaims"); + }); + + builder.Entity>( + b => + { + b.ToTable("AspNetUserLogins"); + }); + + builder.Entity>( + b => + { + b.ToTable("AspNetUserTokens"); + }); + + builder.Entity( + b => + { + b.HasIndex(r => r.NormalizedName).HasName("RoleNameIndex").IsUnique(); + b.ToTable("AspNetRoles"); + }); + + builder.Entity>( + b => + { + b.ToTable("AspNetRoleClaims"); + }); + + builder.Entity>( + b => + { + b.ToTable("AspNetUserRoles"); + }); + } + } +} diff --git a/test/EFCore.Sqlite.FunctionalTests/MigrationsSqliteFixture.cs b/test/EFCore.Sqlite.FunctionalTests/MigrationsSqliteFixture.cs deleted file mode 100644 index af9c00f6337..00000000000 --- a/test/EFCore.Sqlite.FunctionalTests/MigrationsSqliteFixture.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.EntityFrameworkCore.TestUtilities; - -namespace Microsoft.EntityFrameworkCore -{ - public class MigrationsSqliteFixture : MigrationsFixtureBase - { - protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; - } -} diff --git a/test/EFCore.Sqlite.FunctionalTests/MigrationsSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/MigrationsSqliteTest.cs index 0ce5d0a0bea..8ef5a29b989 100644 --- a/test/EFCore.Sqlite.FunctionalTests/MigrationsSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/MigrationsSqliteTest.cs @@ -2,1197 +2,401 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; -using System.Data.Common; -using Identity30.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Internal; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Migrations.Operations; -using Microsoft.EntityFrameworkCore.TestModels.AspNetIdentity; -using ModelSnapshot22; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore.Scaffolding; +using Microsoft.EntityFrameworkCore.Sqlite.Internal; +using Microsoft.EntityFrameworkCore.Sqlite.Scaffolding.Internal; +using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.Extensions.DependencyInjection; using Xunit; +using Xunit.Abstractions; + +#nullable enable namespace Microsoft.EntityFrameworkCore { - public class MigrationsSqliteTest : MigrationsTestBase + public class MigrationsSqliteTest : MigrationsTestBase { - public MigrationsSqliteTest(MigrationsSqliteFixture fixture) + public MigrationsSqliteTest(MigrationsSqliteFixture fixture, ITestOutputHelper testOutputHelper) : base(fixture) { + Fixture.TestSqlLoggerFactory.Clear(); + //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } - public override void Can_generate_migration_from_initial_database_to_initial() + public override async Task Create_table() { - base.Can_generate_migration_from_initial_database_to_initial(); - - Assert.Equal( - @"CREATE TABLE IF NOT EXISTS ""__EFMigrationsHistory"" ( - ""MigrationId"" TEXT NOT NULL CONSTRAINT ""PK___EFMigrationsHistory"" PRIMARY KEY, - ""ProductVersion"" TEXT NOT NULL -); + await base.Create_table(); -", - Sql, - ignoreLineEndingDifferences: true); + AssertSql( + @"CREATE TABLE ""People"" ( + ""Id"" INTEGER NOT NULL CONSTRAINT ""PK_People"" PRIMARY KEY AUTOINCREMENT, + ""Name"" TEXT NULL +);"); } - public override void Can_generate_no_migration_script() - { - base.Can_generate_no_migration_script(); - - Assert.Equal( - @"CREATE TABLE IF NOT EXISTS ""__EFMigrationsHistory"" ( - ""MigrationId"" TEXT NOT NULL CONSTRAINT ""PK___EFMigrationsHistory"" PRIMARY KEY, - ""ProductVersion"" TEXT NOT NULL -); - -", - Sql, - ignoreLineEndingDifferences: true); - } + // SQLite does not support schemas, check constraints, etc. + public override Task Create_table_all_settings() => Task.CompletedTask; - public override void Can_generate_up_scripts() + public override async Task Create_table_with_comments() { - base.Can_generate_up_scripts(); - - Assert.Equal( - @"CREATE TABLE IF NOT EXISTS ""__EFMigrationsHistory"" ( - ""MigrationId"" TEXT NOT NULL CONSTRAINT ""PK___EFMigrationsHistory"" PRIMARY KEY, - ""ProductVersion"" TEXT NOT NULL -); - -CREATE TABLE ""Table1"" ( - ""Id"" INTEGER NOT NULL CONSTRAINT ""PK_Table1"" PRIMARY KEY, - ""Foo"" INTEGER NOT NULL -); - -INSERT INTO ""__EFMigrationsHistory"" (""MigrationId"", ""ProductVersion"") -VALUES ('00000000000001_Migration1', '7.0.0-test'); - -ALTER TABLE ""Table1"" RENAME COLUMN ""Foo"" TO ""Bar""; + await Test( + builder => { }, + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("Name").HasComment("Column comment"); + e.HasComment("Table comment"); + }), + model => + { + // Reverse-engineering of comments isn't supported in Sqlite + var table = Assert.Single(model.Tables); + Assert.Null(table.Comment); + var column = Assert.Single(table.Columns, c => c.Name == "Name"); + Assert.Null(column.Comment); + }); -INSERT INTO ""__EFMigrationsHistory"" (""MigrationId"", ""ProductVersion"") -VALUES ('00000000000002_Migration2', '7.0.0-test'); + AssertSql( + @"CREATE TABLE ""People"" ( + -- Table comment -INSERT INTO ""__EFMigrationsHistory"" (""MigrationId"", ""ProductVersion"") -VALUES ('00000000000003_Migration3', '7.0.0-test'); + ""Id"" INTEGER NOT NULL, -", - Sql, - ignoreLineEndingDifferences: true); + -- Column comment + ""Name"" TEXT NULL +);"); } - public override void Can_generate_one_up_script() + [ConditionalFact] + public override async Task Create_table_with_multiline_comments() { - base.Can_generate_one_up_script(); - - Assert.Equal( - @"ALTER TABLE ""Table1"" RENAME COLUMN ""Foo"" TO ""Bar""; - -INSERT INTO ""__EFMigrationsHistory"" (""MigrationId"", ""ProductVersion"") -VALUES ('00000000000002_Migration2', '7.0.0-test'); + var tableComment = @"This is a multi-line +table comment. +More information can +be found in the docs."; + var columnComment = @"This is a multi-line +column comment. +More information can +be found in the docs."; + + await Test( + builder => { }, + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("Name").HasComment(columnComment); + e.HasComment(tableComment); + }), + model => + { + // Reverse-engineering of comments isn't supported in Sqlite + var table = Assert.Single(model.Tables); + Assert.Null(table.Comment); + var column = Assert.Single(table.Columns, c => c.Name == "Name"); + Assert.Null(column.Comment); + }); -", - Sql, - ignoreLineEndingDifferences: true); + AssertSql( + @"CREATE TABLE ""People"" ( + -- This is a multi-line + -- table comment. + -- More information can + -- be found in the docs. + + ""Id"" INTEGER NOT NULL, + + -- This is a multi-line + -- column comment. + -- More information can + -- be found in the docs. + ""Name"" TEXT NULL +);"); } - public override void Can_generate_up_script_using_names() + // In Sqlite, comments are only generated when creating a table + public override async Task Alter_table_add_comment() { - base.Can_generate_up_script_using_names(); - - Assert.Equal( - @"ALTER TABLE ""Table1"" RENAME COLUMN ""Foo"" TO ""Bar""; - -INSERT INTO ""__EFMigrationsHistory"" (""MigrationId"", ""ProductVersion"") -VALUES ('00000000000002_Migration2', '7.0.0-test'); + await Test( + builder => builder.Entity("People").Property("Id"), + builder => builder.Entity("People").HasComment("Table comment").Property("Id"), + model => Assert.Null(Assert.Single(model.Tables).Comment)); -", - Sql, - ignoreLineEndingDifferences: true); + AssertSql(); } - public override void Can_generate_idempotent_up_scripts() + // In Sqlite, comments are only generated when creating a table + public override async Task Alter_table_add_comment_non_default_schema() { - Assert.Throws(() => base.Can_generate_idempotent_up_scripts()); + await Test( + builder => builder.Entity("People") + .ToTable("People", "SomeOtherSchema") + .Property("Id"), + builder => { }, + builder => builder.Entity("People") + .ToTable("People", "SomeOtherSchema") + .HasComment("Table comment"), + model => Assert.Null(Assert.Single(model.Tables).Comment)); } - public override void Can_generate_down_scripts() + // In Sqlite, comments are only generated when creating a table + public override async Task Alter_table_change_comment() { - base.Can_generate_down_scripts(); - - Assert.Equal( - @"ALTER TABLE ""Table1"" RENAME COLUMN ""Bar"" TO ""Foo""; - -DELETE FROM ""__EFMigrationsHistory"" -WHERE ""MigrationId"" = '00000000000002_Migration2'; + await Test( + builder => builder.Entity("People").HasComment("Table comment1").Property("Id"), + builder => builder.Entity("People").HasComment("Table comment2").Property("Id"), + model => Assert.Null(Assert.Single(model.Tables).Comment)); -DROP TABLE ""Table1""; - -DELETE FROM ""__EFMigrationsHistory"" -WHERE ""MigrationId"" = '00000000000001_Migration1'; - -", - Sql, - ignoreLineEndingDifferences: true); + AssertSql(); } - public override void Can_generate_one_down_script() + // In Sqlite, comments are only generated when creating a table + public override async Task Alter_table_remove_comment() { - base.Can_generate_one_down_script(); - - Assert.Equal( - @"ALTER TABLE ""Table1"" RENAME COLUMN ""Bar"" TO ""Foo""; + await Test( + builder => builder.Entity("People").Property("Id"), + builder => builder.Entity("People").HasComment("Table comment1"), + builder => builder.Entity("People").HasComment("Table comment2"), + model => Assert.Null(Assert.Single(model.Tables).Comment)); -DELETE FROM ""__EFMigrationsHistory"" -WHERE ""MigrationId"" = '00000000000002_Migration2'; - -", - Sql, - ignoreLineEndingDifferences: true); + AssertSql(); } - public override void Can_generate_down_script_using_names() + public override async Task Rename_table() { - base.Can_generate_down_script_using_names(); - - Assert.Equal( - @"ALTER TABLE ""Table1"" RENAME COLUMN ""Bar"" TO ""Foo""; - -DELETE FROM ""__EFMigrationsHistory"" -WHERE ""MigrationId"" = '00000000000002_Migration2'; - -", - Sql, - ignoreLineEndingDifferences: true); + var ex = await Assert.ThrowsAsync(base.Rename_table); + Assert.Contains("there is already another table or index with this name", ex.Message); } - public override void Can_generate_idempotent_down_scripts() - { - Assert.Throws(() => base.Can_generate_idempotent_down_scripts()); - } + public override Task Rename_table_with_primary_key() + => AssertNotSupportedAsync( + base.Rename_table_with_primary_key, SqliteStrings.InvalidMigrationOperation("DropPrimaryKeyOperation")); + + // SQLite does not support schemas. + public override Task Move_table() + => Test( + builder => builder.Entity("TestTable").Property("Id"), + builder => { }, + builder => builder.Entity("TestTable").ToTable("TestTable", "TestTableSchema"), + model => + { + var table = Assert.Single(model.Tables); + Assert.Null(table.Schema); + Assert.Equal("TestTable", table.Name); + }); + + // SQLite does not support schemas + public override Task Create_schema() + => Test( + builder => { }, + builder => builder.Entity("People") + .ToTable("People", "SomeOtherSchema") + .Property("Id"), + model => Assert.Null(Assert.Single(model.Tables).Schema)); - public override void Can_get_active_provider() + public override async Task Add_column_with_defaultValue_datetime() { - base.Can_get_active_provider(); + await base.Add_column_with_defaultValue_datetime(); - Assert.Equal("Microsoft.EntityFrameworkCore.Sqlite", ActiveProvider); + AssertSql( + @"ALTER TABLE ""People"" ADD ""Birthday"" TEXT NOT NULL DEFAULT '2015-04-12 17:05:00';"); } - protected override void AssertFirstMigration(DbConnection connection) + public override async Task Add_column_with_defaultValueSql() { - var sql = GetDatabaseSchemaAsync(connection); - Assert.Equal( - @" -CreatedTable - Id INTEGER NOT NULL - ColumnWithDefaultToDrop INTEGER NULL DEFAULT 0 - ColumnWithDefaultToAlter INTEGER NULL DEFAULT 1 - -Foos - Id INTEGER NOT NULL - -sqlite_sequence - name NULL - seq NULL -", - sql, - ignoreLineEndingDifferences: true); + var ex = await Assert.ThrowsAsync(base.Add_column_with_defaultValueSql); + Assert.Contains("Cannot add a column with non-constant default", ex.Message); } - protected override void BuildSecondMigration(MigrationBuilder migrationBuilder) - { - base.BuildSecondMigration(migrationBuilder); + public override Task Add_column_with_computedSql() + => AssertNotSupportedAsync(base.Add_column_with_computedSql, SqliteStrings.ComputedColumnsNotSupported); - for (var i = migrationBuilder.Operations.Count - 1; i >= 0; i--) - { - var operation = migrationBuilder.Operations[i]; - if (operation is AlterColumnOperation - || operation is DropColumnOperation) - { - migrationBuilder.Operations.RemoveAt(i); - } - } - } - - protected override void AssertSecondMigration(DbConnection connection) + public override async Task Add_column_with_max_length() { - var sql = GetDatabaseSchemaAsync(connection); - Assert.Equal( - @" -CreatedTable - Id INTEGER NOT NULL - ColumnWithDefaultToDrop INTEGER NULL DEFAULT 0 - ColumnWithDefaultToAlter INTEGER NULL DEFAULT 1 - -Foos - Id INTEGER NOT NULL - -sqlite_sequence - name NULL - seq NULL -", - sql, - ignoreLineEndingDifferences: true); + await base.Add_column_with_max_length(); + + // See issue #3698 + AssertSql( + @"ALTER TABLE ""People"" ADD ""Name"" TEXT NULL;"); } - private string GetDatabaseSchemaAsync(DbConnection connection) + // In Sqlite, comments are only generated when creating a table + public override async Task Add_column_with_comment() { - var builder = new IndentedStringBuilder(); - - using (var command = connection.CreateCommand()) - { - command.CommandText = @" - SELECT name - FROM sqlite_master - WHERE type = 'table' - ORDER BY name;"; - - var tables = new List(); - using (var reader = command.ExecuteReader()) - { - while (reader.Read()) - { - tables.Add(reader.GetString(0)); - } - } - - var first = true; - foreach (var table in tables) + await Test( + builder => builder.Entity("People").Property("Id"), + builder => { }, + builder => builder.Entity("People").Property("FullName").HasComment("My comment"), + model => { - if (first) - { - first = false; - } - else - { - builder.DecrementIndent(); - } - - builder - .AppendLine() - .AppendLine(table) - .IncrementIndent(); - - command.CommandText = "PRAGMA table_info(" + table + ");"; - using var reader = command.ExecuteReader(); - while (reader.Read()) - { - builder - .Append(reader[1]) // Name - .Append(" ") - .Append(reader[2]) // Type - .Append(" ") - .Append(reader.GetBoolean(3) ? "NOT NULL" : "NULL"); - - if (!reader.IsDBNull(4)) - { - builder - .Append(" DEFAULT ") - .Append(reader[4]); - } - - builder.AppendLine(); - } - } - } - - return builder.ToString(); - } + var table = Assert.Single(model.Tables); + var column = Assert.Single(table.Columns, c => c.Name == "FullName"); + Assert.Null(column.Comment); + }); - public override void Can_diff_against_2_2_model() - { - using var context = new BloggingContext(); - DiffSnapshot(new BloggingContextModelSnapshot22(), context); + AssertSql( + @"ALTER TABLE ""People"" ADD ""FullName"" TEXT NULL;"); } - public class BloggingContextModelSnapshot22 : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); + public override Task Alter_column_make_required() + => AssertNotSupportedAsync(base.Alter_column_make_required, SqliteStrings.InvalidMigrationOperation("AlterColumnOperation")); - modelBuilder.Entity( - "ModelSnapshot22.Blog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); + public override Task Alter_column_make_required_with_index() + => AssertNotSupportedAsync( + base.Alter_column_make_required_with_index, SqliteStrings.InvalidMigrationOperation("AlterColumnOperation")); - b.Property("Name"); + public override Task Alter_column_make_required_with_composite_index() + => AssertNotSupportedAsync( + base.Alter_column_make_required_with_composite_index, SqliteStrings.InvalidMigrationOperation("AlterColumnOperation")); - b.HasKey("Id"); + public override Task Alter_column_make_computed() + => AssertNotSupportedAsync(base.Alter_column_make_computed, SqliteStrings.InvalidMigrationOperation("AlterColumnOperation")); - b.ToTable("Blogs"); - }); - - modelBuilder.Entity( - "ModelSnapshot22.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); + public override Task Alter_column_change_computed() + => AssertNotSupportedAsync(base.Alter_column_change_computed, SqliteStrings.ComputedColumnsNotSupported); - b.Property("BlogId"); + public override Task Alter_column_add_comment() + => AssertNotSupportedAsync(base.Alter_column_add_comment, SqliteStrings.InvalidMigrationOperation("AlterColumnOperation")); - b.Property("Content"); + public override Task Alter_column_change_comment() + => AssertNotSupportedAsync(base.Alter_column_change_comment, SqliteStrings.InvalidMigrationOperation("AlterColumnOperation")); - b.Property("EditDate"); + public override Task Alter_column_remove_comment() + => AssertNotSupportedAsync(base.Alter_column_remove_comment, SqliteStrings.InvalidMigrationOperation("AlterColumnOperation")); - b.Property("Title"); + public override Task Drop_column() + => AssertNotSupportedAsync(base.Drop_column, SqliteStrings.InvalidMigrationOperation("DropColumnOperation")); - b.HasKey("Id"); + public override Task Drop_column_primary_key() + => AssertNotSupportedAsync(base.Drop_column_primary_key, SqliteStrings.InvalidMigrationOperation("DropPrimaryKeyOperation")); - b.HasIndex("BlogId"); - - b.ToTable("Post"); - }); - - modelBuilder.Entity( - "ModelSnapshot22.Post", b => - { - b.HasOne("ModelSnapshot22.Blog", "Blog") - .WithMany("Posts") - .HasForeignKey("BlogId"); - }); -#pragma warning restore 612, 618 - } - } - - public class AspNetIdentity21ModelSnapshot : ModelSnapshot + public override async Task Rename_column() { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.1.0"); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken(); - - b.Property("Name") - .HasMaxLength(256); - - b.Property("NormalizedName") - .HasMaxLength(256); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasName("RoleNameIndex"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ClaimType"); - - b.Property("ClaimValue"); + await base.Rename_column(); - b.Property("RoleId") - .IsRequired(); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("AccessFailedCount"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken(); - - b.Property("Email") - .HasMaxLength(256); - - b.Property("EmailConfirmed"); - - b.Property("LockoutEnabled"); - - b.Property("LockoutEnd"); - - b.Property("NormalizedEmail") - .HasMaxLength(256); - - b.Property("NormalizedUserName") - .HasMaxLength(256); - - b.Property("PasswordHash"); - - b.Property("PhoneNumber"); - - b.Property("PhoneNumberConfirmed"); - - b.Property("SecurityStamp"); - - b.Property("TwoFactorEnabled"); - - b.Property("UserName") - .HasMaxLength(256); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasName("UserNameIndex"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ClaimType"); - - b.Property("ClaimValue"); - - b.Property("UserId") - .IsRequired(); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasMaxLength(128); - - b.Property("ProviderKey") - .HasMaxLength(128); - - b.Property("ProviderDisplayName"); - - b.Property("UserId") - .IsRequired(); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId"); - - b.Property("RoleId"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId"); - - b.Property("LoginProvider") - .HasMaxLength(128); - - b.Property("Name") - .HasMaxLength(128); - - b.Property("Value"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } + AssertSql( + @"ALTER TABLE ""People"" RENAME COLUMN ""SomeColumn"" TO ""somecolumn"";"); } - public override void Can_diff_against_2_1_ASP_NET_Identity_model() + public override Task Create_index_with_filter() + => Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("Name"); + }), + builder => { }, + builder => builder.Entity("People").HasIndex("Name").HasFilter($"{DelimitIdentifier("Name")} IS NOT NULL"), + // Reverse engineering of index filters isn't supported in SQLite + model => Assert.Null(model.Tables.Single().Indexes.Single().Filter)); + + public override Task Create_unique_index_with_filter() + => Test( + builder => builder.Entity( + "People", e => + { + e.Property("Id"); + e.Property("Name"); + }), + builder => { }, + builder => builder.Entity("People").HasIndex("Name").IsUnique() + .HasFilter($"{DelimitIdentifier("Name")} IS NOT NULL AND {DelimitIdentifier("Name")} <> ''"), + // Reverse engineering of index filters isn't supported in SQLite + model => Assert.Null(model.Tables.Single().Indexes.Single().Filter)); + + public override async Task Rename_index() { - using var context = new ApplicationDbContext(); - DiffSnapshot(new AspNetIdentity21ModelSnapshot(), context); - } - - public class AspNetIdentity22ModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.0-preview1"); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken(); - - b.Property("Name") - .HasMaxLength(256); - - b.Property("NormalizedName") - .HasMaxLength(256); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasName("RoleNameIndex"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ClaimType"); - - b.Property("ClaimValue"); - - b.Property("RoleId") - .IsRequired(); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); + await base.Rename_index(); - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("AccessFailedCount"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken(); - - b.Property("Email") - .HasMaxLength(256); - - b.Property("EmailConfirmed"); - - b.Property("LockoutEnabled"); - - b.Property("LockoutEnd"); - - b.Property("NormalizedEmail") - .HasMaxLength(256); - - b.Property("NormalizedUserName") - .HasMaxLength(256); - - b.Property("PasswordHash"); - - b.Property("PhoneNumber"); - - b.Property("PhoneNumberConfirmed"); - - b.Property("SecurityStamp"); - - b.Property("TwoFactorEnabled"); - - b.Property("UserName") - .HasMaxLength(256); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasName("UserNameIndex"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ClaimType"); - - b.Property("ClaimValue"); - - b.Property("UserId") - .IsRequired(); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasMaxLength(128); + AssertSql( + @"DROP INDEX ""Foo""; +CREATE INDEX ""foo"" ON ""People"" (""FirstName"");"); + } - b.Property("ProviderKey") - .HasMaxLength(128); + public override Task Add_primary_key() + => AssertNotSupportedAsync(base.Add_primary_key, SqliteStrings.InvalidMigrationOperation("AlterColumnOperation")); - b.Property("ProviderDisplayName"); + public override Task Add_primary_key_with_name() + => AssertNotSupportedAsync(base.Add_primary_key_with_name, SqliteStrings.InvalidMigrationOperation("AlterColumnOperation")); - b.Property("UserId") - .IsRequired(); + public override Task Add_primary_key_composite_with_name() + => AssertNotSupportedAsync( + base.Add_primary_key_composite_with_name, SqliteStrings.InvalidMigrationOperation("AlterColumnOperation")); - b.HasKey("LoginProvider", "ProviderKey"); + public override Task Drop_primary_key() + => AssertNotSupportedAsync(base.Drop_primary_key, SqliteStrings.InvalidMigrationOperation("DropPrimaryKeyOperation")); - b.HasIndex("UserId"); + public override Task Add_foreign_key() + => AssertNotSupportedAsync(base.Add_foreign_key, SqliteStrings.InvalidMigrationOperation("AddForeignKeyOperation")); - b.ToTable("AspNetUserLogins"); - }); + public override Task Add_foreign_key_with_name() + => AssertNotSupportedAsync(base.Add_foreign_key_with_name, SqliteStrings.InvalidMigrationOperation("AddForeignKeyOperation")); - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId"); + public override Task Drop_foreign_key() + => AssertNotSupportedAsync(base.Drop_foreign_key, SqliteStrings.InvalidMigrationOperation("DropForeignKeyOperation")); - b.Property("RoleId"); + public override Task Add_unique_constraint() + => AssertNotSupportedAsync(base.Add_unique_constraint, SqliteStrings.InvalidMigrationOperation("AddUniqueConstraintOperation")); - b.HasKey("UserId", "RoleId"); + public override Task Add_unique_constraint_composite_with_name() + => AssertNotSupportedAsync( + base.Add_unique_constraint_composite_with_name, SqliteStrings.InvalidMigrationOperation("AddUniqueConstraintOperation")); - b.HasIndex("RoleId"); + public override Task Drop_unique_constraint() + => AssertNotSupportedAsync( + base.Drop_unique_constraint, SqliteStrings.InvalidMigrationOperation("DropUniqueConstraintOperation")); - b.ToTable("AspNetUserRoles"); - }); + public override Task Add_check_constraint_with_name() + => AssertNotSupportedAsync( + base.Add_check_constraint_with_name, SqliteStrings.InvalidMigrationOperation("CreateCheckConstraintOperation")); - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId"); + public override Task Drop_check_constraint() + => AssertNotSupportedAsync(base.Drop_check_constraint, SqliteStrings.InvalidMigrationOperation("DropCheckConstraintOperation")); - b.Property("LoginProvider") - .HasMaxLength(128); + public override Task Create_sequence() + => AssertNotSupportedAsync(base.Create_sequence, SqliteStrings.SequencesNotSupported); - b.Property("Name") - .HasMaxLength(128); + public override Task Create_sequence_all_settings() + => AssertNotSupportedAsync(base.Create_sequence, SqliteStrings.SequencesNotSupported); - b.Property("Value"); + public override Task Alter_sequence_all_settings() + => AssertNotSupportedAsync(base.Create_sequence, SqliteStrings.SequencesNotSupported); - b.HasKey("UserId", "LoginProvider", "Name"); + public override Task Alter_sequence_increment_by() + => AssertNotSupportedAsync(base.Create_sequence, SqliteStrings.SequencesNotSupported); - b.ToTable("AspNetUserTokens"); - }); + public override Task Drop_sequence() + => AssertNotSupportedAsync(base.Create_sequence, SqliteStrings.SequencesNotSupported); - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } + public override Task Rename_sequence() + => AssertNotSupportedAsync(base.Create_sequence, SqliteStrings.SequencesNotSupported); - public override void Can_diff_against_2_2_ASP_NET_Identity_model() - { - using var context = new ApplicationDbContext(); - DiffSnapshot(new AspNetIdentity22ModelSnapshot(), context); - } + public override Task Move_sequence() + => AssertNotSupportedAsync(base.Create_sequence, SqliteStrings.SequencesNotSupported); - public class AspNetIdentity30ModelSnapshot : ModelSnapshot + protected virtual async Task AssertNotSupportedAsync(Func action, string? message = null) { - protected override void BuildModel(ModelBuilder modelBuilder) + var ex = await Assert.ThrowsAsync(action); + if (message != null) { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.0.0"); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT") - .HasMaxLength(256); - - b.Property("NormalizedName") - .HasColumnType("TEXT") - .HasMaxLength(256); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasName("RoleNameIndex"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ClaimType") - .HasColumnType("TEXT"); - - b.Property("ClaimValue") - .HasColumnType("TEXT"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessFailedCount") - .HasColumnType("INTEGER"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("TEXT"); - - b.Property("Email") - .HasColumnType("TEXT") - .HasMaxLength(256); - - b.Property("EmailConfirmed") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnabled") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnd") - .HasColumnType("TEXT"); - - b.Property("NormalizedEmail") - .HasColumnType("TEXT") - .HasMaxLength(256); - - b.Property("NormalizedUserName") - .HasColumnType("TEXT") - .HasMaxLength(256); - - b.Property("PasswordHash") - .HasColumnType("TEXT"); - - b.Property("PhoneNumber") - .HasColumnType("TEXT"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("INTEGER"); - - b.Property("SecurityStamp") - .HasColumnType("TEXT"); - - b.Property("TwoFactorEnabled") - .HasColumnType("INTEGER"); - - b.Property("UserName") - .HasColumnType("TEXT") - .HasMaxLength(256); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasName("UserNameIndex"); - - b.ToTable("AspNetUsers"); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ClaimType") - .HasColumnType("TEXT"); - - b.Property("ClaimValue") - .HasColumnType("TEXT"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("TEXT") - .HasMaxLength(128); - - b.Property("ProviderKey") - .HasColumnType("TEXT") - .HasMaxLength(128); - - b.Property("ProviderDisplayName") - .HasColumnType("TEXT"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("TEXT"); - - b.Property("RoleId") - .HasColumnType("TEXT"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("TEXT"); - - b.Property("LoginProvider") - .HasColumnType("TEXT") - .HasMaxLength(128); - - b.Property("Name") - .HasColumnType("TEXT") - .HasMaxLength(128); - - b.Property("Value") - .HasColumnType("TEXT"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity( - "Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 + Assert.Equal(message, ex.Message); } } - public override void Can_diff_against_3_0_ASP_NET_Identity_model() + public class MigrationsSqliteFixture : MigrationsFixtureBase { - using var context = new ApplicationDbContext(); - DiffSnapshot(new AspNetIdentity30ModelSnapshot(), context); - } - } -} + protected override string StoreName { get; } = nameof(MigrationsSqliteTest); + protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; + public override TestHelpers TestHelpers => SqliteTestHelpers.Instance; -namespace ModelSnapshot22 -{ - public class Blog - { - public int Id { get; set; } - public string Name { get; set; } - - public ICollection Posts { get; set; } - } - - public class Post - { - public int Id { get; set; } - public string Title { get; set; } - public string Content { get; set; } - public DateTime EditDate { get; set; } - - public Blog Blog { get; set; } - } - - public class BloggingContext : DbContext - { - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseSqlite("DataSource=Test.db"); - - public DbSet Blogs { get; set; } - } -} - -namespace Identity30.Data -{ - public class ApplicationDbContext : IdentityDbContext - { - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseSqlite("DataSource=Test.db"); - - protected override void OnModelCreating(ModelBuilder builder) - { - base.OnModelCreating(builder); - - builder.Entity( - b => - { - b.HasIndex(u => u.NormalizedUserName).HasName("UserNameIndex").IsUnique(); - b.HasIndex(u => u.NormalizedEmail).HasName("EmailIndex"); - b.ToTable("AspNetUsers"); - }); - - builder.Entity>( - b => - { - b.ToTable("AspNetUserClaims"); - }); - - builder.Entity>( - b => - { - b.ToTable("AspNetUserLogins"); - }); - - builder.Entity>( - b => - { - b.ToTable("AspNetUserTokens"); - }); - - builder.Entity( - b => - { - b.HasIndex(r => r.NormalizedName).HasName("RoleNameIndex").IsUnique(); - b.ToTable("AspNetRoles"); - }); - - builder.Entity>( - b => - { - b.ToTable("AspNetRoleClaims"); - }); - - builder.Entity>( - b => - { - b.ToTable("AspNetUserRoles"); - }); + protected override IServiceCollection AddServices(IServiceCollection serviceCollection) + => base.AddServices(serviceCollection) + .AddScoped(); } } } diff --git a/test/EFCore.Sqlite.FunctionalTests/SqliteMigrationSqlGeneratorTest.cs b/test/EFCore.Sqlite.FunctionalTests/SqliteMigrationSqlGeneratorTest.cs deleted file mode 100644 index bb10ede5d44..00000000000 --- a/test/EFCore.Sqlite.FunctionalTests/SqliteMigrationSqlGeneratorTest.cs +++ /dev/null @@ -1,637 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Migrations.Operations; -using Microsoft.EntityFrameworkCore.Sqlite.Internal; -using Microsoft.EntityFrameworkCore.Sqlite.Metadata.Internal; -using Microsoft.EntityFrameworkCore.TestUtilities; -using Xunit; - -// ReSharper disable InconsistentNaming -namespace Microsoft.EntityFrameworkCore -{ - public class SqliteMigrationSqlGeneratorTest : MigrationSqlGeneratorTestBase - { - [ConditionalFact] - public virtual void It_lifts_foreign_key_additions() - { - Generate( - new CreateTableOperation - { - Name = "Pie", - Columns = - { - new AddColumnOperation - { - ClrType = typeof(int), - Name = "FlavorId", - ColumnType = "INT" - } - } - }, - new AddForeignKeyOperation - { - Table = "Pie", - PrincipalTable = "Flavor", - Columns = new[] { "FlavorId" }, - PrincipalColumns = new[] { "Id" } - }); - - AssertSql( - @"CREATE TABLE ""Pie"" ( - ""FlavorId"" INT NOT NULL, - FOREIGN KEY (""FlavorId"") REFERENCES ""Flavor"" (""Id"") -); -"); - } - - [ConditionalFact] - public virtual void DefaultValue_formats_literal_correctly() - { - Generate( - new CreateTableOperation - { - Name = "History", - Columns = - { - new AddColumnOperation - { - Name = "Event", - ClrType = typeof(string), - ColumnType = "TEXT", - DefaultValue = new DateTime(2015, 4, 12, 17, 5, 0) - } - } - }); - - AssertSql( - @"CREATE TABLE ""History"" ( - ""Event"" TEXT NOT NULL DEFAULT '2015-04-12 17:05:00' -); -"); - } - - [ConditionalTheory] - [InlineData(true, null)] - [InlineData(false, "PK_Id")] - public void CreateTableOperation_with_annotations(bool autoincrement, string pkName) - { - var addIdColumn = new AddColumnOperation - { - Name = "Id", - ClrType = typeof(long), - ColumnType = "INTEGER", - IsNullable = false - }; - if (autoincrement) - { - addIdColumn.AddAnnotation(SqliteAnnotationNames.Autoincrement, true); - } - - Generate( - new CreateTableOperation - { - Name = "People", - Columns = - { - addIdColumn, - new AddColumnOperation - { - Name = "EmployerId", - ClrType = typeof(int), - ColumnType = "int", - IsNullable = true - }, - new AddColumnOperation - { - Name = "SSN", - ClrType = typeof(string), - ColumnType = "char(11)", - IsNullable = true - } - }, - PrimaryKey = new AddPrimaryKeyOperation { Name = pkName, Columns = new[] { "Id" } }, - UniqueConstraints = { new AddUniqueConstraintOperation { Columns = new[] { "SSN" } } }, - ForeignKeys = - { - new AddForeignKeyOperation - { - Columns = new[] { "EmployerId" }, - PrincipalTable = "Companies", - PrincipalColumns = new[] { "Id" } - } - } - }); - - AssertSql( - $@"CREATE TABLE ""People"" ( - ""Id"" INTEGER NOT NULL{(pkName != null ? $@" CONSTRAINT ""{pkName}""" : "")} PRIMARY KEY{(autoincrement ? " AUTOINCREMENT," : ",")} - ""EmployerId"" int NULL, - ""SSN"" char(11) NULL, - UNIQUE (""SSN""), - FOREIGN KEY (""EmployerId"") REFERENCES ""Companies"" (""Id"") -); -"); - } - - [ConditionalFact] - public void CreateSchemaOperation_is_ignored() - { - Generate(new EnsureSchemaOperation()); - - Assert.Empty(Sql); - } - - public override void AddColumnOperation_with_defaultValue() - { - base.AddColumnOperation_with_defaultValue(); - - AssertSql( - @"ALTER TABLE ""People"" ADD ""Name"" varchar(30) NOT NULL DEFAULT 'John Doe'; -"); - } - - public override void AddColumnOperation_without_column_type() - { - base.AddColumnOperation_without_column_type(); - - AssertSql( - @"ALTER TABLE ""People"" ADD ""Alias"" TEXT NOT NULL; -"); - } - - public override void AddColumnOperation_with_defaultValueSql() - { - // Override base test because CURRENT_TIMESTAMP is not valid for AddColumn - Generate( - new AddColumnOperation - { - Table = "People", - Name = "Age", - ClrType = typeof(int), - ColumnType = "int", - IsNullable = true, - DefaultValueSql = "10" - }); - - AssertSql( - @"ALTER TABLE ""People"" ADD ""Age"" int NULL DEFAULT (10); -"); - } - - public override void AddColumnOperation_with_maxLength() - { - base.AddColumnOperation_with_maxLength(); - - // See issue #3698 - AssertSql( - @"ALTER TABLE ""Person"" ADD ""Name"" TEXT NULL; -"); - } - - public override void AddColumnOperation_with_maxLength_overridden() - { - base.AddColumnOperation_with_maxLength_overridden(); - - // See issue #3698 - AssertSql( - @"ALTER TABLE ""Person"" ADD ""Name"" TEXT NULL; -"); - } - - public override void AddColumnOperation_with_maxLength_on_derived() - { - base.AddColumnOperation_with_maxLength_on_derived(); - - // See issue #3698 - AssertSql( - @"ALTER TABLE ""Person"" ADD ""Name"" TEXT NULL; -"); - } - - public override void AddColumnOperation_with_shared_column() - { - base.AddColumnOperation_with_shared_column(); - - AssertSql( - @"ALTER TABLE ""Base"" ADD ""Foo"" TEXT NULL; -"); - } - - [ConditionalFact] - public void AddColumnOperation_with_computed_column_SQL() - { - var ex = Assert.Throws( - () => Generate( - new AddColumnOperation - { - Table = "People", - Name = "Birthday", - ClrType = typeof(DateTime), - ColumnType = "TEXT", - IsNullable = true, - ComputedColumnSql = "CURRENT_TIMESTAMP" - })); - Assert.Equal(SqliteStrings.ComputedColumnsNotSupported, ex.Message); - } - - [ConditionalFact] - public void DropSchemaOperation_is_ignored() - { - Generate(new DropSchemaOperation()); - - Assert.Empty(Sql); - } - - [ConditionalFact] - public void RestartSequenceOperation_not_supported() - { - var ex = Assert.Throws(() => Generate(new RestartSequenceOperation())); - Assert.Equal(SqliteStrings.SequencesNotSupported, ex.Message); - } - - public override void AddForeignKeyOperation_with_name() - { - var ex = Assert.Throws(() => base.AddForeignKeyOperation_with_name()); - Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(AddForeignKeyOperation)), ex.Message); - } - - public override void AddForeignKeyOperation_without_name() - { - var ex = Assert.Throws(() => base.AddForeignKeyOperation_without_name()); - Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(AddForeignKeyOperation)), ex.Message); - } - - public override void AddForeignKeyOperation_without_principal_columns() - { - var ex = Assert.Throws(() => base.AddForeignKeyOperation_without_name()); - Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(AddForeignKeyOperation)), ex.Message); - } - - public override void AddPrimaryKeyOperation_with_name() - { - var ex = Assert.Throws(() => base.AddPrimaryKeyOperation_with_name()); - Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(AddPrimaryKeyOperation)), ex.Message); - } - - public override void AddPrimaryKeyOperation_without_name() - { - var ex = Assert.Throws(() => base.AddPrimaryKeyOperation_without_name()); - Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(AddPrimaryKeyOperation)), ex.Message); - } - - public override void AddUniqueConstraintOperation_with_name() - { - var ex = Assert.Throws(() => base.AddUniqueConstraintOperation_with_name()); - Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(AddUniqueConstraintOperation)), ex.Message); - } - - public override void AddUniqueConstraintOperation_without_name() - { - var ex = Assert.Throws(() => base.AddUniqueConstraintOperation_without_name()); - Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(AddUniqueConstraintOperation)), ex.Message); - } - - public override void CreateCheckConstraintOperation_with_name() - { - var ex = Assert.Throws(() => base.CreateCheckConstraintOperation_with_name()); - Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(CreateCheckConstraintOperation)), ex.Message); - } - - public override void AlterColumnOperation() - { - var ex = Assert.Throws(() => base.AlterColumnOperation()); - Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(AlterColumnOperation)), ex.Message); - } - - public override void AlterColumnOperation_without_column_type() - { - var ex = Assert.Throws(() => base.AlterColumnOperation_without_column_type()); - Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(AlterColumnOperation)), ex.Message); - } - - [ConditionalFact] - public void AlterColumnOperation_computed() - { - var ex = Assert.Throws( - () => Generate( - new AlterColumnOperation - { - Table = "People", - Name = "FullName", - ClrType = typeof(string), - ComputedColumnSql = "FirstName || ' ' || LastName" - })); - Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(AlterColumnOperation)), ex.Message); - } - - public override void AlterSequenceOperation_with_minValue_and_maxValue() - { - var ex = Assert.Throws(() => base.AlterSequenceOperation_with_minValue_and_maxValue()); - Assert.Equal(SqliteStrings.SequencesNotSupported, ex.Message); - } - - public override void AlterSequenceOperation_without_minValue_and_maxValue() - { - var ex = Assert.Throws(() => base.AlterSequenceOperation_without_minValue_and_maxValue()); - Assert.Equal(SqliteStrings.SequencesNotSupported, ex.Message); - } - - [ConditionalFact] - public virtual void RenameColumnOperation() - { - Generate( - new RenameColumnOperation - { - Table = "People", - Name = "Name", - NewName = "FullName" - }); - - AssertSql( - @"ALTER TABLE ""People"" RENAME COLUMN ""Name"" TO ""FullName""; -"); - } - - [ConditionalFact] - public virtual void RenameIndexOperation() - { - Generate( - modelBuilder => modelBuilder.Entity( - "Person", - x => - { - x.Property("FullName"); - x.HasKey("FullName"); - x.HasIndex("FullName").IsUnique().HasFilter(@"""Id"" > 2"); - }), - new RenameIndexOperation - { - Table = "Person", - Name = "IX_Person_Name", - NewName = "IX_Person_FullName" - }); - - AssertSql( - @"DROP INDEX ""IX_Person_Name""; -CREATE UNIQUE INDEX ""IX_Person_FullName"" ON ""Person"" (""FullName"") WHERE ""Id"" > 2; -"); - } - - [ConditionalFact] - public virtual void RenameIndexOperations_throws_when_no_model() - { - var migrationBuilder = new MigrationBuilder("Sqlite"); - - migrationBuilder.RenameIndex( - table: "Person", - name: "IX_Person_Name", - newName: "IX_Person_FullName"); - - var ex = Assert.Throws( - () => Generate(migrationBuilder.Operations.ToArray())); - - Assert.Equal(SqliteStrings.InvalidMigrationOperation("RenameIndexOperation"), ex.Message); - } - - public override void RenameTableOperation_legacy() - { - base.RenameTableOperation_legacy(); - - AssertSql( - @"ALTER TABLE ""People"" RENAME TO ""Person""; -"); - } - - public override void RenameTableOperation() - { - base.RenameTableOperation(); - - AssertSql( - @"ALTER TABLE ""People"" RENAME TO ""Person""; -"); - } - - public override void CreateSequenceOperation_with_minValue_and_maxValue() - { - var ex = Assert.Throws(() => base.CreateSequenceOperation_with_minValue_and_maxValue()); - Assert.Equal(SqliteStrings.SequencesNotSupported, ex.Message); - } - - public override void CreateSequenceOperation_with_minValue_and_maxValue_not_long() - { - var ex = Assert.Throws(() => base.CreateSequenceOperation_with_minValue_and_maxValue_not_long()); - Assert.Equal(SqliteStrings.SequencesNotSupported, ex.Message); - } - - public override void CreateSequenceOperation_without_minValue_and_maxValue() - { - var ex = Assert.Throws(() => base.CreateSequenceOperation_without_minValue_and_maxValue()); - Assert.Equal(SqliteStrings.SequencesNotSupported, ex.Message); - } - - public override void DropColumnOperation() - { - var ex = Assert.Throws(() => base.DropColumnOperation()); - Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(DropColumnOperation)), ex.Message); - } - - public override void DropForeignKeyOperation() - { - var ex = Assert.Throws(() => base.DropForeignKeyOperation()); - Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(DropForeignKeyOperation)), ex.Message); - } - - public override void DropIndexOperation() - { - base.DropIndexOperation(); - - AssertSql( - @"DROP INDEX ""IX_People_Name""; -"); - } - - public override void DropPrimaryKeyOperation() - { - var ex = Assert.Throws(() => base.DropPrimaryKeyOperation()); - Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(DropPrimaryKeyOperation)), ex.Message); - } - - public override void DropSequenceOperation() - { - var ex = Assert.Throws(() => base.DropSequenceOperation()); - Assert.Equal(SqliteStrings.SequencesNotSupported, ex.Message); - } - - public override void DropUniqueConstraintOperation() - { - var ex = Assert.Throws(() => base.DropUniqueConstraintOperation()); - Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(DropUniqueConstraintOperation)), ex.Message); - } - - public override void DropCheckConstraintOperation() - { - var ex = Assert.Throws(() => base.DropCheckConstraintOperation()); - Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(DropCheckConstraintOperation)), ex.Message); - } - - [ConditionalFact] - public virtual void CreateTableOperation_old_autoincrement_annotation() - { - Generate( - new CreateTableOperation - { - Name = "People", - Columns = - { - new AddColumnOperation - { - Name = "Id", - Table = "People", - ClrType = typeof(int), - IsNullable = false, - ["Autoincrement"] = true - } - }, - PrimaryKey = new AddPrimaryKeyOperation { Columns = new[] { "Id" } } - }); - - AssertSql( - @"CREATE TABLE ""People"" ( - ""Id"" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT -); -"); - } - - [ConditionalFact] - public virtual void CreateTableOperation_has_comment() - { - Generate( - new CreateTableOperation - { - Name = "People", - Columns = - { - new AddColumnOperation - { - Name = "Id", - Table = "People", - ClrType = typeof(int), - IsNullable = false, - Comment = "The ID" - }, - new AddColumnOperation - { - Name = "UncommentedColumn1", - Table = "People", - ClrType = typeof(string), - IsNullable = false - }, - new AddColumnOperation - { - Name = "UncommentedColumn2", - Table = "People", - ClrType = typeof(string), - IsNullable = false - }, - new AddColumnOperation - { - Name = "Name", - Table = "People", - ClrType = typeof(string), - IsNullable = false, - Comment = "The Name" - } - } - }); - - AssertSql( - @"CREATE TABLE ""People"" ( - -- The ID - ""Id"" INTEGER NOT NULL, - - ""UncommentedColumn1"" TEXT NOT NULL, - - ""UncommentedColumn2"" TEXT NOT NULL, - - -- The Name - ""Name"" TEXT NOT NULL -); -"); - } - - [ConditionalFact] - public virtual void CreateTableOperation_has_multi_line_comment() - { - Generate( - new CreateTableOperation - { - Name = "People", - Columns = - { - new AddColumnOperation - { - Name = "Id", - Table = "People", - ClrType = typeof(int), - IsNullable = false, - Comment = @"This is a multi-line -comment. -More information can -be found in the docs." - } - } - }); - - AssertSql( - @"CREATE TABLE ""People"" ( - -- This is a multi-line - -- comment. - -- More information can - -- be found in the docs. - ""Id"" INTEGER NOT NULL -); -"); - } - - [ConditionalFact] - public virtual void CreateTableOperation_has_multi_line_table_comment() - { - Generate( - new CreateTableOperation - { - Name = "People", - Comment = @"Table level comment -that continues onto another line", - Columns = - { - new AddColumnOperation - { - Name = "Id", - Table = "People", - ClrType = typeof(int), - IsNullable = false, - Comment = "My Comment" - } - } - }); - - AssertSql( - @"CREATE TABLE ""People"" ( - -- Table level comment - -- that continues onto another line - - -- My Comment - ""Id"" INTEGER NOT NULL -); -"); - } - - public SqliteMigrationSqlGeneratorTest() - : base(SqliteTestHelpers.Instance) - { - } - } -} diff --git a/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationSqlGeneratorTest.cs b/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationSqlGeneratorTest.cs new file mode 100644 index 00000000000..a88c81d7e8d --- /dev/null +++ b/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationSqlGeneratorTest.cs @@ -0,0 +1,281 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Sqlite.Internal; +using Microsoft.EntityFrameworkCore.Sqlite.Metadata.Internal; +using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; + +namespace Microsoft.EntityFrameworkCore.Migrations +{ + public class SqliteMigrationSqlGeneratorTest : MigrationSqlGeneratorTestBase + { + [ConditionalFact] + public virtual void It_lifts_foreign_key_additions() + { + Generate( + new CreateTableOperation + { + Name = "Pie", + Columns = + { + new AddColumnOperation + { + ClrType = typeof(int), + Name = "FlavorId", + ColumnType = "INT" + } + } + }, + new AddForeignKeyOperation + { + Table = "Pie", + PrincipalTable = "Flavor", + Columns = new[] { "FlavorId" }, + PrincipalColumns = new[] { "Id" } + }); + + AssertSql( + @"CREATE TABLE ""Pie"" ( + ""FlavorId"" INT NOT NULL, + FOREIGN KEY (""FlavorId"") REFERENCES ""Flavor"" (""Id"") +); +"); + } + + [ConditionalFact] + public virtual void DefaultValue_formats_literal_correctly() + { + Generate( + new CreateTableOperation + { + Name = "History", + Columns = + { + new AddColumnOperation + { + Name = "Event", + ClrType = typeof(string), + ColumnType = "TEXT", + DefaultValue = new DateTime(2015, 4, 12, 17, 5, 0) + } + } + }); + + AssertSql( + @"CREATE TABLE ""History"" ( + ""Event"" TEXT NOT NULL DEFAULT '2015-04-12 17:05:00' +); +"); + } + + [ConditionalTheory] + [InlineData(true, null)] + [InlineData(false, "PK_Id")] + public void CreateTableOperation_with_annotations(bool autoincrement, string pkName) + { + var addIdColumn = new AddColumnOperation + { + Name = "Id", + ClrType = typeof(long), + ColumnType = "INTEGER", + IsNullable = false + }; + if (autoincrement) + { + addIdColumn.AddAnnotation(SqliteAnnotationNames.Autoincrement, true); + } + + Generate( + new CreateTableOperation + { + Name = "People", + Columns = + { + addIdColumn, + new AddColumnOperation + { + Name = "EmployerId", + ClrType = typeof(int), + ColumnType = "int", + IsNullable = true + }, + new AddColumnOperation + { + Name = "SSN", + ClrType = typeof(string), + ColumnType = "char(11)", + IsNullable = true + } + }, + PrimaryKey = new AddPrimaryKeyOperation { Name = pkName, Columns = new[] { "Id" } }, + UniqueConstraints = { new AddUniqueConstraintOperation { Columns = new[] { "SSN" } } }, + ForeignKeys = + { + new AddForeignKeyOperation + { + Columns = new[] { "EmployerId" }, + PrincipalTable = "Companies", + PrincipalColumns = new[] { "Id" } + } + } + }); + + AssertSql( + $@"CREATE TABLE ""People"" ( + ""Id"" INTEGER NOT NULL{(pkName != null ? $@" CONSTRAINT ""{pkName}""" : "")} PRIMARY KEY{(autoincrement ? " AUTOINCREMENT," : ",")} + ""EmployerId"" int NULL, + ""SSN"" char(11) NULL, + UNIQUE (""SSN""), + FOREIGN KEY (""EmployerId"") REFERENCES ""Companies"" (""Id"") +); +"); + } + + [ConditionalFact] + public void CreateSchemaOperation_is_ignored() + { + Generate(new EnsureSchemaOperation()); + + Assert.Empty(Sql); + } + + public override void AddColumnOperation_without_column_type() + { + base.AddColumnOperation_without_column_type(); + + AssertSql( + @"ALTER TABLE ""People"" ADD ""Alias"" TEXT NOT NULL; +"); + } + + public override void AddColumnOperation_with_maxLength_overridden() + { + base.AddColumnOperation_with_maxLength_overridden(); + + // See issue #3698 + AssertSql( + @"ALTER TABLE ""Person"" ADD ""Name"" TEXT NULL; +"); + } + + [ConditionalFact] + public void AddColumnOperation_with_computed_column_SQL() + { + var ex = Assert.Throws( + () => Generate( + new AddColumnOperation + { + Table = "People", + Name = "Birthday", + ClrType = typeof(DateTime), + ColumnType = "TEXT", + IsNullable = true, + ComputedColumnSql = "CURRENT_TIMESTAMP" + })); + Assert.Equal(SqliteStrings.ComputedColumnsNotSupported, ex.Message); + } + + [ConditionalFact] + public void DropSchemaOperation_is_ignored() + { + Generate(new DropSchemaOperation()); + + Assert.Empty(Sql); + } + + [ConditionalFact] + public void RestartSequenceOperation_not_supported() + { + var ex = Assert.Throws(() => Generate(new RestartSequenceOperation())); + Assert.Equal(SqliteStrings.SequencesNotSupported, ex.Message); + } + + public override void AddForeignKeyOperation_without_principal_columns() + { + var ex = Assert.Throws(() => base.AddForeignKeyOperation_without_principal_columns()); + Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(AddForeignKeyOperation)), ex.Message); + } + + public override void AlterColumnOperation_without_column_type() + { + var ex = Assert.Throws(() => base.AlterColumnOperation_without_column_type()); + Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(AlterColumnOperation)), ex.Message); + } + + [ConditionalFact] + public void AlterColumnOperation_computed() + { + var ex = Assert.Throws( + () => Generate( + new AlterColumnOperation + { + Table = "People", + Name = "FullName", + ClrType = typeof(string), + ComputedColumnSql = "FirstName || ' ' || LastName" + })); + Assert.Equal(SqliteStrings.InvalidMigrationOperation(nameof(AlterColumnOperation)), ex.Message); + } + + [ConditionalFact] + public virtual void RenameIndexOperations_throws_when_no_model() + { + var migrationBuilder = new MigrationBuilder("Sqlite"); + + migrationBuilder.RenameIndex( + table: "Person", + name: "IX_Person_Name", + newName: "IX_Person_FullName"); + + var ex = Assert.Throws( + () => Generate(migrationBuilder.Operations.ToArray())); + + Assert.Equal(SqliteStrings.InvalidMigrationOperation("RenameIndexOperation"), ex.Message); + } + + public override void RenameTableOperation_legacy() + { + base.RenameTableOperation_legacy(); + + AssertSql( + @"ALTER TABLE ""People"" RENAME TO ""Person""; +"); + } + + [ConditionalFact] + public virtual void CreateTableOperation_old_autoincrement_annotation() + { + Generate( + new CreateTableOperation + { + Name = "People", + Columns = + { + new AddColumnOperation + { + Name = "Id", + Table = "People", + ClrType = typeof(int), + IsNullable = false, + ["Autoincrement"] = true + } + }, + PrimaryKey = new AddPrimaryKeyOperation { Columns = new[] { "Id" } } + }); + + AssertSql( + @"CREATE TABLE ""People"" ( + ""Id"" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT +); +"); + } + + public SqliteMigrationSqlGeneratorTest() + : base(SqliteTestHelpers.Instance) + { + } + } +}