// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading.Tasks; using EntityFrameworkCore.Jet.FunctionalTests.TestUtilities; using EntityFrameworkCore.Jet.Internal; using EntityFrameworkCore.Jet.Metadata.Internal; using EntityFrameworkCore.Jet.Scaffolding.Internal; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Scaffolding; using Microsoft.EntityFrameworkCore.Scaffolding.Metadata.Internal; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.DependencyInjection; using Xunit; using Xunit.Abstractions; // ReSharper disable StringLiteralTypo // ReSharper disable UnusedParameter.Local // ReSharper disable ParameterOnlyUsedForPreconditionCheck.Local #nullable enable namespace EntityFrameworkCore.Jet.FunctionalTests.Migrations; public class MigrationsJetTest : MigrationsTestBase { protected static string EOL => Environment.NewLine; public MigrationsJetTest(MigrationsJetFixture fixture, ITestOutputHelper testOutputHelper) : base(fixture) { Fixture.TestSqlLoggerFactory.Clear(); Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } public override async Task Create_table() { await base.Create_table(); AssertSql( """ CREATE TABLE [People] ( [Id] int NOT NULL IDENTITY, [Name] nvarchar(max) NULL, CONSTRAINT [PK_People] PRIMARY KEY ([Id]) ); """); } public override async Task Create_table_all_settings() { 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 IDENTITY, [EmployerId] int NOT NULL, [SSN] nvarchar(11) COLLATE German_PhoneBook_CI_AS NOT NULL, CONSTRAINT [PK_People] PRIMARY KEY ([CustomId]), CONSTRAINT [AK_People_SSN] UNIQUE ([SSN]), CONSTRAINT [CK_People_EmployerId] CHECK ([EmployerId] > 0), CONSTRAINT [FK_People_Employers_EmployerId] FOREIGN KEY ([EmployerId]) REFERENCES [Employers] ([Id]) ON DELETE CASCADE ); DECLARE @description AS sql_variant; SET @description = N'Table comment'; EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', N'dbo2', 'TABLE', N'People'; SET @description = N'Employer ID comment'; EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', N'dbo2', 'TABLE', N'People', 'COLUMN', N'EmployerId'; """, // """ CREATE INDEX [IX_People_EmployerId] ON [dbo2].[People] ([EmployerId]); """); } public override async Task Create_table_no_key() { await base.Create_table_no_key(); AssertSql( """ CREATE TABLE [Anonymous] ( [SomeColumn] int NOT NULL ); """); } public override async Task Create_table_with_comments() { await base.Create_table_with_comments(); AssertSql( """ CREATE TABLE [People] ( [Id] int NOT NULL IDENTITY, [Name] nvarchar(max) NULL, CONSTRAINT [PK_People] PRIMARY KEY ([Id]) ); DECLARE @defaultSchema AS sysname; SET @defaultSchema = SCHEMA_NAME(); DECLARE @description AS sql_variant; SET @description = N'Table comment'; EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'People'; SET @description = N'Column comment'; EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'Name'; """); } public override async Task Create_table_with_multiline_comments() { await base.Create_table_with_multiline_comments(); AssertSql( """ CREATE TABLE [People] ( [Id] int NOT NULL IDENTITY, [Name] nvarchar(max) NULL, CONSTRAINT [PK_People] PRIMARY KEY ([Id]) ); DECLARE @defaultSchema AS sysname; SET @defaultSchema = SCHEMA_NAME(); DECLARE @description AS sql_variant; SET @description = CONCAT(N'This is a multi-line', NCHAR(13), NCHAR(10), N'table comment.', NCHAR(13), NCHAR(10), N'More information can', NCHAR(13), NCHAR(10), N'be found in the docs.'); EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'People'; SET @description = CONCAT(N'This is a multi-line', NCHAR(10), N'column comment.', NCHAR(10), N'More information can', NCHAR(10), N'be found in the docs.'); EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'Name'; """); } public override async Task Create_table_with_computed_column(bool? stored) { await base.Create_table_with_computed_column(stored); var storedSql = stored == true ? " PERSISTED" : ""; AssertSql( $""" CREATE TABLE [People] ( [Id] int NOT NULL IDENTITY, [Sum] AS [X] + [Y]{storedSql}, [X] int NOT NULL, [Y] int NOT NULL, CONSTRAINT [PK_People] PRIMARY KEY ([Id]) ); """); } public override async Task Create_table_with_json_column() { await base.Create_table_with_json_column(); AssertSql( """ CREATE TABLE [Entity] ( [Id] int NOT NULL IDENTITY, [Name] nvarchar(max) NULL, [OwnedCollection] nvarchar(max) NULL, [OwnedReference] nvarchar(max) NULL, [OwnedRequiredReference] nvarchar(max) NOT NULL, CONSTRAINT [PK_Entity] PRIMARY KEY ([Id]) ); """); } public override async Task Create_table_with_json_column_explicit_json_column_names() { await base.Create_table_with_json_column_explicit_json_column_names(); AssertSql( """ CREATE TABLE [Entity] ( [Id] int NOT NULL IDENTITY, [Name] nvarchar(max) NULL, [json_collection] nvarchar(max) NULL, [json_reference] nvarchar(max) NULL, CONSTRAINT [PK_Entity] PRIMARY KEY ([Id]) ); """); } [ConditionalFact] public virtual async Task Create_table_with_identity_column_value_converter() { await Test( _ => { }, builder => builder.UseJetIdentityColumns() .Entity("People").Property("IdentityColumn").HasConversion().ValueGeneratedOnAdd(), model => { var table = Assert.Single(model.Tables); var column = Assert.Single(table.Columns, c => c.Name == "IdentityColumn"); Assert.Equal(ValueGenerated.OnAdd, column.ValueGenerated); }); AssertSql( """ CREATE TABLE [People] ( [IdentityColumn] smallint NOT NULL IDENTITY ); """); } public override async Task Drop_table() { await base.Drop_table(); AssertSql( """ DROP TABLE [People]; """); } public override async Task Alter_table_add_comment() { await base.Alter_table_add_comment(); AssertSql( """ DECLARE @defaultSchema AS sysname; SET @defaultSchema = SCHEMA_NAME(); DECLARE @description AS sql_variant; SET @description = N'Table comment'; EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'People'; """); } public override async Task Alter_table_add_comment_non_default_schema() { await base.Alter_table_add_comment_non_default_schema(); AssertSql( """ DECLARE @description AS sql_variant; SET @description = N'Table comment'; EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', N'SomeOtherSchema', 'TABLE', N'People'; """); } public override async Task Alter_table_change_comment() { await base.Alter_table_change_comment(); AssertSql( """ DECLARE @defaultSchema AS sysname; SET @defaultSchema = SCHEMA_NAME(); DECLARE @description AS sql_variant; EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema, 'TABLE', N'People'; SET @description = N'Table comment2'; EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'People'; """); } public override async Task Alter_table_remove_comment() { await base.Alter_table_remove_comment(); AssertSql( """ DECLARE @defaultSchema AS sysname; SET @defaultSchema = SCHEMA_NAME(); DECLARE @description AS sql_variant; EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema, 'TABLE', N'People'; """); } public override async Task Rename_table() { await base.Rename_table(); AssertSql( """ ALTER TABLE [People] DROP CONSTRAINT [PK_People]; """, // """ EXEC sp_rename '[People]', 'Persons', 'OBJECT'; """, // """ ALTER TABLE [Persons] ADD CONSTRAINT [PK_Persons] PRIMARY KEY ([Id]); """); } 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 '[People]', N'Persons', 'OBJECT'; """, // """ ALTER TABLE [Persons] ADD CONSTRAINT [PK_Persons] PRIMARY KEY ([Id]); """); } public override async Task Rename_table_with_json_column() { await base.Rename_table_with_json_column(); AssertSql( """ ALTER TABLE [Entities] DROP CONSTRAINT [PK_Entities]; """, // """ EXEC sp_rename N'[Entities]', N'NewEntities', 'OBJECT'; """, // """ ALTER TABLE [NewEntities] ADD CONSTRAINT [PK_NewEntities] PRIMARY KEY ([Id]); """); } public override async Task Move_table() { await base.Move_table(); AssertSql( """ IF SCHEMA_ID(N'TestTableSchema') IS NULL EXEC(N'CREATE SCHEMA [TestTableSchema];'); """, // """ ALTER SCHEMA [TestTableSchema] TRANSFER [TestTable]; """); } public override async Task Create_schema() { 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 IDENTITY, CONSTRAINT [PK_People] PRIMARY KEY ([Id]) ); """); } [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 IDENTITY, CONSTRAINT [PK_People] PRIMARY KEY ([Id]) ); """); } public override async Task Add_column_with_defaultValue_string() { await base.Add_column_with_defaultValue_string(); AssertSql( """ ALTER TABLE [People] ADD [Name] nvarchar(max) NOT NULL DEFAULT N'John Doe'; """); } public override async Task Add_column_with_defaultValue_datetime() { await base.Add_column_with_defaultValue_datetime(); AssertSql( """ ALTER TABLE [People] ADD [Birthday] datetime2 NOT NULL DEFAULT '2015-04-12T17:05:00.0000000'; """); } [ConditionalTheory] [InlineData(0, "", 1234567)] [InlineData(1, ".1", 1234567)] [InlineData(2, ".12", 1234567)] [InlineData(3, ".123", 1234567)] [InlineData(4, ".1234", 1234567)] [InlineData(5, ".12345", 1234567)] [InlineData(6, ".123456", 1234567)] [InlineData(7, ".1234567", 1234567)] [InlineData(7, ".1200000", 1200000)] //should this really output trailing zeros? public async Task Add_column_with_defaultValue_datetime_with_explicit_precision(int precision, string fractionalSeconds, int ticksToAdd) { await Test( builder => builder.Entity("People").Property("Id"), builder => { }, builder => builder.Entity("People").Property("Birthday").HasPrecision(precision) .HasDefaultValue(new DateTime(2015, 4, 12, 17, 5, 0).AddTicks(ticksToAdd)), 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); }); AssertSql( $""" ALTER TABLE [People] ADD [Birthday] datetime2({precision}) NOT NULL DEFAULT '2015-04-12T17:05:00{fractionalSeconds}'; """); } [ConditionalTheory] [InlineData(0, "", 1234567)] [InlineData(1, ".1", 1234567)] [InlineData(2, ".12", 1234567)] [InlineData(3, ".123", 1234567)] [InlineData(4, ".1234", 1234567)] [InlineData(5, ".12345", 1234567)] [InlineData(6, ".123456", 1234567)] [InlineData(7, ".1234567", 1234567)] [InlineData(7, ".1200000", 1200000)] //should this really output trailing zeros? public async Task Add_column_with_defaultValue_datetimeoffset_with_explicit_precision( int precision, string fractionalSeconds, int ticksToAdd) { await Test( builder => builder.Entity("People").Property("Id"), builder => { }, builder => builder.Entity("People").Property("Birthday").HasPrecision(precision) .HasDefaultValue(new DateTimeOffset(new DateTime(2015, 4, 12, 17, 5, 0).AddTicks(ticksToAdd), TimeSpan.FromHours(10))), 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); }); AssertSql( $""" ALTER TABLE [People] ADD [Birthday] datetimeoffset({precision}) NOT NULL DEFAULT '2015-04-12T17:05:00{fractionalSeconds}+10:00'; """); } [ConditionalTheory] [InlineData(0, "", 1234567)] [InlineData(1, ".1", 1234567)] [InlineData(2, ".12", 1234567)] [InlineData(3, ".123", 1234567)] [InlineData(4, ".1234", 1234567)] [InlineData(5, ".12345", 1234567)] [InlineData(6, ".123456", 1234567)] [InlineData(7, ".1234567", 1234567)] [InlineData(7, ".12", 1200000)] public async Task Add_column_with_defaultValue_time_with_explicit_precision(int precision, string fractionalSeconds, int ticksToAdd) { await Test( builder => builder.Entity("People").Property("Id"), builder => { }, builder => builder.Entity("People").Property("Age").HasPrecision(precision) .HasDefaultValue( TimeSpan.Parse("12:34:56", CultureInfo.InvariantCulture).Add(TimeSpan.FromTicks(ticksToAdd))), model => { var table = Assert.Single(model.Tables); Assert.Equal(2, table.Columns.Count); var birthdayColumn = Assert.Single(table.Columns, c => c.Name == "Age"); Assert.False(birthdayColumn.IsNullable); }); AssertSql( $""" ALTER TABLE [People] ADD [Age] time({precision}) NOT NULL DEFAULT '12:34:56{fractionalSeconds}'; """); } [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); }); AssertSql( """ ALTER TABLE [People] ADD [Birthday] datetime NOT NULL DEFAULT '2019-01-01T00:00:00.000'; """); } [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); }); AssertSql( """ ALTER TABLE [People] ADD [Birthday] smalldatetime NOT NULL DEFAULT '2019-01-01T00:00:00'; """); } [ConditionalFact] public virtual async Task Add_column_with_rowversion() { 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()); }); AssertSql( """ ALTER TABLE [People] ADD [RowVersion] rowversion NULL; """); } [ConditionalFact] public virtual async Task Add_column_with_rowversion_and_value_conversion() { await Test( builder => builder.Entity("People").Property("Id"), builder => { }, builder => builder.Entity("People").Property("RowVersion") .IsRowVersion() .HasConversion(), 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()); }); AssertSql( """ ALTER TABLE [People] ADD [RowVersion] rowversion NOT NULL; """); } public override async Task Add_column_with_defaultValueSql() { await base.Add_column_with_defaultValueSql(); AssertSql( """ ALTER TABLE [People] ADD [Sum] int NOT NULL DEFAULT (1 + 2); """); } public override async Task Add_json_columns_to_existing_table() { await base.Add_json_columns_to_existing_table(); AssertSql( """ ALTER TABLE [Entity] ADD [OwnedCollection] nvarchar(max) NULL; """, // """ ALTER TABLE [Entity] ADD [OwnedReference] nvarchar(max) NULL; """, // """ ALTER TABLE [Entity] ADD [OwnedRequiredReference] nvarchar(max) NOT NULL DEFAULT N'{}'; """); } public override async Task Add_column_with_computedSql(bool? stored) { await base.Add_column_with_computedSql(stored); var computedColumnTypeSql = stored == true ? " PERSISTED" : ""; AssertSql( $""" ALTER TABLE [People] ADD [Sum] AS [X] + [Y]{computedColumnTypeSql}; """); } [ConditionalFact] public virtual async Task Add_column_generates_exec_when_computed_and_idempotent() { await Test( builder => builder.Entity("People").Property("Id"), builder => { }, builder => builder.Entity("People").Property("IdPlusOne").HasComputedColumnSql("[Id] + 1"), model => { var table = Assert.Single(model.Tables); Assert.Equal(2, table.Columns.Count); var column = Assert.Single(table.Columns, c => c.Name == "IdPlusOne"); Assert.Equal("([Id]+(1))", column.ComputedColumnSql); }, migrationsSqlGenerationOptions: MigrationsSqlGenerationOptions.Idempotent); AssertSql( """ EXEC(N'ALTER TABLE [People] ADD [IdPlusOne] AS [Id] + 1'); """); } public override async Task Add_column_with_required() { await base.Add_column_with_required(); AssertSql( """ ALTER TABLE [People] ADD [Name] nvarchar(max) NOT NULL DEFAULT N''; """); } public override async Task Add_column_with_ansi() { await base.Add_column_with_ansi(); AssertSql( """ ALTER TABLE [People] ADD [Name] varchar(max) NULL; """); } public override async Task Add_column_with_max_length() { await base.Add_column_with_max_length(); AssertSql( """ ALTER TABLE [People] ADD [Name] nvarchar(30) NULL; """); } public override async Task Add_column_with_max_length_on_derived() { await base.Add_column_with_max_length_on_derived(); Assert.Empty(Fixture.TestSqlLoggerFactory.SqlStatements); } public override async Task Add_column_with_fixed_length() { await base.Add_column_with_fixed_length(); AssertSql( """ ALTER TABLE [People] ADD [Name] nchar(100) NULL; """); } public override async Task Add_column_with_comment() { await base.Add_column_with_comment(); AssertSql( """ ALTER TABLE [People] ADD [FullName] nvarchar(max) NULL; DECLARE @defaultSchema AS sysname; SET @defaultSchema = SCHEMA_NAME(); DECLARE @description AS sql_variant; SET @description = N'My comment'; EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'FullName'; """); } public override async Task Add_column_with_collation() { await base.Add_column_with_collation(); AssertSql( """ ALTER TABLE [People] ADD [Name] nvarchar(max) COLLATE German_PhoneBook_CI_AS NULL; """); } public override async Task Add_column_computed_with_collation(bool stored) { await base.Add_column_computed_with_collation(stored); AssertSql( stored ? """ALTER TABLE [People] ADD [Name] AS 'hello' COLLATE German_PhoneBook_CI_AS PERSISTED;""" : """ALTER TABLE [People] ADD [Name] AS 'hello' COLLATE German_PhoneBook_CI_AS;"""); } public override async Task Add_column_shared() { await base.Add_column_shared(); AssertSql(); } public override async Task Add_column_with_check_constraint() { await base.Add_column_with_check_constraint(); AssertSql( """ ALTER TABLE [People] ADD [DriverLicense] int NOT NULL DEFAULT 0; """, // """ ALTER TABLE [People] ADD CONSTRAINT [CK_People_Foo] CHECK ([DriverLicense] > 0); """); } [ConditionalFact] public virtual async Task Add_column_identity() { await Test( builder => builder.Entity("People").Property("Id"), builder => { }, builder => builder.Entity("People").Property("IdentityColumn").UseJetIdentityColumn(), model => { var table = Assert.Single(model.Tables); var column = Assert.Single(table.Columns, c => c.Name == "IdentityColumn"); Assert.Equal(ValueGenerated.OnAdd, column.ValueGenerated); }); AssertSql( """ ALTER TABLE [People] ADD [IdentityColumn] int NOT NULL IDENTITY; """); } [ConditionalFact] public virtual async Task Add_column_identity_seed_increment() { await Test( builder => builder.Entity("People").Property("Id"), builder => { }, builder => builder.Entity("People").Property("IdentityColumn").UseJetIdentityColumn(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]); }); AssertSql( """ ALTER TABLE [People] ADD [IdentityColumn] int NOT NULL IDENTITY(100, 5); """); } [ConditionalFact] public virtual async Task Add_column_identity_seed_increment_for_TPC() { await Test( buildCommonAction: builder => { builder.Entity("Animal").UseTpcMappingStrategy().Property("Id"); builder.Entity("Cat").HasBaseType("Animal").ToTable("Cats"); builder.Entity("Dog").HasBaseType("Animal").ToTable("Dogs"); }, buildSourceAction:builder => { }, buildTargetAction:builder => { builder.Entity("Animal") .Property("IdentityColumn"); builder.Entity("Cat").ToTable("Cats", tb => tb.Property("IdentityColumn").UseJetIdentityColumn(1, 2)); builder.Entity("Dog").ToTable("Dogs", tb => tb.Property("IdentityColumn").UseJetIdentityColumn(2, 2)); }, asserter:model => { Assert.Collection( model.Tables, t => { Assert.Equal("Animal", t.Name); var column = Assert.Single(t.Columns, c => c.Name == "IdentityColumn"); Assert.Null(column.ValueGenerated); }, t => { Assert.Equal("Cats", t.Name); var column = Assert.Single(t.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]); }, t => { Assert.Equal("Dogs", t.Name); var column = Assert.Single(t.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]); }); }); AssertSql( """ ALTER TABLE [Dogs] ADD [IdentityColumn] int NOT NULL IDENTITY(2, 2); """, // """ ALTER TABLE [Cats] ADD [IdentityColumn] int NOT NULL IDENTITY(1, 2); """, // """ ALTER TABLE [Animal] ADD [IdentityColumn] int NOT NULL DEFAULT 0; """); } public override async Task Alter_column_change_type() { 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; """); } public override async Task Alter_column_make_required() { 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 + '];'); UPDATE [People] SET [SomeColumn] = N'' WHERE [SomeColumn] IS NULL; ALTER TABLE [People] ALTER COLUMN [SomeColumn] nvarchar(max) NOT NULL; ALTER TABLE [People] ADD DEFAULT N'' FOR [SomeColumn]; """); } public override async Task Alter_column_make_required_with_null_data() { await base.Alter_column_make_required_with_null_data(); 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 + '];'); UPDATE [People] SET [SomeColumn] = N'' WHERE [SomeColumn] IS NULL; ALTER TABLE [People] ALTER COLUMN [SomeColumn] nvarchar(max) NOT NULL; ALTER TABLE [People] ADD DEFAULT N'' FOR [SomeColumn]; """); } [ConditionalFact] public override async Task Alter_column_make_required_with_index() { 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 + '];'); UPDATE [People] SET [SomeColumn] = N'' WHERE [SomeColumn] IS NULL; ALTER TABLE [People] ALTER COLUMN [SomeColumn] nvarchar(450) NOT NULL; ALTER TABLE [People] ADD DEFAULT N'' FOR [SomeColumn]; CREATE INDEX [IX_People_SomeColumn] ON [People] ([SomeColumn]); """); } [ConditionalFact] public override async Task Alter_column_make_required_with_composite_index() { 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 + '];'); UPDATE [People] SET [FirstName] = N'' WHERE [FirstName] IS NULL; ALTER TABLE [People] ALTER COLUMN [FirstName] nvarchar(450) NOT NULL; ALTER TABLE [People] ADD DEFAULT N'' FOR [FirstName]; CREATE INDEX [IX_People_FirstName_LastName] ON [People] ([FirstName], [LastName]); """); } public override async Task Alter_column_make_computed(bool? stored) { await base.Alter_column_make_computed(stored); var computedColumnTypeSql = stored == true ? " PERSISTED" : ""; 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'Sum'); IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); ALTER TABLE [People] DROP COLUMN [Sum]; ALTER TABLE [People] ADD [Sum] AS [X] + [Y]{computedColumnTypeSql}; """); } public override async Task Alter_column_change_computed() { 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'Sum'); IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); ALTER TABLE [People] DROP COLUMN [Sum]; ALTER TABLE [People] ADD [Sum] AS [X] - [Y]; """); } public override async Task Alter_column_change_computed_recreates_indexes() { await base.Alter_column_change_computed_recreates_indexes(); AssertSql( """ DROP INDEX [IX_People_Sum] 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'Sum'); IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); ALTER TABLE [People] DROP COLUMN [Sum]; ALTER TABLE [People] ADD [Sum] AS [X] - [Y]; """, // """ CREATE INDEX [IX_People_Sum] ON [People] ([Sum]); """); } public override async Task Alter_column_change_computed_type() { await base.Alter_column_change_computed_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'Sum'); IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); ALTER TABLE [People] DROP COLUMN [Sum]; ALTER TABLE [People] ADD [Sum] AS [X] + [Y] PERSISTED; """); } public override async Task Alter_column_make_non_computed() { await base.Alter_column_make_non_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'Sum'); IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); ALTER TABLE [People] DROP COLUMN [Sum]; ALTER TABLE [People] ADD [Sum] int NOT NULL; """); } [ConditionalFact] public override async Task Alter_column_add_comment() { await base.Alter_column_add_comment(); AssertSql( """ DECLARE @defaultSchema AS sysname; SET @defaultSchema = SCHEMA_NAME(); DECLARE @description AS sql_variant; SET @description = N'Some comment'; EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'Id'; """); } [ConditionalFact] public override async Task Alter_computed_column_add_comment() { await base.Alter_computed_column_add_comment(); AssertSql( """ DECLARE @defaultSchema AS sysname; SET @defaultSchema = SCHEMA_NAME(); DECLARE @description AS sql_variant; SET @description = N'Some comment'; EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'SomeColumn'; """); } [ConditionalFact] public override async Task Alter_column_change_comment() { await base.Alter_column_change_comment(); AssertSql( """ DECLARE @defaultSchema AS sysname; SET @defaultSchema = SCHEMA_NAME(); DECLARE @description AS sql_variant; EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'Id'; SET @description = N'Some comment2'; EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'Id'; """); } [ConditionalFact] public override async Task Alter_column_remove_comment() { await base.Alter_column_remove_comment(); AssertSql( """ DECLARE @defaultSchema AS sysname; SET @defaultSchema = SCHEMA_NAME(); DECLARE @description AS sql_variant; EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'Id'; """); } [ConditionalFact] public override async Task Alter_column_set_collation() { await base.Alter_column_set_collation(); 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(max) COLLATE German_PhoneBook_CI_AS NULL; """); } [ConditionalFact] public virtual async Task Alter_column_set_collation_with_index() { await Test( builder => builder.Entity( "People", e => { e.Property("Name"); e.HasIndex("Name"); }), builder => { }, builder => builder.Entity("People").Property("Name") .UseCollation(NonDefaultCollation), model => { var nameColumn = Assert.Single(Assert.Single(model.Tables).Columns); Assert.Equal(NonDefaultCollation, nameColumn.Collation); }); AssertSql( """ DROP INDEX [IX_People_Name] 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(450) COLLATE German_PhoneBook_CI_AS NULL; CREATE INDEX [IX_People_Name] ON [People] ([Name]); """); } [ConditionalFact] public override async Task Alter_column_reset_collation() { await base.Alter_column_reset_collation(); 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(max) NULL; """); } public override async Task Convert_json_entities_to_regular_owned() { await base.Convert_json_entities_to_regular_owned(); 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'[Entity]') AND [c].[name] = N'OwnedCollection'); IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Entity] DROP CONSTRAINT [' + @var0 + '];'); ALTER TABLE [Entity] DROP COLUMN [OwnedCollection]; """, // """ 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'[Entity]') AND [c].[name] = N'OwnedReference'); IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Entity] DROP CONSTRAINT [' + @var1 + '];'); ALTER TABLE [Entity] DROP COLUMN [OwnedReference]; """, // """ ALTER TABLE [Entity] ADD [OwnedReference_Date] datetime2 NULL; """, // """ ALTER TABLE [Entity] ADD [OwnedReference_NestedReference_Number] int NULL; """, // """ CREATE TABLE [Entity_NestedCollection] ( [OwnedEntityId] int NOT NULL, [Id] int NOT NULL IDENTITY, [Number2] int NOT NULL, CONSTRAINT [PK_Entity_NestedCollection] PRIMARY KEY ([OwnedEntityId], [Id]), CONSTRAINT [FK_Entity_NestedCollection_Entity_OwnedEntityId] FOREIGN KEY ([OwnedEntityId]) REFERENCES [Entity] ([Id]) ON DELETE CASCADE ); """, // """ CREATE TABLE [Entity_OwnedCollection] ( [EntityId] int NOT NULL, [Id] int NOT NULL IDENTITY, [Date2] datetime2 NOT NULL, [NestedReference2_Number3] int NULL, CONSTRAINT [PK_Entity_OwnedCollection] PRIMARY KEY ([EntityId], [Id]), CONSTRAINT [FK_Entity_OwnedCollection_Entity_EntityId] FOREIGN KEY ([EntityId]) REFERENCES [Entity] ([Id]) ON DELETE CASCADE ); """, // """ CREATE TABLE [Entity_OwnedCollection_NestedCollection2] ( [Owned2EntityId] int NOT NULL, [Owned2Id] int NOT NULL, [Id] int NOT NULL IDENTITY, [Number4] int NOT NULL, CONSTRAINT [PK_Entity_OwnedCollection_NestedCollection2] PRIMARY KEY ([Owned2EntityId], [Owned2Id], [Id]), CONSTRAINT [FK_Entity_OwnedCollection_NestedCollection2_Entity_OwnedCollection_Owned2EntityId_Owned2Id] FOREIGN KEY ([Owned2EntityId], [Owned2Id]) REFERENCES [Entity_OwnedCollection] ([EntityId], [Id]) ON DELETE CASCADE ); """); } public override async Task Convert_regular_owned_entities_to_json() { await base.Convert_regular_owned_entities_to_json(); AssertSql( """ DROP TABLE [Entity_NestedCollection]; """, // """ DROP TABLE [Entity_OwnedCollection_NestedCollection2]; """, // """ DROP TABLE [Entity_OwnedCollection]; """, // """ 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'[Entity]') AND [c].[name] = N'OwnedReference_Date'); IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Entity] DROP CONSTRAINT [' + @var0 + '];'); ALTER TABLE [Entity] DROP COLUMN [OwnedReference_Date]; """, // """ 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'[Entity]') AND [c].[name] = N'OwnedReference_NestedReference_Number'); IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Entity] DROP CONSTRAINT [' + @var1 + '];'); ALTER TABLE [Entity] DROP COLUMN [OwnedReference_NestedReference_Number]; """, // """ ALTER TABLE [Entity] ADD [OwnedCollection] nvarchar(max) NULL; """, // """ ALTER TABLE [Entity] ADD [OwnedReference] nvarchar(max) NULL; """); } public override async Task Convert_string_column_to_a_json_column_containing_reference() { await base.Convert_string_column_to_a_json_column_containing_reference(); AssertSql(); } public override async Task Convert_string_column_to_a_json_column_containing_required_reference() { await base.Convert_string_column_to_a_json_column_containing_required_reference(); 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'[Entity]') AND [c].[name] = N'Name'); IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Entity] DROP CONSTRAINT [' + @var0 + '];'); UPDATE [Entity] SET [Name] = N'{}' WHERE [Name] IS NULL; ALTER TABLE [Entity] ALTER COLUMN [Name] nvarchar(max) NOT NULL; ALTER TABLE [Entity] ADD DEFAULT N'{}' FOR [Name]; """); } public override async Task Convert_string_column_to_a_json_column_containing_collection() { await base.Convert_string_column_to_a_json_column_containing_collection(); AssertSql(); } [ConditionalFact] public virtual async Task Alter_column_with_index_no_narrowing() { await Test( builder => builder.Entity( "People", e => { 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); }); 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; """); } [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").UseJetIdentityColumn()); Assert.Equal(JetStrings.AlterIdentityColumn, ex.Message); } [ConditionalFact] public virtual async Task Alter_column_remove_identity() { var ex = await TestThrows( builder => builder.Entity("People").Property("SomeColumn").UseJetIdentityColumn(), builder => builder.Entity("People").Property("SomeColumn")); Assert.Equal(JetStrings.AlterIdentityColumn, ex.Message); } [ConditionalFact] public virtual async Task Alter_column_change_type_with_identity() { await Test( builder => builder.Entity( "People", e => { e.Property("Id"); e.Property("IdentityColumn").UseJetIdentityColumn(); }), builder => builder.Entity( "People", e => { e.Property("Id"); e.Property("IdentityColumn").UseJetIdentityColumn(); }), 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); }); 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; """); } [ConditionalFact] public virtual async Task Alter_column_change_identity_seed() { await Test( builder => builder.Entity("People", e => e.Property("Id").UseJetIdentityColumn(seed: 10)), builder => builder.Entity("People", e => e.Property("Id").UseJetIdentityColumn(seed: 100)), model => { // DBCC CHECKIDENT RESEED doesn't actually change the table definition, it only resets the current identity value. // For example, if the table is truncated, the identity is reset back to its original value (with the RESEED lost). // Therefore we cannot check the value via scaffolding. }); AssertSql( """ DBCC CHECKIDENT(N'[People]', RESEED, 100); """); } [ConditionalFact] public virtual async Task Alter_column_change_default() { await Test( builder => builder.Entity("People").Property("Name"), builder => { }, builder => builder.Entity("People").Property("Name") .HasDefaultValue("Doe"), model => { var nameColumn = Assert.Single(Assert.Single(model.Tables).Columns); Assert.Equal("(N'Doe')", nameColumn.DefaultValueSql); }); 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] ADD DEFAULT N'Doe' FOR [Name]; """); } [ConditionalFact] public virtual async Task Alter_column_change_comment_with_default() { await Test( builder => builder.Entity("People").Property("Name").HasDefaultValue("Doe"), builder => { }, builder => builder.Entity("People").Property("Name") .HasComment("Some comment"), model => { var nameColumn = Assert.Single(Assert.Single(model.Tables).Columns); Assert.Equal("(N'Doe')", nameColumn.DefaultValueSql); Assert.Equal("Some comment", nameColumn.Comment); }); AssertSql( """ DECLARE @defaultSchema AS sysname; SET @defaultSchema = SCHEMA_NAME(); DECLARE @description AS sql_variant; SET @description = N'Some comment'; EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSchema, 'TABLE', N'People', 'COLUMN', N'Name'; """); } public override async Task Drop_column() { 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 override async Task Drop_column_primary_key() { 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]; """); } public override async Task Drop_column_computed_and_non_computed_with_dependency() { await base.Drop_column_computed_and_non_computed_with_dependency(); 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'Y'); IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); ALTER TABLE [People] DROP COLUMN [Y]; """, // """ 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'X'); IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var1 + '];'); ALTER TABLE [People] DROP COLUMN [X]; """); } public override async Task Drop_json_columns_from_existing_table() { await base.Drop_json_columns_from_existing_table(); 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'[Entity]') AND [c].[name] = N'OwnedCollection'); IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [Entity] DROP CONSTRAINT [' + @var0 + '];'); ALTER TABLE [Entity] DROP COLUMN [OwnedCollection]; """, // """ 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'[Entity]') AND [c].[name] = N'OwnedReference'); IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [Entity] DROP CONSTRAINT [' + @var1 + '];'); ALTER TABLE [Entity] DROP COLUMN [OwnedReference]; """); } public override async Task Rename_column() { await base.Rename_column(); AssertSql( """ EXEC sp_rename N'[People].[SomeColumn]', N'SomeOtherColumn', 'COLUMN'; """); } public override async Task Rename_json_column() { await base.Rename_json_column(); AssertSql( """ EXEC sp_rename N'[Entity].[json_reference]', N'new_json_reference', 'COLUMN'; """, // """ EXEC sp_rename N'[Entity].[json_collection]', N'new_json_collection', 'COLUMN'; """); } 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]); """); } 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]) WHERE [FirstName] IS NOT NULL AND [LastName] IS NOT NULL; """); } public override async Task Create_index_descending() { await base.Create_index_descending(); AssertSql( """ CREATE INDEX [IX_People_X] ON [People] ([X] DESC); """); } public override async Task Create_index_descending_mixed() { await base.Create_index_descending_mixed(); AssertSql( """ CREATE INDEX [IX_People_X_Y_Z] ON [People] ([X], [Y] DESC, [Z]); """); } public override async Task Alter_index_make_unique() { await base.Alter_index_make_unique(); AssertSql( """ DROP INDEX [IX_People_X] ON [People]; """, // """ CREATE UNIQUE INDEX [IX_People_X] ON [People] ([X]); """); } public override async Task Alter_index_change_sort_order() { await base.Alter_index_change_sort_order(); AssertSql( """ DROP INDEX [IX_People_X_Y_Z] ON [People]; """, // """ CREATE INDEX [IX_People_X_Y_Z] ON [People] ([X], [Y] DESC, [Z]); """); } 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; """); } [ConditionalFact] public virtual async Task CreateIndex_generates_exec_when_filter_and_idempotent() { await Test( builder => builder.Entity( "People", e => { e.Property("Id"); e.Property("Name"); }), builder => { }, builder => builder.Entity("People").HasIndex("Name").HasFilter("[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); }, migrationsSqlGenerationOptions: MigrationsSqlGenerationOptions.Idempotent); 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; """, // """ EXEC(N'CREATE INDEX [IX_People_Name] ON [People] ([Name]) WHERE [Name] IS NOT NULL'); """); } 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] <> ''; """); } public override async Task Drop_index() { await base.Drop_index(); AssertSql( """ DROP INDEX [IX_People_SomeField] ON [People]; """); } public override async Task Rename_index() { await base.Rename_index(); AssertSql( """ EXEC sp_rename N'[People].[Foo]', N'foo', 'INDEX'; """); } public override async Task Add_primary_key_int() { var exception = await Assert.ThrowsAsync(() => base.Add_primary_key_int()); Assert.Equal(JetStrings.AlterIdentityColumn, exception.Message); } public override async Task Add_primary_key_string() { await base.Add_primary_key_string(); 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]); """); } 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 + '];'); UPDATE [People] SET [SomeField] = N'' WHERE [SomeField] IS NULL; ALTER TABLE [People] ALTER COLUMN [SomeField] nvarchar(450) NOT NULL; ALTER TABLE [People] ADD DEFAULT N'' FOR [SomeField]; """, // """ ALTER TABLE [People] ADD CONSTRAINT [PK_Foo] PRIMARY KEY ([SomeField]); """); } public override async Task Add_primary_key_composite_with_name() { await base.Add_primary_key_composite_with_name(); AssertSql( """ ALTER TABLE [People] ADD CONSTRAINT [PK_Foo] PRIMARY KEY ([SomeField1], [SomeField2]); """); } public override async Task Drop_primary_key_int() { var exception = await Assert.ThrowsAsync(() => base.Drop_primary_key_int()); Assert.Equal(JetStrings.AlterIdentityColumn, exception.Message); } public override async Task Drop_primary_key_string() { await base.Drop_primary_key_string(); 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'SomeField'); IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); ALTER TABLE [People] ALTER COLUMN [SomeField] nvarchar(max) NOT NULL; """); } public override async Task Add_foreign_key() { await base.Add_foreign_key(); AssertSql( """ CREATE INDEX [IX_Orders_CustomerId] ON [Orders] ([CustomerId]); """, // """ ALTER TABLE [Orders] ADD CONSTRAINT [FK_Orders_Customers_CustomerId] FOREIGN KEY ([CustomerId]) REFERENCES [Customers] ([Id]) ON DELETE CASCADE; """); } public override async Task Add_foreign_key_with_name() { await base.Add_foreign_key_with_name(); // AssertSql( // @"ALTER TABLE [Orders] DROP CONSTRAINT [FK_Orders_Customers_CustomerId];", // // // @"DROP INDEX [IX_Orders_CustomerId] ON [Orders];"); AssertSql( """ CREATE INDEX [IX_Orders_CustomerId] ON [Orders] ([CustomerId]); """, // """ ALTER TABLE [Orders] ADD CONSTRAINT [FK_Foo] FOREIGN KEY ([CustomerId]) REFERENCES [Customers] ([Id]) ON DELETE CASCADE; """); } public override async Task Drop_foreign_key() { await base.Drop_foreign_key(); AssertSql( """ ALTER TABLE [Orders] DROP CONSTRAINT [FK_Orders_Customers_CustomerId]; """, // """ DROP INDEX [IX_Orders_CustomerId] ON [Orders]; """); } public override async Task Add_unique_constraint() { await base.Add_unique_constraint(); AssertSql( """ ALTER TABLE [People] ADD CONSTRAINT [AK_People_AlternateKeyColumn] UNIQUE ([AlternateKeyColumn]); """); } public override async Task Add_unique_constraint_composite_with_name() { await base.Add_unique_constraint_composite_with_name(); AssertSql( """ ALTER TABLE [People] ADD CONSTRAINT [AK_Foo] UNIQUE ([AlternateKeyColumn1], [AlternateKeyColumn2]); """); } public override async Task Drop_unique_constraint() { await base.Drop_unique_constraint(); AssertSql( """ ALTER TABLE [People] DROP CONSTRAINT [AK_People_AlternateKeyColumn]; """); } public override async Task Add_check_constraint_with_name() { await base.Add_check_constraint_with_name(); AssertSql( """ ALTER TABLE [People] ADD CONSTRAINT [CK_People_Foo] CHECK ([DriverLicense] > 0); """); } [ConditionalFact] public virtual async Task Add_check_constraint_generates_exec_when_idempotent() { await Test( builder => builder.Entity( "People", e => { e.Property("Id"); e.Property("DriverLicense"); }), builder => { }, builder => builder.Entity("People").ToTable(tb => tb.HasCheckConstraint("CK_People_Foo", "[DriverLicense] > 0")), model => { // TODO: no scaffolding support for check constraints, https://github.com/aspnet/EntityFrameworkCore/issues/15408 }, migrationsSqlGenerationOptions: MigrationsSqlGenerationOptions.Idempotent); AssertSql( """ EXEC(N'ALTER TABLE [People] ADD CONSTRAINT [CK_People_Foo] CHECK ([DriverLicense] > 0)'); """); } public override async Task Alter_check_constraint() { await base.Alter_check_constraint(); AssertSql( """ ALTER TABLE [People] DROP CONSTRAINT [CK_People_Foo]; """, // """ ALTER TABLE [People] ADD CONSTRAINT [CK_People_Foo] CHECK ([DriverLicense] > 1); """); } public override async Task Drop_check_constraint() { await base.Drop_check_constraint(); AssertSql( """ ALTER TABLE [People] DROP CONSTRAINT [CK_People_Foo]; """); } public override async Task Create_sequence() { await base.Create_sequence(); AssertSql( """ CREATE SEQUENCE [TestSequence] AS int START WITH 1 INCREMENT BY 1 NO CYCLE; """); } public override async Task Create_sequence_long() { await base.Create_sequence_long(); AssertSql( """ CREATE SEQUENCE [TestSequence] START WITH 1 INCREMENT BY 1 NO CYCLE; """); } public override async Task Create_sequence_short() { await base.Create_sequence_short(); AssertSql( """ CREATE SEQUENCE [TestSequence] AS smallint START WITH 1 INCREMENT BY 1 NO CYCLE; """); } public override async Task Create_sequence_all_settings() { await base.Create_sequence_all_settings(); 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; """); } public override async Task Alter_sequence_all_settings() { await base.Alter_sequence_all_settings(); AssertSql( """ ALTER SEQUENCE [foo] INCREMENT BY 2 MINVALUE -5 MAXVALUE 10 CYCLE; """, // """ ALTER SEQUENCE [foo] RESTART WITH -3; """); } public override async Task Alter_sequence_increment_by() { await base.Alter_sequence_increment_by(); AssertSql( """ ALTER SEQUENCE [foo] INCREMENT BY 2 NO MINVALUE NO MAXVALUE NO CYCLE; """); } public override async Task Alter_sequence_restart_with() { await base.Alter_sequence_restart_with(); AssertSql( @"ALTER SEQUENCE [foo] RESTART WITH 3;"); } public override async Task Drop_sequence() { await base.Drop_sequence(); AssertSql( """ DROP SEQUENCE [TestSequence]; """); } public override async Task Rename_sequence() { await base.Rename_sequence(); AssertSql( """ EXEC sp_rename N'[TestSequence]', N'testsequence', 'OBJECT'; """); } public override async Task Move_sequence() { await base.Move_sequence(); AssertSql( """ IF SCHEMA_ID(N'TestSequenceSchema') IS NULL EXEC(N'CREATE SCHEMA [TestSequenceSchema];'); """, // """ ALTER SCHEMA [TestSequenceSchema] TRANSFER [TestSequence]; """); } [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); }); AssertSql( """ DECLARE @defaultSchema sysname = SCHEMA_NAME(); EXEC(N'ALTER SCHEMA [' + @defaultSchema + N'] TRANSFER [TestSequenceSchema].[TestSequence];'); """); } [ConditionalFact] public async Task Create_sequence_and_dependent_column() { await Test( builder => builder.Entity("People").Property("Id"), builder => { }, builder => { builder.HasSequence("TestSequence"); builder.Entity("People").Property("SeqProp").HasDefaultValueSql("NEXT VALUE FOR TestSequence"); }, model => { var sequence = Assert.Single(model.Sequences); Assert.Equal("TestSequence", sequence.Name); }); AssertSql( """ CREATE SEQUENCE [TestSequence] AS int START WITH 1 INCREMENT BY 1 NO CYCLE; """, // """ ALTER TABLE [People] ADD [SeqProp] int NOT NULL DEFAULT (NEXT VALUE FOR TestSequence); """); } [ConditionalFact] public async Task Drop_sequence_and_dependent_column() { await Test( builder => builder.Entity("People").Property("Id"), builder => { builder.HasSequence("TestSequence"); builder.Entity("People").Property("SeqProp").HasDefaultValueSql("NEXT VALUE FOR TestSequence"); }, builder => { }, model => Assert.Empty(model.Sequences)); 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'SeqProp'); IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); ALTER TABLE [People] DROP COLUMN [SeqProp]; """, // """ DROP SEQUENCE [TestSequence]; """); } 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 override async Task DeleteDataOperation_simple_key() { await base.DeleteDataOperation_simple_key(); // TODO remove rowcount AssertSql( """ DELETE FROM [Person] WHERE [Id] = 2; SELECT @@ROWCOUNT; """); } public override async Task DeleteDataOperation_composite_key() { await base.DeleteDataOperation_composite_key(); // TODO remove rowcount AssertSql( """ DELETE FROM [Person] WHERE [AnotherId] = 12 AND [Id] = 2; SELECT @@ROWCOUNT; """); } public override async Task UpdateDataOperation_simple_key() { await base.UpdateDataOperation_simple_key(); // TODO remove rowcount AssertSql( """ UPDATE [Person] SET [Name] = N'Another John Snow' WHERE [Id] = 2; SELECT @@ROWCOUNT; """); } public override async Task UpdateDataOperation_composite_key() { await base.UpdateDataOperation_composite_key(); // TODO remove rowcount AssertSql( """ UPDATE [Person] SET [Name] = N'Another John Snow' WHERE [AnotherId] = 11 AND [Id] = 2; SELECT @@ROWCOUNT; """); } public override async Task UpdateDataOperation_multiple_columns() { await base.UpdateDataOperation_multiple_columns(); // TODO remove rowcount AssertSql( """ UPDATE [Person] SET [Age] = 21, [Name] = N'Another John Snow' WHERE [Id] = 2; SELECT @@ROWCOUNT; """); } [ConditionalFact] public virtual async Task InsertDataOperation_generates_exec_when_idempotent() { await 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 => { }, migrationsSqlGenerationOptions: MigrationsSqlGenerationOptions.Idempotent); 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; EXEC(N'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; """); } [ConditionalFact] public virtual async Task DeleteDataOperation_generates_exec_when_idempotent() { await 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 => { }, migrationsSqlGenerationOptions: MigrationsSqlGenerationOptions.Idempotent); AssertSql( """ EXEC(N'DELETE FROM [Person] WHERE [Id] = 2; SELECT @@ROWCOUNT'); """); } [ConditionalFact] public virtual async Task UpdateDataOperation_generates_exec_when_idempotent() { await 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 => { }, migrationsSqlGenerationOptions: MigrationsSqlGenerationOptions.Idempotent); AssertSql( """ EXEC(N'UPDATE [Person] SET [Name] = N''Another John Snow'' WHERE [Id] = 2; SELECT @@ROWCOUNT'); """); } [ConditionalFact] public override async Task Add_required_primitive_collection_to_existing_table() { await base.Add_required_primitive_collection_to_existing_table(); AssertSql( """ ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'[]'; """); } [ConditionalFact] public override async Task Add_required_primitive_collection_with_custom_default_value_to_existing_table() { await base.Add_required_primitive_collection_with_custom_default_value_to_existing_table(); AssertSql( """ ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'[1,2,3]'; """); } [ConditionalFact] public override async Task Add_required_primitive_collection_with_custom_default_value_sql_to_existing_table() { await base.Add_required_primitive_collection_with_custom_default_value_sql_to_existing_table_core("N'[3, 2, 1]'"); AssertSql( """ ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT (N'[3, 2, 1]'); """); } [ConditionalFact(Skip = "issue #33038")] public override async Task Add_required_primitive_collection_with_custom_converter_to_existing_table() { await base.Add_required_primitive_collection_with_custom_converter_to_existing_table(); AssertSql( """ ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'nothing'; """); } [ConditionalFact] public override async Task Add_required_primitive_collection_with_custom_converter_and_custom_default_value_to_existing_table() { await base.Add_required_primitive_collection_with_custom_converter_and_custom_default_value_to_existing_table(); AssertSql( """ ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'some numbers'; """); } [ConditionalFact] public override async Task Add_optional_primitive_collection_to_existing_table() { await base.Add_optional_primitive_collection_to_existing_table(); AssertSql( """ ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NULL; """); } [ConditionalFact] public override async Task Create_table_with_required_primitive_collection() { await base.Create_table_with_required_primitive_collection(); AssertSql( """ CREATE TABLE [Customers] ( [Id] int NOT NULL IDENTITY, [Name] nvarchar(max) NULL, [Numbers] nvarchar(max) NOT NULL, CONSTRAINT [PK_Customers] PRIMARY KEY ([Id]) ); """); } [ConditionalFact] public override async Task Create_table_with_optional_primitive_collection() { await base.Create_table_with_optional_primitive_collection(); AssertSql( """ CREATE TABLE [Customers] ( [Id] int NOT NULL IDENTITY, [Name] nvarchar(max) NULL, [Numbers] nvarchar(max) NULL, CONSTRAINT [PK_Customers] PRIMARY KEY ([Id]) ); """); } [ConditionalFact] public override async Task Create_table_with_complex_type_with_required_properties_on_derived_entity_in_TPH() { await base.Create_table_with_complex_type_with_required_properties_on_derived_entity_in_TPH(); AssertSql( """ CREATE TABLE [Contacts] ( [Id] int NOT NULL IDENTITY, [Discriminator] nvarchar(8) NOT NULL, [Name] nvarchar(max) NULL, [Number] int NULL, [MyComplex_Prop] nvarchar(max) NULL, [MyComplex_MyNestedComplex_Bar] datetime2 NULL, [MyComplex_MyNestedComplex_Foo] int NULL, CONSTRAINT [PK_Contacts] PRIMARY KEY ([Id]) ); """); } [ConditionalFact] public override async Task Add_required_primitve_collection_to_existing_table() { await base.Add_required_primitve_collection_to_existing_table(); AssertSql( """ ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'[]'; """); } [ConditionalFact] public override async Task Add_required_primitve_collection_with_custom_default_value_to_existing_table() { await base.Add_required_primitve_collection_with_custom_default_value_to_existing_table(); AssertSql( """ ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'[1,2,3]'; """); } [ConditionalFact] public override async Task Add_required_primitve_collection_with_custom_default_value_sql_to_existing_table() { await base.Add_required_primitve_collection_with_custom_default_value_sql_to_existing_table_core("N'[3, 2, 1]'"); AssertSql( """ ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT (N'[3, 2, 1]'); """); } [ConditionalFact(Skip = "issue #33038")] public override async Task Add_required_primitve_collection_with_custom_converter_to_existing_table() { await base.Add_required_primitve_collection_with_custom_converter_to_existing_table(); AssertSql( """ ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'nothing'; """); } [ConditionalFact] public override async Task Add_required_primitve_collection_with_custom_converter_and_custom_default_value_to_existing_table() { await base.Add_required_primitve_collection_with_custom_converter_and_custom_default_value_to_existing_table(); AssertSql( """ ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'some numbers'; """); } protected override string NonDefaultCollation => _nonDefaultCollation ??= GetDatabaseCollation() == "German_PhoneBook_CI_AS" ? "French_CI_AS" : "German_PhoneBook_CI_AS"; private string? _nonDefaultCollation; private string? GetDatabaseCollation() { using var ctx = CreateContext(); var connection = ctx.Database.GetDbConnection(); using var command = connection.CreateCommand(); command.CommandText = $@" SELECT collation_name FROM sys.databases WHERE name = '{connection.Database}';"; return command.ExecuteScalar() is string collation ? collation : null; } public class MigrationsJetFixture : MigrationsFixtureBase { protected override string StoreName => nameof(MigrationsJetTest); protected override ITestStoreFactory TestStoreFactory => JetTestStoreFactory.Instance; public override RelationalTestHelpers TestHelpers => JetTestHelpers.Instance; protected override IServiceCollection AddServices(IServiceCollection serviceCollection) => base.AddServices(serviceCollection) .AddScoped(); } }