diff --git a/src/EFCore.Jet/Extensions/JetPropertyBuilderExtensions.cs b/src/EFCore.Jet/Extensions/JetPropertyBuilderExtensions.cs index 3bef17e..6902133 100644 --- a/src/EFCore.Jet/Extensions/JetPropertyBuilderExtensions.cs +++ b/src/EFCore.Jet/Extensions/JetPropertyBuilderExtensions.cs @@ -25,12 +25,10 @@ namespace Microsoft.EntityFrameworkCore /// The incremental value that is added to the identity value of the previous row that was loaded. /// The same builder instance so that multiple calls can be chained. public static PropertyBuilder UseJetIdentityColumn( - [NotNull] this PropertyBuilder propertyBuilder, + this PropertyBuilder propertyBuilder, int seed = 1, int increment = 1) { - Check.NotNull(propertyBuilder, nameof(propertyBuilder)); - var property = propertyBuilder.Metadata; property.SetValueGenerationStrategy(JetValueGenerationStrategy.IdentityColumn); property.SetJetIdentitySeed(seed); @@ -39,6 +37,27 @@ namespace Microsoft.EntityFrameworkCore return propertyBuilder; } + /// + /// Configures the key column to use the Jet IDENTITY feature to generate values for new entities, + /// when targeting Jet. This method sets the property to be . + /// + /// The builder for the column being configured. + /// The value that is used for the very first row loaded into the table. + /// The incremental value that is added to the identity value of the previous row that was loaded. + /// The same builder instance so that multiple calls can be chained. + public static ColumnBuilder UseJetIdentityColumn( + this ColumnBuilder columnBuilder, + int seed = 1, + int increment = 1) + { + var overrides = columnBuilder.Overrides; + overrides.SetValueGenerationStrategy(JetValueGenerationStrategy.IdentityColumn); + overrides.SetJetIdentitySeed(seed); + overrides.SetJetIdentityIncrement(increment); + + return columnBuilder; + } + /// /// Configures the key property to use the Jet IDENTITY feature to generate values for new entities, /// when targeting Jet. This method sets the property to be . @@ -54,6 +73,21 @@ namespace Microsoft.EntityFrameworkCore int increment = 1) => (PropertyBuilder)UseJetIdentityColumn((PropertyBuilder)propertyBuilder, seed, increment); + /// + /// Configures the key column to use the Jet IDENTITY feature to generate values for new entities, + /// when targeting Jet. This method sets the property to be . + /// + /// The type of the property being configured. + /// The builder for the column being configured. + /// The value that is used for the very first row loaded into the table. + /// The incremental value that is added to the identity value of the previous row that was loaded. + /// The same builder instance so that multiple calls can be chained. + public static ColumnBuilder UseJetIdentityColumn( + this ColumnBuilder columnBuilder, + int seed = 1, + int increment = 1) + => (ColumnBuilder)UseJetIdentityColumn((ColumnBuilder)columnBuilder, seed, increment); + /// /// Configures the seed for Jet IDENTITY. /// diff --git a/src/EFCore.Jet/Extensions/JetPropertyExtensions.cs b/src/EFCore.Jet/Extensions/JetPropertyExtensions.cs index 36f46d2..b836337 100644 --- a/src/EFCore.Jet/Extensions/JetPropertyExtensions.cs +++ b/src/EFCore.Jet/Extensions/JetPropertyExtensions.cs @@ -10,6 +10,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using System.Linq; using EntityFrameworkCore.Jet.Utilities; using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Diagnostics; // ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore @@ -24,8 +25,16 @@ namespace Microsoft.EntityFrameworkCore /// /// The property. /// The identity seed. - public static int? GetJetIdentitySeed([NotNull] this IReadOnlyProperty property) - => (int?)property[JetAnnotationNames.IdentitySeed]; + public static int? GetJetIdentitySeed(this IReadOnlyProperty property) + { + if (property is RuntimeProperty) + { + throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + } + + var annotation = property.FindAnnotation(JetAnnotationNames.IdentitySeed); + return (int?)annotation?.Value; + } /// /// Returns the identity seed. @@ -33,26 +42,47 @@ namespace Microsoft.EntityFrameworkCore /// The property. /// The identifier of the store object. /// The identity seed. - public static int? GetJetIdentitySeed([NotNull] this IReadOnlyProperty property, in StoreObjectIdentifier storeObject) + public static int? GetJetIdentitySeed(this IReadOnlyProperty property, in StoreObjectIdentifier storeObject) { + if (property is RuntimeProperty) + { + throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + } + + var @override = property.FindOverrides(storeObject)?.FindAnnotation(JetAnnotationNames.IdentitySeed); + if (@override != null) + { + return (int?)@override.Value; + } + var annotation = property.FindAnnotation(JetAnnotationNames.IdentitySeed); - if (annotation != null) + if (annotation is not null) { return (int?)annotation.Value; } - var sharedTableRootProperty = property.FindSharedStoreObjectRootProperty(storeObject); - return sharedTableRootProperty != null - ? sharedTableRootProperty.GetJetIdentitySeed(storeObject) - : null; + var sharedProperty = property.FindSharedStoreObjectRootProperty(storeObject); + return sharedProperty == null + ? property.DeclaringType.Model.GetJetIdentitySeed() + : sharedProperty.GetJetIdentitySeed(storeObject); } + /// + /// Returns the identity seed. + /// + /// The property overrides. + /// The identity seed. + public static int? GetJetIdentitySeed(this IReadOnlyRelationalPropertyOverrides overrides) + => overrides is RuntimeRelationalPropertyOverrides + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (int?)overrides.FindAnnotation(JetAnnotationNames.IdentitySeed)?.Value; + /// /// Sets the identity seed. /// /// The property. /// The value to set. - public static void SetJetIdentitySeed([NotNull] this IMutableProperty property, int? seed) + public static void SetJetIdentitySeed(this IMutableProperty property, int? seed) => property.SetOrRemoveAnnotation( JetAnnotationNames.IdentitySeed, seed); @@ -65,17 +95,63 @@ namespace Microsoft.EntityFrameworkCore /// Indicates whether the configuration was specified using a data annotation. /// The configured value. public static int? SetJetIdentitySeed( - [NotNull] this IConventionProperty property, + this IConventionProperty property, int? seed, bool fromDataAnnotation = false) - { - property.SetOrRemoveAnnotation( + => (int?)property.SetOrRemoveAnnotation( JetAnnotationNames.IdentitySeed, seed, - fromDataAnnotation); + fromDataAnnotation)?.Value; - return seed; - } + /// + /// Sets the identity seed for a particular table. + /// + /// The property. + /// The value to set. + /// The identifier of the table containing the column. + public static void SetJetIdentitySeed( + this IMutableProperty property, + int? seed, + in StoreObjectIdentifier storeObject) + => property.GetOrCreateOverrides(storeObject) + .SetJetIdentitySeed(seed); + + /// + /// Sets the identity seed for a particular table. + /// + /// The property. + /// The value to set. + /// The identifier of the table containing the column. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static int? SetJetIdentitySeed( + this IConventionProperty property, + int? seed, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + => property.GetOrCreateOverrides(storeObject, fromDataAnnotation) + .SetJetIdentitySeed(seed, fromDataAnnotation); + + /// + /// Sets the identity seed for a particular table. + /// + /// The property overrides. + /// The value to set. + public static void SetJetIdentitySeed(this IMutableRelationalPropertyOverrides overrides, int? seed) + => overrides.SetOrRemoveAnnotation(JetAnnotationNames.IdentitySeed, seed); + + /// + /// Sets the identity seed for a particular table. + /// + /// The property overrides. + /// The value to set. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static int? SetJetIdentitySeed( + this IConventionRelationalPropertyOverrides overrides, + int? seed, + bool fromDataAnnotation = false) + => (int?)overrides.SetOrRemoveAnnotation(JetAnnotationNames.IdentitySeed, seed, fromDataAnnotation)?.Value; /// /// Returns the for the identity seed. @@ -85,13 +161,36 @@ namespace Microsoft.EntityFrameworkCore public static ConfigurationSource? GetJetIdentitySeedConfigurationSource([NotNull] this IConventionProperty property) => property.FindAnnotation(JetAnnotationNames.IdentitySeed)?.GetConfigurationSource(); + /// + /// Returns the for the identity seed for a particular table. + /// + /// The property. + /// The identifier of the table containing the column. + /// The for the identity seed. + public static ConfigurationSource? GetJetIdentitySeedConfigurationSource( + this IConventionProperty property, + in StoreObjectIdentifier storeObject) + => property.FindOverrides(storeObject)?.GetJetIdentitySeedConfigurationSource(); + + /// + /// Returns the for the identity seed for a particular table. + /// + /// The property overrides. + /// The for the identity seed. + public static ConfigurationSource? GetJetIdentitySeedConfigurationSource( + this IConventionRelationalPropertyOverrides overrides) + => overrides.FindAnnotation(JetAnnotationNames.IdentitySeed)?.GetConfigurationSource(); + /// /// Returns the identity increment. /// /// The property. /// The identity increment. - public static int? GetJetIdentityIncrement([NotNull] this IReadOnlyProperty property) - => (int?)property[JetAnnotationNames.IdentityIncrement]; + public static int? GetJetIdentityIncrement(this IReadOnlyProperty property) + => (property is RuntimeProperty) + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (int?)property[JetAnnotationNames.IdentityIncrement] + ?? property.DeclaringType.Model.GetJetIdentityIncrement(); /// /// Returns the identity increment. @@ -99,26 +198,47 @@ namespace Microsoft.EntityFrameworkCore /// The property. /// The identifier of the store object. /// The identity increment. - public static int? GetJetIdentityIncrement([NotNull] this IReadOnlyProperty property, in StoreObjectIdentifier storeObject) + public static int? GetJetIdentityIncrement(this IReadOnlyProperty property, in StoreObjectIdentifier storeObject) { + if (property is RuntimeProperty) + { + throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + } + + var @override = property.FindOverrides(storeObject)?.FindAnnotation(JetAnnotationNames.IdentityIncrement); + if (@override != null) + { + return (int?)@override.Value; + } + var annotation = property.FindAnnotation(JetAnnotationNames.IdentityIncrement); if (annotation != null) { return (int?)annotation.Value; } - var sharedTableRootProperty = property.FindSharedStoreObjectRootProperty(storeObject); - return sharedTableRootProperty != null - ? sharedTableRootProperty.GetJetIdentityIncrement(storeObject) - : null; + var sharedProperty = property.FindSharedStoreObjectRootProperty(storeObject); + return sharedProperty == null + ? property.DeclaringType.Model.GetJetIdentityIncrement() + : sharedProperty.GetJetIdentityIncrement(storeObject); } + /// + /// Returns the identity increment. + /// + /// The property overrides. + /// The identity increment. + public static int? GetJetIdentityIncrement(this IReadOnlyRelationalPropertyOverrides overrides) + => overrides is RuntimeRelationalPropertyOverrides + ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) + : (int?)overrides.FindAnnotation(JetAnnotationNames.IdentityIncrement)?.Value; + /// /// Sets the identity increment. /// /// The property. /// The value to set. - public static void SetJetIdentityIncrement([NotNull] this IMutableProperty property, int? increment) + public static void SetJetIdentityIncrement(this IMutableProperty property, int? increment) => property.SetOrRemoveAnnotation( JetAnnotationNames.IdentityIncrement, increment); @@ -134,14 +254,61 @@ namespace Microsoft.EntityFrameworkCore [NotNull] this IConventionProperty property, int? increment, bool fromDataAnnotation = false) - { - property.SetOrRemoveAnnotation( + => (int?)property.SetOrRemoveAnnotation( JetAnnotationNames.IdentityIncrement, increment, - fromDataAnnotation); + fromDataAnnotation)?.Value; - return increment; - } + /// + /// Sets the identity increment for a particular table. + /// + /// The property. + /// The value to set. + /// The identifier of the table containing the column. + public static void SetJetIdentityIncrement( + this IMutableProperty property, + int? increment, + in StoreObjectIdentifier storeObject) + => property.GetOrCreateOverrides(storeObject) + .SetJetIdentityIncrement(increment); + + /// + /// Sets the identity increment for a particular table. + /// + /// The property. + /// The value to set. + /// The identifier of the table containing the column. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static int? SetJetIdentityIncrement( + this IConventionProperty property, + int? increment, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + => property.GetOrCreateOverrides(storeObject, fromDataAnnotation) + .SetJetIdentityIncrement(increment, fromDataAnnotation); + + /// + /// Sets the identity increment for a particular table. + /// + /// The property overrides. + /// The value to set. + public static void SetJetIdentityIncrement(this IMutableRelationalPropertyOverrides overrides, int? increment) + => overrides.SetOrRemoveAnnotation(JetAnnotationNames.IdentityIncrement, increment); + + /// + /// Sets the identity increment for a particular table. + /// + /// The property overrides. + /// The value to set. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static int? SetJetIdentityIncrement( + this IConventionRelationalPropertyOverrides overrides, + int? increment, + bool fromDataAnnotation = false) + => (int?)overrides.SetOrRemoveAnnotation(JetAnnotationNames.IdentityIncrement, increment, fromDataAnnotation) + ?.Value; /// /// Returns the for the identity increment. @@ -290,12 +457,8 @@ namespace Microsoft.EntityFrameworkCore /// The property. /// The strategy to use. public static void SetValueGenerationStrategy( - [NotNull] this IMutableProperty property, JetValueGenerationStrategy? value) - { - CheckValueGenerationStrategy(property, value); - - property.SetOrRemoveAnnotation(JetAnnotationNames.ValueGenerationStrategy, value); - } + this IMutableProperty property, JetValueGenerationStrategy? value) + => property.SetOrRemoveAnnotation(JetAnnotationNames.ValueGenerationStrategy, value); /// /// Sets the to use for the property. @@ -305,32 +468,64 @@ namespace Microsoft.EntityFrameworkCore /// Indicates whether the configuration was specified using a data annotation. /// The configured value. public static JetValueGenerationStrategy? SetValueGenerationStrategy( - [NotNull] this IConventionProperty property, + this IConventionProperty property, JetValueGenerationStrategy? value, bool fromDataAnnotation = false) - { - CheckValueGenerationStrategy(property, value); + => (JetValueGenerationStrategy?)property.SetOrRemoveAnnotation( + JetAnnotationNames.ValueGenerationStrategy, value, fromDataAnnotation)?.Value; - property.SetOrRemoveAnnotation(JetAnnotationNames.ValueGenerationStrategy, value, fromDataAnnotation); + /// + /// Sets the to use for the property for a particular table. + /// + /// The property. + /// The strategy to use. + /// The identifier of the table containing the column. + public static void SetValueGenerationStrategy( + this IMutableProperty property, + JetValueGenerationStrategy? value, + in StoreObjectIdentifier storeObject) + => property.GetOrCreateOverrides(storeObject) + .SetValueGenerationStrategy(value); - return value; - } + /// + /// Sets the to use for the property for a particular table. + /// + /// The property. + /// The strategy to use. + /// The identifier of the table containing the column. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static JetValueGenerationStrategy? SetValueGenerationStrategy( + this IConventionProperty property, + JetValueGenerationStrategy? value, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + => property.GetOrCreateOverrides(storeObject, fromDataAnnotation) + .SetValueGenerationStrategy(value, fromDataAnnotation); - private static void CheckValueGenerationStrategy(IReadOnlyProperty property, JetValueGenerationStrategy? value) - { - if (value != null) - { - var propertyType = property.ClrType; + /// + /// Sets the to use for the property for a particular table. + /// + /// The property overrides. + /// The strategy to use. + public static void SetValueGenerationStrategy( + this IMutableRelationalPropertyOverrides overrides, + JetValueGenerationStrategy? value) + => overrides.SetOrRemoveAnnotation(JetAnnotationNames.ValueGenerationStrategy, value); - if (value == JetValueGenerationStrategy.IdentityColumn - && !IsCompatibleWithValueGeneration(property)) - { - throw new ArgumentException( - JetStrings.IdentityBadType( - property.Name, property.DeclaringType.DisplayName(), propertyType.ShortDisplayName())); - } - } - } + /// + /// Sets the to use for the property for a particular table. + /// + /// The property overrides. + /// The strategy to use. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static JetValueGenerationStrategy? SetValueGenerationStrategy( + this IConventionRelationalPropertyOverrides overrides, + JetValueGenerationStrategy? value, + bool fromDataAnnotation = false) + => (JetValueGenerationStrategy?)overrides.SetOrRemoveAnnotation( + JetAnnotationNames.ValueGenerationStrategy, value, fromDataAnnotation)?.Value; /// /// Returns the for the . diff --git a/test/EFCore.Jet.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesJetTest.cs b/test/EFCore.Jet.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesJetTest.cs index 04a5612..962ee6a 100644 --- a/test/EFCore.Jet.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesJetTest.cs +++ b/test/EFCore.Jet.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesJetTest.cs @@ -12,8 +12,7 @@ namespace EntityFrameworkCore.Jet.FunctionalTests.BulkUpdates; public class NorthwindBulkUpdatesJetTest( NorthwindBulkUpdatesJetFixture fixture, - ITestOutputHelper testOutputHelper) - : NorthwindBulkUpdatesRelationalTestBase>(fixture, testOutputHelper) + ITestOutputHelper testOutputHelper) : NorthwindBulkUpdatesRelationalTestBase>(fixture, testOutputHelper) { [ConditionalFact] public virtual void Check_all_tests_overridden() diff --git a/test/EFCore.Jet.FunctionalTests/EFCore.Jet.FunctionalTests.csproj b/test/EFCore.Jet.FunctionalTests/EFCore.Jet.FunctionalTests.csproj index af7c614..a5bf5e1 100644 --- a/test/EFCore.Jet.FunctionalTests/EFCore.Jet.FunctionalTests.csproj +++ b/test/EFCore.Jet.FunctionalTests/EFCore.Jet.FunctionalTests.csproj @@ -5,6 +5,7 @@ EntityFrameworkCore.Jet.FunctionalTests EntityFrameworkCore.Jet.FunctionalTests AnyCPU;x86;x64 + true @@ -71,7 +72,6 @@ - @@ -104,7 +104,6 @@ - diff --git a/test/EFCore.Jet.FunctionalTests/MigrationsInfrastructureJetTest.cs b/test/EFCore.Jet.FunctionalTests/Migrations/MigrationsInfrastructureJetTest.cs similarity index 99% rename from test/EFCore.Jet.FunctionalTests/MigrationsInfrastructureJetTest.cs rename to test/EFCore.Jet.FunctionalTests/Migrations/MigrationsInfrastructureJetTest.cs index 93c8d3c..b27a766 100644 --- a/test/EFCore.Jet.FunctionalTests/MigrationsInfrastructureJetTest.cs +++ b/test/EFCore.Jet.FunctionalTests/Migrations/MigrationsInfrastructureJetTest.cs @@ -19,7 +19,7 @@ using JetDatabaseCreator = EntityFrameworkCore.Jet.Storage.Internal.JetDatabaseC // ReSharper disable InconsistentNaming #nullable disable -namespace EntityFrameworkCore.Jet.FunctionalTests +namespace EntityFrameworkCore.Jet.FunctionalTests.Migrations { [JetCondition(JetCondition.IsNotCI)] public class MigrationsInfrastructureJetTest( @@ -269,7 +269,7 @@ COMMIT TRANSACTION; Assert.Equal("EntityFrameworkCore.Jet", ActiveProvider); } - + [ConditionalFact] public async Task Empty_Migration_Creates_Database() { diff --git a/test/EFCore.Jet.FunctionalTests/Migrations/MigrationsJetTest.cs b/test/EFCore.Jet.FunctionalTests/Migrations/MigrationsJetTest.cs index 0d6af43..397a242 100644 --- a/test/EFCore.Jet.FunctionalTests/Migrations/MigrationsJetTest.cs +++ b/test/EFCore.Jet.FunctionalTests/Migrations/MigrationsJetTest.cs @@ -1,3 +1,4 @@ + // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. @@ -14,6 +15,7 @@ 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; @@ -44,7 +46,7 @@ public class MigrationsJetTest : MigrationsTestBase 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); - }); + await base.Move_table(); AssertSql( -""" -DECLARE @defaultSchema sysname = SCHEMA_NAME(); -EXEC(N'ALTER SCHEMA [' + @defaultSchema + N'] TRANSFER [TestTableSchema].[TestTable];'); + """ +IF SCHEMA_ID(N'TestTableSchema') IS NULL EXEC(N'CREATE SCHEMA [TestTableSchema];'); +""", + // + """ +ALTER SCHEMA [TestTableSchema] TRANSFER [TestTable]; """); } @@ -320,11 +350,11 @@ EXEC(N'ALTER SCHEMA [' + @defaultSchema + N'] TRANSFER [TestTableSchema].[TestTa 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]) @@ -343,7 +373,7 @@ CREATE TABLE [SomeOtherSchema].[People] ( 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]) @@ -356,7 +386,7 @@ CREATE TABLE [dbo].[People] ( await base.Add_column_with_defaultValue_string(); AssertSql( -""" + """ ALTER TABLE [People] ADD [Name] nvarchar(max) NOT NULL DEFAULT N'John Doe'; """); } @@ -366,7 +396,7 @@ ALTER TABLE [People] ADD [Name] nvarchar(max) NOT NULL DEFAULT N'John Doe'; await base.Add_column_with_defaultValue_datetime(); AssertSql( -""" + """ ALTER TABLE [People] ADD [Birthday] datetime2 NOT NULL DEFAULT '2015-04-12T17:05:00.0000000'; """); } @@ -397,7 +427,7 @@ ALTER TABLE [People] ADD [Birthday] datetime2 NOT NULL DEFAULT '2015-04-12T17:05 }); AssertSql( -$""" + $""" ALTER TABLE [People] ADD [Birthday] datetime2({precision}) NOT NULL DEFAULT '2015-04-12T17:05:00{fractionalSeconds}'; """); } @@ -431,7 +461,7 @@ ALTER TABLE [People] ADD [Birthday] datetime2({precision}) NOT NULL DEFAULT '201 }); AssertSql( -$""" + $""" ALTER TABLE [People] ADD [Birthday] datetimeoffset({precision}) NOT NULL DEFAULT '2015-04-12T17:05:00{fractionalSeconds}+10:00'; """); } @@ -463,7 +493,7 @@ ALTER TABLE [People] ADD [Birthday] datetimeoffset({precision}) NOT NULL DEFAULT }); AssertSql( -$""" + $""" ALTER TABLE [People] ADD [Age] time({precision}) NOT NULL DEFAULT '12:34:56{fractionalSeconds}'; """); } @@ -485,7 +515,7 @@ ALTER TABLE [People] ADD [Age] time({precision}) NOT NULL DEFAULT '12:34:56{frac }); AssertSql( -""" + """ ALTER TABLE [People] ADD [Birthday] datetime NOT NULL DEFAULT '2019-01-01T00:00:00.000'; """); } @@ -507,12 +537,12 @@ ALTER TABLE [People] ADD [Birthday] datetime NOT NULL DEFAULT '2019-01-01T00:00: }); AssertSql( -""" + """ ALTER TABLE [People] ADD [Birthday] smalldatetime NOT NULL DEFAULT '2019-01-01T00:00:00'; """); } - /*[ConditionalFact] + [ConditionalFact] public virtual async Task Add_column_with_rowversion() { await Test( @@ -528,12 +558,12 @@ ALTER TABLE [People] ADD [Birthday] smalldatetime NOT NULL DEFAULT '2019-01-01T0 }); AssertSql( -""" + """ ALTER TABLE [People] ADD [RowVersion] rowversion NULL; """); - }*/ + } - /*[ConditionalFact] + [ConditionalFact] public virtual async Task Add_column_with_rowversion_and_value_conversion() { await Test( @@ -551,21 +581,39 @@ ALTER TABLE [People] ADD [RowVersion] rowversion NULL; }); 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); @@ -573,7 +621,7 @@ ALTER TABLE [People] ADD [Sum] int NOT NULL DEFAULT (1 + 2); var computedColumnTypeSql = stored == true ? " PERSISTED" : ""; AssertSql( -$""" + $""" ALTER TABLE [People] ADD [Sum] AS [X] + [Y]{computedColumnTypeSql}; """); } @@ -595,7 +643,7 @@ ALTER TABLE [People] ADD [Sum] AS [X] + [Y]{computedColumnTypeSql}; migrationsSqlGenerationOptions: MigrationsSqlGenerationOptions.Idempotent); AssertSql( -""" + """ EXEC(N'ALTER TABLE [People] ADD [IdPlusOne] AS [Id] + 1'); """); } @@ -605,7 +653,7 @@ EXEC(N'ALTER TABLE [People] ADD [IdPlusOne] AS [Id] + 1'); await base.Add_column_with_required(); AssertSql( -""" + """ ALTER TABLE [People] ADD [Name] nvarchar(max) NOT NULL DEFAULT N''; """); } @@ -615,7 +663,7 @@ ALTER TABLE [People] ADD [Name] nvarchar(max) NOT NULL DEFAULT N''; await base.Add_column_with_ansi(); AssertSql( -""" + """ ALTER TABLE [People] ADD [Name] varchar(max) NULL; """); } @@ -625,7 +673,7 @@ ALTER TABLE [People] ADD [Name] varchar(max) NULL; await base.Add_column_with_max_length(); AssertSql( -""" + """ ALTER TABLE [People] ADD [Name] nvarchar(30) NULL; """); } @@ -642,7 +690,7 @@ ALTER TABLE [People] ADD [Name] nvarchar(30) NULL; await base.Add_column_with_fixed_length(); AssertSql( -""" + """ ALTER TABLE [People] ADD [Name] nchar(100) NULL; """); } @@ -652,7 +700,7 @@ ALTER TABLE [People] ADD [Name] nchar(100) NULL; await base.Add_column_with_comment(); AssertSql( -""" + """ ALTER TABLE [People] ADD [FullName] nvarchar(max) NULL; DECLARE @defaultSchema AS sysname; SET @defaultSchema = SCHEMA_NAME(); @@ -667,7 +715,7 @@ EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSc await base.Add_column_with_collation(); AssertSql( -""" + """ ALTER TABLE [People] ADD [Name] nvarchar(max) COLLATE German_PhoneBook_CI_AS NULL; """); } @@ -694,11 +742,11 @@ ALTER TABLE [People] ADD [Name] nvarchar(max) COLLATE German_PhoneBook_CI_AS NUL 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); """); } @@ -718,7 +766,7 @@ ALTER TABLE [People] ADD CONSTRAINT [CK_People_Foo] CHECK ([DriverLicense] > 0); }); AssertSql( -""" + """ ALTER TABLE [People] ADD [IdentityColumn] int NOT NULL IDENTITY; """); } @@ -736,12 +784,12 @@ ALTER TABLE [People] ADD [IdentityColumn] int NOT NULL IDENTITY; 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[JetAnnotationNames.IdentitySeed]); - // Assert.Equal(5, column[JetAnnotationNames.IdentityIncrement]); + // Assert.Equal(100, column[SqlServerAnnotationNames.IdentitySeed]); + // Assert.Equal(5, column[SqlServerAnnotationNames.IdentityIncrement]); }); AssertSql( -""" + """ ALTER TABLE [People] ADD [IdentityColumn] int NOT NULL IDENTITY(100, 5); """); } @@ -750,21 +798,21 @@ ALTER TABLE [People] ADD [IdentityColumn] int NOT NULL IDENTITY(100, 5); public virtual async Task Add_column_identity_seed_increment_for_TPC() { await Test( - builder => + buildCommonAction: builder => { builder.Entity("Animal").UseTpcMappingStrategy().Property("Id"); builder.Entity("Cat").HasBaseType("Animal").ToTable("Cats"); builder.Entity("Dog").HasBaseType("Animal").ToTable("Dogs"); }, - builder => { }, - builder => + 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)); }, - model => + asserter:model => { Assert.Collection( model.Tables, @@ -780,8 +828,8 @@ ALTER TABLE [People] ADD [IdentityColumn] int NOT NULL IDENTITY(100, 5); 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[JetAnnotationNames.IdentitySeed]); - // Assert.Equal(5, column[JetAnnotationNames.IdentityIncrement]); + // Assert.Equal(100, column[SqlServerAnnotationNames.IdentitySeed]); + // Assert.Equal(5, column[SqlServerAnnotationNames.IdentityIncrement]); }, t => { @@ -789,21 +837,21 @@ ALTER TABLE [People] ADD [IdentityColumn] int NOT NULL IDENTITY(100, 5); 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[JetAnnotationNames.IdentitySeed]); - // Assert.Equal(5, column[JetAnnotationNames.IdentityIncrement]); + // 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; """); } @@ -813,7 +861,7 @@ ALTER TABLE [Animal] ADD [IdentityColumn] int NOT NULL DEFAULT 0; await base.Alter_column_change_type(); AssertSql( -""" + """ DECLARE @var0 sysname; SELECT @var0 = [d].[name] FROM [sys].[default_constraints] [d] @@ -829,7 +877,7 @@ ALTER TABLE [People] ALTER COLUMN [SomeColumn] bigint NOT NULL; await base.Alter_column_make_required(); AssertSql( -""" + """ DECLARE @var0 sysname; SELECT @var0 = [d].[name] FROM [sys].[default_constraints] [d] @@ -847,7 +895,7 @@ ALTER TABLE [People] ADD DEFAULT N'' FOR [SomeColumn]; await base.Alter_column_make_required_with_null_data(); AssertSql( -""" + """ DECLARE @var0 sysname; SELECT @var0 = [d].[name] FROM [sys].[default_constraints] [d] @@ -866,7 +914,7 @@ ALTER TABLE [People] ADD DEFAULT N'' FOR [SomeColumn]; await base.Alter_column_make_required_with_index(); AssertSql( -""" + """ DROP INDEX [IX_People_SomeColumn] ON [People]; DECLARE @var0 sysname; SELECT @var0 = [d].[name] @@ -887,7 +935,7 @@ CREATE INDEX [IX_People_SomeColumn] ON [People] ([SomeColumn]); 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] @@ -909,7 +957,7 @@ CREATE INDEX [IX_People_FirstName_LastName] ON [People] ([FirstName], [LastName] var computedColumnTypeSql = stored == true ? " PERSISTED" : ""; AssertSql( -$""" + $""" DECLARE @var0 sysname; SELECT @var0 = [d].[name] FROM [sys].[default_constraints] [d] @@ -926,7 +974,7 @@ ALTER TABLE [People] ADD [Sum] AS [X] + [Y]{computedColumnTypeSql}; await base.Alter_column_change_computed(); AssertSql( -""" + """ DECLARE @var0 sysname; SELECT @var0 = [d].[name] FROM [sys].[default_constraints] [d] @@ -943,7 +991,7 @@ ALTER TABLE [People] ADD [Sum] AS [X] - [Y]; await base.Alter_column_change_computed_recreates_indexes(); AssertSql( -""" + """ DROP INDEX [IX_People_Sum] ON [People]; DECLARE @var0 sysname; SELECT @var0 = [d].[name] @@ -954,8 +1002,8 @@ 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]); """); } @@ -965,7 +1013,7 @@ CREATE INDEX [IX_People_Sum] ON [People] ([Sum]); await base.Alter_column_change_computed_type(); AssertSql( -""" + """ DECLARE @var0 sysname; SELECT @var0 = [d].[name] FROM [sys].[default_constraints] [d] @@ -982,7 +1030,7 @@ ALTER TABLE [People] ADD [Sum] AS [X] + [Y] PERSISTED; await base.Alter_column_make_non_computed(); AssertSql( -""" + """ DECLARE @var0 sysname; SELECT @var0 = [d].[name] FROM [sys].[default_constraints] [d] @@ -1000,7 +1048,7 @@ ALTER TABLE [People] ADD [Sum] int NOT NULL; await base.Alter_column_add_comment(); AssertSql( -""" + """ DECLARE @defaultSchema AS sysname; SET @defaultSchema = SCHEMA_NAME(); DECLARE @description AS sql_variant; @@ -1015,7 +1063,7 @@ EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSc await base.Alter_computed_column_add_comment(); AssertSql( -""" + """ DECLARE @defaultSchema AS sysname; SET @defaultSchema = SCHEMA_NAME(); DECLARE @description AS sql_variant; @@ -1030,7 +1078,7 @@ EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSc await base.Alter_column_change_comment(); AssertSql( -""" + """ DECLARE @defaultSchema AS sysname; SET @defaultSchema = SCHEMA_NAME(); DECLARE @description AS sql_variant; @@ -1046,7 +1094,7 @@ EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSc await base.Alter_column_remove_comment(); AssertSql( -""" + """ DECLARE @defaultSchema AS sysname; SET @defaultSchema = SCHEMA_NAME(); DECLARE @description AS sql_variant; @@ -1060,7 +1108,7 @@ EXEC sp_dropextendedproperty 'MS_Description', 'SCHEMA', @defaultSchema, 'TABLE' await base.Alter_column_set_collation(); AssertSql( -""" + """ DECLARE @var0 sysname; SELECT @var0 = [d].[name] FROM [sys].[default_constraints] [d] @@ -1091,7 +1139,7 @@ ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(max) COLLATE German_PhoneBook_ }); AssertSql( -""" + """ DROP INDEX [IX_People_Name] ON [People]; DECLARE @var0 sysname; SELECT @var0 = [d].[name] @@ -1110,7 +1158,7 @@ CREATE INDEX [IX_People_Name] ON [People] ([Name]); await base.Alter_column_reset_collation(); AssertSql( -""" + """ DECLARE @var0 sysname; SELECT @var0 = [d].[name] FROM [sys].[default_constraints] [d] @@ -1121,48 +1169,150 @@ ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(max) NULL; """); } - [ConditionalFact] - public virtual async Task Alter_column_make_required_with_index_with_included_properties() + public override async Task Convert_json_entities_to_regular_owned() { - await Test( - builder => builder.Entity( - "People", e => - { - 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); - Assert.Equal(1, index.Columns.Count); - Assert.Contains(table.Columns.Single(c => c.Name == "SomeColumn"), index.Columns); - var includedColumns = (IReadOnlyList?)index[JetAnnotationNames.Include]; - Assert.Null(includedColumns); - }); + await base.Convert_json_entities_to_regular_owned(); 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]) INCLUDE ([SomeOtherColumn]); +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() { @@ -1184,7 +1334,7 @@ CREATE INDEX [IX_People_SomeColumn] ON [People] ([SomeColumn]) INCLUDE ([SomeOth }); AssertSql( -""" + """ DECLARE @var0 sysname; SELECT @var0 = [d].[name] FROM [sys].[default_constraints] [d] @@ -1196,47 +1346,7 @@ ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NULL; } [ConditionalFact] - public virtual async Task Alter_column_with_index_included_column() - { - await Test( - builder => builder.Entity( - "People", e => - { - 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); - 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); - var includedColumns = (IReadOnlyList?)index[JetAnnotationNames.Include]; - Assert.Null(includedColumns); - }); - - 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]); -"""); - } - - [ConditionalFact] - public virtual async Task Alter_column_add_identity() + public virtual async Task Alter_column_add_identity() { var ex = await TestThrows( builder => builder.Entity("People").Property("SomeColumn"), @@ -1280,7 +1390,7 @@ CREATE INDEX [IX_People_FirstName_LastName] ON [People] ([FirstName], [LastName] }); AssertSql( -""" + """ DECLARE @var0 sysname; SELECT @var0 = [d].[name] FROM [sys].[default_constraints] [d] @@ -1291,6 +1401,25 @@ 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() { @@ -1306,7 +1435,7 @@ ALTER TABLE [People] ALTER COLUMN [IdentityColumn] bigint NOT NULL; }); AssertSql( -""" + """ DECLARE @var0 sysname; SELECT @var0 = [d].[name] FROM [sys].[default_constraints] [d] @@ -1333,7 +1462,7 @@ ALTER TABLE [People] ADD DEFAULT N'Doe' FOR [Name]; }); AssertSql( -""" + """ DECLARE @defaultSchema AS sysname; SET @defaultSchema = SCHEMA_NAME(); DECLARE @description AS sql_variant; @@ -1347,7 +1476,7 @@ EXEC sp_addextendedproperty 'MS_Description', @description, 'SCHEMA', @defaultSc await base.Drop_column(); AssertSql( -""" + """ DECLARE @var0 sysname; SELECT @var0 = [d].[name] FROM [sys].[default_constraints] [d] @@ -1363,11 +1492,11 @@ ALTER TABLE [People] DROP COLUMN [SomeColumn]; 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] @@ -1383,7 +1512,7 @@ ALTER TABLE [People] DROP COLUMN [Id]; await base.Drop_column_computed_and_non_computed_with_dependency(); AssertSql( -""" + """ DECLARE @var0 sysname; SELECT @var0 = [d].[name] FROM [sys].[default_constraints] [d] @@ -1392,8 +1521,8 @@ 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] @@ -1404,13 +1533,53 @@ 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', N'COLUMN'; + """ +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'; """); } @@ -1419,7 +1588,7 @@ EXEC sp_rename N'[People].[SomeColumn]', N'SomeOtherColumn', N'COLUMN'; await base.Create_index(); AssertSql( -""" + """ DECLARE @var0 sysname; SELECT @var0 = [d].[name] FROM [sys].[default_constraints] [d] @@ -1428,8 +1597,8 @@ WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'FirstN 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]); """); } @@ -1439,7 +1608,7 @@ CREATE INDEX [IX_People_FirstName] ON [People] ([FirstName]); await base.Create_index_unique(); AssertSql( -""" + """ DECLARE @var0 sysname; SELECT @var0 = [d].[name] FROM [sys].[default_constraints] [d] @@ -1448,8 +1617,8 @@ WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'LastNa 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] @@ -1458,8 +1627,8 @@ WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'FirstN 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; """); } @@ -1469,7 +1638,7 @@ CREATE UNIQUE INDEX [IX_People_FirstName_LastName] ON [People] ([FirstName], [La await base.Create_index_descending(); AssertSql( -""" + """ CREATE INDEX [IX_People_X] ON [People] ([X] DESC); """); } @@ -1479,7 +1648,7 @@ CREATE INDEX [IX_People_X] ON [People] ([X] DESC); await base.Create_index_descending_mixed(); AssertSql( -""" + """ CREATE INDEX [IX_People_X_Y_Z] ON [People] ([X], [Y] DESC, [Z]); """); } @@ -1489,11 +1658,11 @@ CREATE INDEX [IX_People_X_Y_Z] ON [People] ([X], [Y] DESC, [Z]); await base.Alter_index_make_unique(); AssertSql( -""" + """ DROP INDEX [IX_People_X] ON [People]; """, -// -""" + // + """ CREATE UNIQUE INDEX [IX_People_X] ON [People] ([X]); """); } @@ -1503,11 +1672,11 @@ CREATE UNIQUE INDEX [IX_People_X] ON [People] ([X]); 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]); """); } @@ -1517,7 +1686,7 @@ CREATE INDEX [IX_People_X_Y_Z] ON [People] ([X], [Y] DESC, [Z]); await base.Create_index_with_filter(); AssertSql( -""" + """ DECLARE @var0 sysname; SELECT @var0 = [d].[name] FROM [sys].[default_constraints] [d] @@ -1526,8 +1695,8 @@ 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; """); } @@ -1554,7 +1723,7 @@ CREATE INDEX [IX_People_Name] ON [People] ([Name]) WHERE [Name] IS NOT NULL; migrationsSqlGenerationOptions: MigrationsSqlGenerationOptions.Idempotent); AssertSql( -""" + """ DECLARE @var0 sysname; SELECT @var0 = [d].[name] FROM [sys].[default_constraints] [d] @@ -1563,8 +1732,8 @@ 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'); """); } @@ -1574,7 +1743,7 @@ EXEC(N'CREATE INDEX [IX_People_Name] ON [People] ([Name]) WHERE [Name] IS NOT NU await base.Create_unique_index_with_filter(); AssertSql( -""" + """ DECLARE @var0 sysname; SELECT @var0 = [d].[name] FROM [sys].[default_constraints] [d] @@ -1583,237 +1752,18 @@ 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] <> ''; """); } - [ConditionalFact] - public virtual async Task Create_index_with_include() - { - await Test( - builder => builder.Entity( - "People", e => - { - 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); - Assert.Equal(1, index.Columns.Count); - Assert.Contains(table.Columns.Single(c => c.Name == "Name"), index.Columns); - var includedColumns = (IReadOnlyList?)index[JetAnnotationNames.Include]; - Assert.Null(includedColumns); - }); - - 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]); -"""); - } - - [ConditionalFact] - public virtual async Task Create_index_with_include_and_filter() - { - await Test( - builder => builder.Entity( - "People", e => - { - 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); - Assert.Equal(1, index.Columns.Count); - Assert.Contains(table.Columns.Single(c => c.Name == "Name"), index.Columns); - var includedColumns = (IReadOnlyList?)index[JetAnnotationNames.Include]; - Assert.Null(includedColumns); - }); - - 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; -"""); - } - - [ConditionalFact] - public virtual async Task Create_index_unique_with_include() - { - 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"), - model => - { - var table = Assert.Single(model.Tables); - var index = Assert.Single(table.Indexes); - Assert.True(index.IsUnique); - Assert.Equal(1, index.Columns.Count); - Assert.Contains(table.Columns.Single(c => c.Name == "Name"), index.Columns); - var includedColumns = (IReadOnlyList?)index[JetAnnotationNames.Include]; - Assert.Null(includedColumns); - }); - - 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]); -"""); - } - - [ConditionalFact] - public virtual async Task Create_index_unique_with_include_and_filter() - { - 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"), - 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); - Assert.Equal(1, index.Columns.Count); - Assert.Contains(table.Columns.Single(c => c.Name == "Name"), index.Columns); - var includedColumns = (IReadOnlyList?)index[JetAnnotationNames.Include]; - Assert.Null(includedColumns); - }); - - 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; -"""); - } - - [ConditionalFact] - public virtual async Task Create_index_unique_with_include_filter_and_fillfactor() - { - 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") - .HasFillFactor(90), - 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); - Assert.Equal(1, index.Columns.Count); - Assert.Contains(table.Columns.Single(c => c.Name == "Name"), index.Columns); - var includedColumns = (IReadOnlyList?)index[JetAnnotationNames.Include]; - Assert.Null(includedColumns); - // 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 (FILLFACTOR = 90); -"""); - } - public override async Task Drop_index() { await base.Drop_index(); AssertSql( -""" + """ DROP INDEX [IX_People_SomeField] ON [People]; """); } @@ -1823,8 +1773,8 @@ DROP INDEX [IX_People_SomeField] ON [People]; await base.Rename_index(); AssertSql( -""" -EXEC sp_rename N'[People].[Foo]', N'foo', N'INDEX'; + """ +EXEC sp_rename N'[People].[Foo]', N'foo', 'INDEX'; """); } @@ -1840,7 +1790,7 @@ EXEC sp_rename N'[People].[Foo]', N'foo', N'INDEX'; await base.Add_primary_key_string(); AssertSql( -""" + """ DECLARE @var0 sysname; SELECT @var0 = [d].[name] FROM [sys].[default_constraints] [d] @@ -1849,8 +1799,8 @@ WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'SomeFi 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]); """); } @@ -1860,7 +1810,7 @@ ALTER TABLE [People] ADD CONSTRAINT [PK_People] PRIMARY KEY ([SomeField]); await base.Add_primary_key_with_name(); AssertSql( -""" + """ DECLARE @var0 sysname; SELECT @var0 = [d].[name] FROM [sys].[default_constraints] [d] @@ -1871,8 +1821,8 @@ 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]); """); } @@ -1882,7 +1832,7 @@ ALTER TABLE [People] ADD CONSTRAINT [PK_Foo] PRIMARY KEY ([SomeField]); await base.Add_primary_key_composite_with_name(); AssertSql( -""" + """ ALTER TABLE [People] ADD CONSTRAINT [PK_Foo] PRIMARY KEY ([SomeField1], [SomeField2]); """); } @@ -1899,11 +1849,11 @@ ALTER TABLE [People] ADD CONSTRAINT [PK_Foo] PRIMARY KEY ([SomeField1], [SomeFie 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] @@ -1919,11 +1869,11 @@ ALTER TABLE [People] ALTER COLUMN [SomeField] nvarchar(max) NOT NULL; 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; """); } @@ -1938,11 +1888,11 @@ ALTER TABLE [Orders] ADD CONSTRAINT [FK_Orders_Customers_CustomerId] FOREIGN KEY // @"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; """); } @@ -1952,11 +1902,11 @@ ALTER TABLE [Orders] ADD CONSTRAINT [FK_Foo] FOREIGN KEY ([CustomerId]) REFERENC await base.Drop_foreign_key(); AssertSql( -""" + """ ALTER TABLE [Orders] DROP CONSTRAINT [FK_Orders_Customers_CustomerId]; """, -// -""" + // + """ DROP INDEX [IX_Orders_CustomerId] ON [Orders]; """); } @@ -1966,7 +1916,7 @@ DROP INDEX [IX_Orders_CustomerId] ON [Orders]; await base.Add_unique_constraint(); AssertSql( -""" + """ ALTER TABLE [People] ADD CONSTRAINT [AK_People_AlternateKeyColumn] UNIQUE ([AlternateKeyColumn]); """); } @@ -1976,7 +1926,7 @@ ALTER TABLE [People] ADD CONSTRAINT [AK_People_AlternateKeyColumn] UNIQUE ([Alte await base.Add_unique_constraint_composite_with_name(); AssertSql( -""" + """ ALTER TABLE [People] ADD CONSTRAINT [AK_Foo] UNIQUE ([AlternateKeyColumn1], [AlternateKeyColumn2]); """); } @@ -1986,7 +1936,7 @@ ALTER TABLE [People] ADD CONSTRAINT [AK_Foo] UNIQUE ([AlternateKeyColumn1], [Alt await base.Drop_unique_constraint(); AssertSql( -""" + """ ALTER TABLE [People] DROP CONSTRAINT [AK_People_AlternateKeyColumn]; """); } @@ -1996,7 +1946,7 @@ ALTER TABLE [People] DROP CONSTRAINT [AK_People_AlternateKeyColumn]; await base.Add_check_constraint_with_name(); AssertSql( -""" + """ ALTER TABLE [People] ADD CONSTRAINT [CK_People_Foo] CHECK ([DriverLicense] > 0); """); } @@ -2020,7 +1970,7 @@ ALTER TABLE [People] ADD CONSTRAINT [CK_People_Foo] CHECK ([DriverLicense] > 0); migrationsSqlGenerationOptions: MigrationsSqlGenerationOptions.Idempotent); AssertSql( -""" + """ EXEC(N'ALTER TABLE [People] ADD CONSTRAINT [CK_People_Foo] CHECK ([DriverLicense] > 0)'); """); } @@ -2030,11 +1980,11 @@ EXEC(N'ALTER TABLE [People] ADD CONSTRAINT [CK_People_Foo] CHECK ([DriverLicense 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); """); } @@ -2044,7 +1994,7 @@ ALTER TABLE [People] ADD CONSTRAINT [CK_People_Foo] CHECK ([DriverLicense] > 1); await base.Drop_check_constraint(); AssertSql( -""" + """ ALTER TABLE [People] DROP CONSTRAINT [CK_People_Foo]; """); } @@ -2054,53 +2004,18 @@ ALTER TABLE [People] DROP CONSTRAINT [CK_People_Foo]; await base.Create_sequence(); AssertSql( -""" -CREATE SEQUENCE [TestSequence] AS int START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE; + """ +CREATE SEQUENCE [TestSequence] AS int START WITH 1 INCREMENT BY 1 NO CYCLE; """); } - [ConditionalFact] - public async Task Create_sequence_byte() - { - await Test( - builder => { }, - builder => builder.HasSequence("TestSequence"), - model => - { - var sequence = Assert.Single(model.Sequences); - Assert.Equal("TestSequence", sequence.Name); - }); - AssertSql( -""" -CREATE SEQUENCE [TestSequence] AS tinyint START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE; -"""); - } - - [ConditionalFact] - public async Task Create_sequence_decimal() - { - await Test( - builder => { }, - builder => builder.HasSequence("TestSequence"), - model => - { - var sequence = Assert.Single(model.Sequences); - Assert.Equal("TestSequence", sequence.Name); - }); - - AssertSql( -""" -CREATE SEQUENCE [TestSequence] AS decimal START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE; -"""); - } - - public override async Task Create_sequence_long() + public override async Task Create_sequence_long() { await base.Create_sequence_long(); AssertSql( -""" -CREATE SEQUENCE [TestSequence] START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE; + """ +CREATE SEQUENCE [TestSequence] START WITH 1 INCREMENT BY 1 NO CYCLE; """); } @@ -2109,8 +2024,8 @@ CREATE SEQUENCE [TestSequence] START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVAL await base.Create_sequence_short(); AssertSql( -""" -CREATE SEQUENCE [TestSequence] AS smallint START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE; + """ +CREATE SEQUENCE [TestSequence] AS smallint START WITH 1 INCREMENT BY 1 NO CYCLE; """); } @@ -2119,11 +2034,11 @@ CREATE SEQUENCE [TestSequence] AS smallint START WITH 1 INCREMENT BY 1 NO MINVAL 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; """); } @@ -2133,11 +2048,11 @@ CREATE SEQUENCE [dbo2].[TestSequence] START WITH 3 INCREMENT BY 2 MINVALUE 2 MAX await base.Alter_sequence_all_settings(); AssertSql( -""" + """ ALTER SEQUENCE [foo] INCREMENT BY 2 MINVALUE -5 MAXVALUE 10 CYCLE; """, -// -""" + // + """ ALTER SEQUENCE [foo] RESTART WITH -3; """); } @@ -2147,17 +2062,25 @@ ALTER SEQUENCE [foo] RESTART WITH -3; 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]; """); } @@ -2167,8 +2090,8 @@ DROP SEQUENCE [TestSequence]; await base.Rename_sequence(); AssertSql( -""" -EXEC sp_rename N'[TestSequence]', N'testsequence'; + """ +EXEC sp_rename N'[TestSequence]', N'testsequence', 'OBJECT'; """); } @@ -2177,11 +2100,11 @@ EXEC sp_rename N'[TestSequence]', N'testsequence'; await base.Move_sequence(); AssertSql( -""" + """ IF SCHEMA_ID(N'TestSequenceSchema') IS NULL EXEC(N'CREATE SCHEMA [TestSequenceSchema];'); """, -// -""" + // + """ ALTER SCHEMA [TestSequenceSchema] TRANSFER [TestSequence]; """); } @@ -2200,7 +2123,7 @@ ALTER SCHEMA [TestSequenceSchema] TRANSFER [TestSequence]; }); AssertSql( -""" + """ DECLARE @defaultSchema sysname = SCHEMA_NAME(); EXEC(N'ALTER SCHEMA [' + @defaultSchema + N'] TRANSFER [TestSequenceSchema].[TestSequence];'); """); @@ -2224,11 +2147,11 @@ EXEC(N'ALTER SCHEMA [' + @defaultSchema + N'] TRANSFER [TestSequenceSchema].[Tes }); AssertSql( -""" -CREATE SEQUENCE [TestSequence] AS int START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE NO CYCLE; + """ +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); """); } @@ -2247,7 +2170,7 @@ ALTER TABLE [People] ADD [SeqProp] int NOT NULL DEFAULT (NEXT VALUE FOR TestSequ model => Assert.Empty(model.Sequences)); AssertSql( -""" + """ DECLARE @var0 sysname; SELECT @var0 = [d].[name] FROM [sys].[default_constraints] [d] @@ -2256,8 +2179,8 @@ WHERE ([d].[parent_object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'SeqPro IF @var0 IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT [' + @var0 + '];'); ALTER TABLE [People] DROP COLUMN [SeqProp]; """, -// -""" + // + """ DROP SEQUENCE [TestSequence]; """); } @@ -2267,7 +2190,7 @@ DROP SEQUENCE [TestSequence]; 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]) @@ -2287,7 +2210,7 @@ IF EXISTS (SELECT * FROM [sys].[identity_columns] WHERE [name] IN (N'Id', N'Name // TODO remove rowcount AssertSql( -""" + """ DELETE FROM [Person] WHERE [Id] = 2; SELECT @@ROWCOUNT; @@ -2300,7 +2223,7 @@ SELECT @@ROWCOUNT; // TODO remove rowcount AssertSql( -""" + """ DELETE FROM [Person] WHERE [AnotherId] = 12 AND [Id] = 2; SELECT @@ROWCOUNT; @@ -2313,7 +2236,7 @@ SELECT @@ROWCOUNT; // TODO remove rowcount AssertSql( -""" + """ UPDATE [Person] SET [Name] = N'Another John Snow' WHERE [Id] = 2; SELECT @@ROWCOUNT; @@ -2326,7 +2249,7 @@ SELECT @@ROWCOUNT; // TODO remove rowcount AssertSql( -""" + """ UPDATE [Person] SET [Name] = N'Another John Snow' WHERE [AnotherId] = 11 AND [Id] = 2; SELECT @@ROWCOUNT; @@ -2339,7 +2262,7 @@ SELECT @@ROWCOUNT; // TODO remove rowcount AssertSql( -""" + """ UPDATE [Person] SET [Age] = 21, [Name] = N'Another John Snow' WHERE [Id] = 2; SELECT @@ROWCOUNT; @@ -2369,7 +2292,7 @@ SELECT @@ROWCOUNT; 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]) @@ -2401,7 +2324,7 @@ IF EXISTS (SELECT * FROM [sys].[identity_columns] WHERE [name] IN (N'Id', N'Name migrationsSqlGenerationOptions: MigrationsSqlGenerationOptions.Idempotent); AssertSql( -""" + """ EXEC(N'DELETE FROM [Person] WHERE [Id] = 2; SELECT @@ROWCOUNT'); @@ -2426,7 +2349,7 @@ SELECT @@ROWCOUNT'); migrationsSqlGenerationOptions: MigrationsSqlGenerationOptions.Idempotent); AssertSql( -""" + """ EXEC(N'UPDATE [Person] SET [Name] = N''Another John Snow'' WHERE [Id] = 2; SELECT @@ROWCOUNT'); @@ -2434,1127 +2357,176 @@ SELECT @@ROWCOUNT'); } [ConditionalFact] - public virtual async Task Create_table_with_json_column() + public override async Task Add_required_primitive_collection_to_existing_table() { - await Test( - builder => { }, - builder => - { - builder.Entity( - "Entity", e => - { - e.Property("Id").ValueGeneratedOnAdd(); - e.HasKey("Id"); - e.Property("Name"); - e.OwnsOne( - "Owned", "OwnedReference", o => - { - o.OwnsOne( - "Nested", "NestedReference", n => - { - n.Property("Number"); - }); - o.OwnsMany( - "Nested2", "NestedCollection", n => - { - n.Property("Number2"); - }); - o.Property("Date"); - o.ToJson(); - }); - - e.OwnsMany( - "Owned2", "OwnedCollection", o => - { - o.OwnsOne( - "Nested3", "NestedReference2", n => - { - n.Property("Number3"); - }); - o.OwnsMany( - "Nested4", "NestedCollection2", n => - { - n.Property("Number4"); - }); - o.Property("Date2"); - o.ToJson(); - }); - - e.OwnsOne( - "Owned", "OwnedRequiredReference", o => - { - o.Property("Date"); - o.ToJson(); - }); - - e.Navigation("OwnedRequiredReference").IsRequired(); - }); - }, - model => - { - var table = Assert.Single(model.Tables); - Assert.Equal("Entity", table.Name); - - Assert.Collection( - table.Columns, - c => Assert.Equal("Id", c.Name), - c => Assert.Equal("Name", c.Name), - c => - { - Assert.Equal("OwnedCollection", c.Name); - Assert.Equal("nvarchar(max)", c.StoreType); - }, - c => - { - Assert.Equal("OwnedReference", c.Name); - Assert.Equal("nvarchar(max)", c.StoreType); - Assert.True(c.IsNullable); - }, - c => - { - Assert.Equal("OwnedRequiredReference", c.Name); - Assert.Equal("nvarchar(max)", c.StoreType); - Assert.False(c.IsNullable); - }); - Assert.Same( - table.Columns.Single(c => c.Name == "Id"), - Assert.Single(table.PrimaryKey!.Columns)); - }); + await base.Add_required_primitive_collection_to_existing_table(); 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]) -); +ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'[]'; """); } [ConditionalFact] - public virtual async Task Create_table_with_json_column_explicit_json_column_names() + public override async Task Add_required_primitive_collection_with_custom_default_value_to_existing_table() { - await Test( - builder => { }, - builder => - { - builder.Entity( - "Entity", e => - { - e.Property("Id").ValueGeneratedOnAdd(); - e.HasKey("Id"); - e.Property("Name"); - e.OwnsOne( - "Owned", "json_reference", o => - { - o.OwnsOne( - "Nested", "json_reference", n => - { - n.Property("Number"); - }); - o.OwnsMany( - "Nested2", "NestedCollection", n => - { - n.Property("Number2"); - }); - o.Property("Date"); - o.ToJson(); - }); - - e.OwnsMany( - "Owned2", "json_collection", o => - { - o.OwnsOne( - "Nested3", "NestedReference2", n => - { - n.Property("Number3"); - }); - o.OwnsMany( - "Nested4", "NestedCollection2", n => - { - n.Property("Number4"); - }); - o.Property("Date2"); - o.ToJson(); - }); - }); - }, - model => - { - var table = Assert.Single(model.Tables); - Assert.Equal("Entity", table.Name); - - Assert.Collection( - table.Columns, - c => Assert.Equal("Id", c.Name), - c => Assert.Equal("Name", c.Name), - c => - { - Assert.Equal("json_collection", c.Name); - Assert.Equal("nvarchar(max)", c.StoreType); - }, - c => - { - Assert.Equal("json_reference", c.Name); - Assert.Equal("nvarchar(max)", c.StoreType); - }); - Assert.Same( - table.Columns.Single(c => c.Name == "Id"), - Assert.Single(table.PrimaryKey!.Columns)); - }); + await base.Add_required_primitive_collection_with_custom_default_value_to_existing_table(); 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]) -); +ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'[1,2,3]'; """); } [ConditionalFact] - public virtual async Task Add_json_columns_to_existing_table() + public override async Task Add_required_primitive_collection_with_custom_default_value_sql_to_existing_table() { - await Test( - builder => builder.Entity( - "Entity", e => - { - e.Property("Id").ValueGeneratedOnAdd(); - e.HasKey("Id"); - e.Property("Name"); - }), - builder => - { - builder.Entity( - "Entity", e => - { - e.Property("Id").ValueGeneratedOnAdd(); - e.HasKey("Id"); - e.Property("Name"); - - e.OwnsOne( - "Owned", "OwnedReference", o => - { - o.OwnsOne( - "Nested", "NestedReference", n => - { - n.Property("Number"); - }); - o.OwnsMany( - "Nested2", "NestedCollection", n => - { - n.Property("Number2"); - }); - o.Property("Date"); - o.ToJson(); - }); - - e.OwnsOne( - "Owned", "OwnedRequiredReference", o => - { - o.Property("Date"); - o.ToJson(); - }); - - e.Navigation("OwnedRequiredReference").IsRequired(); - - e.OwnsMany( - "Owned2", "OwnedCollection", o => - { - o.OwnsOne( - "Nested3", "NestedReference2", n => - { - n.Property("Number3"); - }); - o.OwnsMany( - "Nested4", "NestedCollection2", n => - { - n.Property("Number4"); - }); - o.Property("Date2"); - o.ToJson(); - }); - }); - }, - model => - { - var table = Assert.Single(model.Tables); - Assert.Equal("Entity", table.Name); - - Assert.Collection( - table.Columns, - c => Assert.Equal("Id", c.Name), - c => Assert.Equal("Name", c.Name), - c => - { - Assert.Equal("OwnedCollection", c.Name); - Assert.Equal("nvarchar(max)", c.StoreType); - }, - c => - { - Assert.Equal("OwnedReference", c.Name); - Assert.Equal("nvarchar(max)", c.StoreType); - Assert.True(c.IsNullable); - }, - c => - { - Assert.Equal("OwnedRequiredReference", c.Name); - Assert.Equal("nvarchar(max)", c.StoreType); - Assert.False(c.IsNullable); - }); - Assert.Same( - table.Columns.Single(c => c.Name == "Id"), - Assert.Single(table.PrimaryKey!.Columns)); - }); + await base.Add_required_primitive_collection_with_custom_default_value_sql_to_existing_table_core("N'[3, 2, 1]'"); 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''; +ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT (N'[3, 2, 1]'); """); } - [ConditionalFact] - public virtual async Task Remove_json_columns_from_existing_table() + [ConditionalFact(Skip = "issue #33038")] + public override async Task Add_required_primitive_collection_with_custom_converter_to_existing_table() { - await Test( - builder => - { - builder.Entity( - "Entity", e => - { - e.Property("Id").ValueGeneratedOnAdd(); - e.HasKey("Id"); - e.Property("Name"); - e.OwnsOne( - "Owned", "OwnedReference", o => - { - o.OwnsOne( - "Nested", "NestedReference", n => - { - n.Property("Number"); - }); - o.OwnsMany( - "Nested2", "NestedCollection", n => - { - n.Property("Number2"); - }); - o.Property("Date"); - o.ToJson(); - }); - - e.OwnsMany( - "Owned2", "OwnedCollection", o => - { - o.OwnsOne( - "Nested3", "NestedReference2", n => - { - n.Property("Number3"); - }); - o.OwnsMany( - "Nested4", "NestedCollection2", n => - { - n.Property("Number4"); - }); - o.Property("Date2"); - o.ToJson(); - }); - }); - }, - builder => builder.Entity( - "Entity", e => - { - e.Property("Id").ValueGeneratedOnAdd(); - e.HasKey("Id"); - e.Property("Name"); - }), - model => - { - var table = Assert.Single(model.Tables); - Assert.Equal("Entity", table.Name); - - 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)); - }); + await base.Add_required_primitive_collection_with_custom_converter_to_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]; +ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'nothing'; """); } [ConditionalFact] - public virtual async Task Rename_json_column() + public override async Task Add_required_primitive_collection_with_custom_converter_and_custom_default_value_to_existing_table() { - await Test( - builder => - { - builder.Entity( - "Entity", e => - { - e.Property("Id").ValueGeneratedOnAdd(); - e.HasKey("Id"); - e.Property("Name"); - - e.OwnsOne( - "Owned", "OwnedReference", o => - { - o.OwnsOne( - "Nested", "NestedReference", n => - { - n.Property("Number"); - }); - o.OwnsMany( - "Nested2", "NestedCollection", n => - { - n.Property("Number2"); - }); - o.Property("Date"); - o.ToJson("json_reference"); - }); - - e.OwnsMany( - "Owned2", "OwnedCollection", o => - { - o.OwnsOne( - "Nested3", "NestedReference2", n => - { - n.Property("Number3"); - }); - o.OwnsMany( - "Nested4", "NestedCollection2", n => - { - n.Property("Number4"); - }); - o.Property("Date2"); - o.ToJson("json_collection"); - }); - }); - }, - builder => - { - builder.Entity( - "Entity", e => - { - e.Property("Id").ValueGeneratedOnAdd(); - e.HasKey("Id"); - e.Property("Name"); - - e.OwnsOne( - "Owned", "OwnedReference", o => - { - o.OwnsOne( - "Nested", "NestedReference", n => - { - n.Property("Number"); - }); - o.OwnsMany( - "Nested2", "NestedCollection", n => - { - n.Property("Number2"); - }); - o.Property("Date"); - o.ToJson("new_json_reference"); - }); - - e.OwnsMany( - "Owned2", "OwnedCollection", o => - { - o.OwnsOne( - "Nested3", "NestedReference2", n => - { - n.Property("Number3"); - }); - o.OwnsMany( - "Nested4", "NestedCollection2", n => - { - n.Property("Number4"); - }); - o.Property("Date2"); - o.ToJson("new_json_collection"); - }); - }); - }, - model => - { - var table = Assert.Single(model.Tables); - Assert.Equal("Entity", table.Name); - - Assert.Collection( - table.Columns, - c => Assert.Equal("Id", c.Name), - c => Assert.Equal("Name", c.Name), - c => - { - Assert.Equal("new_json_collection", c.Name); - Assert.Equal("nvarchar(max)", c.StoreType); - }, - c => - { - Assert.Equal("new_json_reference", c.Name); - Assert.Equal("nvarchar(max)", c.StoreType); - }); - Assert.Same( - table.Columns.Single(c => c.Name == "Id"), - Assert.Single(table.PrimaryKey!.Columns)); - }); + await base.Add_required_primitive_collection_with_custom_converter_and_custom_default_value_to_existing_table(); AssertSql( """ -EXEC sp_rename N'[Entity].[json_reference]', N'new_json_reference', N'COLUMN'; -""", -// -""" -EXEC sp_rename N'[Entity].[json_collection]', N'new_json_collection', N'COLUMN'; +ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'some numbers'; """); } [ConditionalFact] - public virtual async Task Rename_table_with_json_column() + public override async Task Add_optional_primitive_collection_to_existing_table() { - await Test( - builder => - { - builder.Entity( - "Entity", e => - { - e.Property("Id").ValueGeneratedOnAdd(); - e.HasKey("Id"); - e.Property("Name"); - e.ToTable("Entities"); - - e.OwnsOne( - "Owned", "OwnedReference", o => - { - o.OwnsOne( - "Nested", "NestedReference", n => - { - n.Property("Number"); - }); - o.OwnsMany( - "Nested2", "NestedCollection", n => - { - n.Property("Number2"); - }); - o.Property("Date"); - o.ToJson(); - }); - - e.OwnsMany( - "Owned2", "OwnedCollection", o => - { - o.OwnsOne( - "Nested3", "NestedReference2", n => - { - n.Property("Number3"); - }); - o.OwnsMany( - "Nested4", "NestedCollection2", n => - { - n.Property("Number4"); - }); - o.Property("Date2"); - o.ToJson(); - }); - }); - }, - builder => - { - builder.Entity( - "Entity", e => - { - e.Property("Id").ValueGeneratedOnAdd(); - e.HasKey("Id"); - e.Property("Name"); - e.ToTable("NewEntities"); - - e.OwnsOne( - "Owned", "OwnedReference", o => - { - o.OwnsOne( - "Nested", "NestedReference", n => - { - n.Property("Number"); - }); - o.OwnsMany( - "Nested2", "NestedCollection", n => - { - n.Property("Number2"); - }); - o.Property("Date"); - o.ToJson(); - }); - - e.OwnsMany( - "Owned2", "OwnedCollection", o => - { - o.OwnsOne( - "Nested3", "NestedReference2", n => - { - n.Property("Number3"); - }); - o.OwnsMany( - "Nested4", "NestedCollection2", n => - { - n.Property("Number4"); - }); - o.Property("Date2"); - o.ToJson(); - }); - }); - }, - model => - { - var table = Assert.Single(model.Tables); - Assert.Equal("NewEntities", table.Name); - - Assert.Collection( - table.Columns, - c => Assert.Equal("Id", c.Name), - c => Assert.Equal("Name", c.Name), - c => - { - Assert.Equal("OwnedCollection", c.Name); - Assert.Equal("nvarchar(max)", c.StoreType); - }, - c => - { - Assert.Equal("OwnedReference", c.Name); - Assert.Equal("nvarchar(max)", c.StoreType); - }); - Assert.Same( - table.Columns.Single(c => c.Name == "Id"), - Assert.Single(table.PrimaryKey!.Columns)); - }); + await base.Add_optional_primitive_collection_to_existing_table(); AssertSql( """ -ALTER TABLE [Entities] DROP CONSTRAINT [PK_Entities]; -""", -// -""" -EXEC sp_rename N'[Entities]', N'NewEntities'; -""", -// -""" -ALTER TABLE [NewEntities] ADD CONSTRAINT [PK_NewEntities] PRIMARY KEY ([Id]); +ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NULL; """); } [ConditionalFact] - public virtual async Task Convert_regular_owned_entities_to_json() + public override async Task Create_table_with_required_primitive_collection() { - await Test( - builder => - { - builder.Entity( - "Entity", e => - { - e.Property("Id").ValueGeneratedOnAdd(); - e.HasKey("Id"); - e.Property("Name"); - - e.OwnsOne( - "Owned", "OwnedReference", o => - { - o.OwnsOne( - "Nested", "NestedReference", n => - { - n.Property("Number"); - }); - o.OwnsMany( - "Nested2", "NestedCollection", n => - { - n.Property("Number2"); - }); - o.Property("Date"); - }); - - e.OwnsMany( - "Owned2", "OwnedCollection", o => - { - o.OwnsOne( - "Nested3", "NestedReference2", n => - { - n.Property("Number3"); - }); - o.OwnsMany( - "Nested4", "NestedCollection2", n => - { - n.Property("Number4"); - }); - o.Property("Date2"); - }); - }); - }, - builder => - { - builder.Entity( - "Entity", e => - { - e.Property("Id").ValueGeneratedOnAdd(); - e.HasKey("Id"); - e.Property("Name"); - - e.OwnsOne( - "Owned", "OwnedReference", o => - { - o.OwnsOne( - "Nested", "NestedReference", n => - { - n.Property("Number"); - }); - o.OwnsMany( - "Nested2", "NestedCollection", n => - { - n.Property("Number2"); - }); - o.Property("Date"); - o.ToJson(); - }); - - e.OwnsMany( - "Owned2", "OwnedCollection", o => - { - o.OwnsOne( - "Nested3", "NestedReference2", n => - { - n.Property("Number3"); - }); - o.OwnsMany( - "Nested4", "NestedCollection2", n => - { - n.Property("Number4"); - }); - o.Property("Date2"); - o.ToJson(); - }); - }); - }, - model => - { - var table = Assert.Single(model.Tables); - Assert.Equal("Entity", table.Name); - - Assert.Collection( - table.Columns, - c => Assert.Equal("Id", c.Name), - c => Assert.Equal("Name", c.Name), - c => - { - Assert.Equal("OwnedCollection", c.Name); - Assert.Equal("nvarchar(max)", c.StoreType); - }, - c => - { - Assert.Equal("OwnedReference", c.Name); - Assert.Equal("nvarchar(max)", c.StoreType); - }); - Assert.Same( - table.Columns.Single(c => c.Name == "Id"), - Assert.Single(table.PrimaryKey!.Columns)); - }); + await base.Create_table_with_required_primitive_collection(); 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; +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 virtual async Task Convert_json_entities_to_regular_owned() + public override async Task Create_table_with_optional_primitive_collection() { - await Test( - builder => - { - builder.Entity( - "Entity", e => - { - e.Property("Id").ValueGeneratedOnAdd(); - e.HasKey("Id"); - e.Property("Name"); - - e.OwnsOne( - "Owned", "OwnedReference", o => - { - o.OwnsOne( - "Nested", "NestedReference", n => - { - n.Property("Number"); - }); - o.OwnsMany( - "Nested2", "NestedCollection", n => - { - n.Property("Number2"); - }); - o.Property("Date"); - o.ToJson(); - }); - - e.OwnsMany( - "Owned2", "OwnedCollection", o => - { - o.OwnsOne( - "Nested3", "NestedReference2", n => - { - n.Property("Number3"); - }); - o.OwnsMany( - "Nested4", "NestedCollection2", n => - { - n.Property("Number4"); - }); - o.Property("Date2"); - o.ToJson(); - }); - }); - }, - builder => - { - builder.Entity( - "Entity", e => - { - e.Property("Id").ValueGeneratedOnAdd(); - e.HasKey("Id"); - e.Property("Name"); - - e.OwnsOne( - "Owned", "OwnedReference", o => - { - o.OwnsOne( - "Nested", "NestedReference", n => - { - n.Property("Number"); - }); - o.OwnsMany( - "Nested2", "NestedCollection", n => - { - n.Property("Number2"); - }); - o.Property("Date"); - }); - - e.OwnsMany( - "Owned2", "OwnedCollection", o => - { - o.OwnsOne( - "Nested3", "NestedReference2", n => - { - n.Property("Number3"); - }); - o.OwnsMany( - "Nested4", "NestedCollection2", n => - { - n.Property("Number4"); - }); - o.Property("Date2"); - }); - }); - }, - model => - { - Assert.Equal(4, model.Tables.Count()); - }); + await base.Create_table_with_optional_primitive_collection(); 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, +CREATE TABLE [Customers] ( [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 + [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 [Entity_OwnedCollection] ( - [EntityId] int NOT NULL, +CREATE TABLE [Contacts] ( [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 + [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( """ -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 -); +ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'[]'; """); } [ConditionalFact] - public virtual async Task Convert_string_column_to_a_json_column_containing_reference() + public override async Task Add_required_primitve_collection_with_custom_default_value_to_existing_table() { - await Test( - builder => - { - builder.Entity( - "Entity", e => - { - e.Property("Id").ValueGeneratedOnAdd(); - e.HasKey("Id"); - e.Property("Name"); - }); - }, - builder => - { - builder.Entity( - "Entity", e => - { - e.Property("Id").ValueGeneratedOnAdd(); - e.HasKey("Id"); - - e.OwnsOne( - "Owned", "OwnedReference", o => - { - o.ToJson("Name"); - o.OwnsOne( - "Nested", "NestedReference", n => - { - n.Property("Number"); - }); - o.OwnsMany( - "Nested2", "NestedCollection", n => - { - n.Property("Number2"); - }); - o.Property("Date"); - }); - }); - }, - model => - { - var table = model.Tables.Single(); - Assert.Collection( - table.Columns, - c => Assert.Equal("Id", c.Name), - c => Assert.Equal("Name", c.Name)); - }); + await base.Add_required_primitve_collection_with_custom_default_value_to_existing_table(); - AssertSql(); + AssertSql( +""" +ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'[1,2,3]'; +"""); } [ConditionalFact] - public virtual async Task Convert_string_column_to_a_json_column_containing_required_reference() + public override async Task Add_required_primitve_collection_with_custom_default_value_sql_to_existing_table() { - await Test( - builder => - { - builder.Entity( - "Entity", e => - { - e.Property("Id").ValueGeneratedOnAdd(); - e.HasKey("Id"); - e.Property("Name"); - }); - }, - builder => - { - builder.Entity( - "Entity", e => - { - e.Property("Id").ValueGeneratedOnAdd(); - e.HasKey("Id"); - - e.OwnsOne( - "Owned", "OwnedReference", o => - { - o.ToJson("Name"); - o.OwnsOne( - "Nested", "NestedReference", n => - { - n.Property("Number"); - }); - o.OwnsMany( - "Nested2", "NestedCollection", n => - { - n.Property("Number2"); - }); - o.Property("Date"); - }); - - e.Navigation("OwnedReference").IsRequired(); - }); - }, - model => - { - var table = model.Tables.Single(); - Assert.Collection( - table.Columns, - c => Assert.Equal("Id", c.Name), - c => Assert.Equal("Name", c.Name)); - }); + await base.Add_required_primitve_collection_with_custom_default_value_sql_to_existing_table_core("N'[3, 2, 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'[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]; +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 virtual async Task Convert_string_column_to_a_json_column_containing_collection() + public override async Task Add_required_primitve_collection_with_custom_converter_and_custom_default_value_to_existing_table() { - await Test( - builder => - { - builder.Entity( - "Entity", e => - { - e.Property("Id").ValueGeneratedOnAdd(); - e.HasKey("Id"); - e.Property("Name"); - }); - }, - builder => - { - builder.Entity( - "Entity", e => - { - e.Property("Id").ValueGeneratedOnAdd(); - e.HasKey("Id"); - - e.OwnsMany( - "Owned2", "OwnedCollection", o => - { - o.OwnsOne( - "Nested3", "NestedReference2", n => - { - n.Property("Number3"); - }); - o.OwnsMany( - "Nested4", "NestedCollection2", n => - { - n.Property("Number4"); - }); - o.Property("Date2"); - o.ToJson("Name"); - }); - }); - }, - model => - { - var table = model.Tables.Single(); - Assert.Collection( - table.Columns, - c => Assert.Equal("Id", c.Name), - c => Assert.Equal("Name", c.Name)); - }); + await base.Add_required_primitve_collection_with_custom_converter_and_custom_default_value_to_existing_table(); - AssertSql(); + AssertSql( +""" +ALTER TABLE [Customers] ADD [Numbers] nvarchar(max) NOT NULL DEFAULT N'some numbers'; +"""); } protected override string NonDefaultCollation diff --git a/test/EFCore.Jet.FunctionalTests/ModelBuilding/JetModelBuilderGenericTest.cs b/test/EFCore.Jet.FunctionalTests/ModelBuilding/JetModelBuilderGenericTest.cs new file mode 100644 index 0000000..90895ca --- /dev/null +++ b/test/EFCore.Jet.FunctionalTests/ModelBuilding/JetModelBuilderGenericTest.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// ReSharper disable InconsistentNaming + +using EntityFrameworkCore.Jet.FunctionalTests.ModelBuilding; +using System; + +namespace Microsoft.EntityFrameworkCore.ModelBuilding; + +public class JetModelBuilderGenericTest : JetModelBuilderTestBase +{ + public class JetGenericNonRelationship(JetModelBuilderFixture fixture) : JetNonRelationship(fixture) + { + protected override TestModelBuilder CreateModelBuilder( + Action? configure) + => new GenericTestModelBuilder(Fixture, configure); + } + + public class JetGenericComplexType(JetModelBuilderFixture fixture) : JetComplexType(fixture) + { + protected override TestModelBuilder CreateModelBuilder( + Action? configure) + => new GenericTestModelBuilder(Fixture, configure); + } + + public class JetGenericInheritance(JetModelBuilderFixture fixture) : JetInheritance(fixture) + { + protected override TestModelBuilder CreateModelBuilder( + Action? configure) + => new GenericTestModelBuilder(Fixture, configure); + } + + public class JetGenericOneToMany(JetModelBuilderFixture fixture) : JetOneToMany(fixture) + { + protected override TestModelBuilder CreateModelBuilder( + Action? configure) + => new GenericTestModelBuilder(Fixture, configure); + } + + public class JetGenericManyToOne(JetModelBuilderFixture fixture) : JetManyToOne(fixture) + { + protected override TestModelBuilder CreateModelBuilder( + Action? configure) + => new GenericTestModelBuilder(Fixture, configure); + } + + public class JetGenericOneToOne(JetModelBuilderFixture fixture) : JetOneToOne(fixture) + { + protected override TestModelBuilder CreateModelBuilder( + Action? configure) + => new GenericTestModelBuilder(Fixture, configure); + } + + public class JetGenericManyToMany(JetModelBuilderFixture fixture) : JetManyToMany(fixture) + { + protected override TestModelBuilder CreateModelBuilder( + Action? configure) + => new GenericTestModelBuilder(Fixture, configure); + } + + public class JetGenericOwnedTypes(JetModelBuilderFixture fixture) : JetOwnedTypes(fixture) + { + protected override TestModelBuilder CreateModelBuilder( + Action? configure) + => new GenericTestModelBuilder(Fixture, configure); + } +} diff --git a/test/EFCore.Jet.FunctionalTests/ModelBuilding/JetModelBuilderNonGenericTest.cs b/test/EFCore.Jet.FunctionalTests/ModelBuilding/JetModelBuilderNonGenericTest.cs new file mode 100644 index 0000000..c180a7f --- /dev/null +++ b/test/EFCore.Jet.FunctionalTests/ModelBuilding/JetModelBuilderNonGenericTest.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// ReSharper disable InconsistentNaming + +using System; +using Microsoft.EntityFrameworkCore; + +namespace EntityFrameworkCore.Jet.FunctionalTests.ModelBuilding; + +public class JetModelBuilderNonGenericTest : JetModelBuilderTestBase +{ + public class JetNonGenericNonRelationship(JetModelBuilderFixture fixture) : JetNonRelationship(fixture) + { + protected override TestModelBuilder CreateModelBuilder(Action? configure = null) + => new NonGenericTestModelBuilder(Fixture, configure); + } + + public class JetNonGenericComplexType(JetModelBuilderFixture fixture) : JetComplexType(fixture) + { + protected override TestModelBuilder CreateModelBuilder(Action? configure = null) + => new NonGenericTestModelBuilder(Fixture, configure); + } + + public class JetNonGenericInheritance(JetModelBuilderFixture fixture) : JetInheritance(fixture) + { + protected override TestModelBuilder CreateModelBuilder(Action? configure = null) + => new NonGenericTestModelBuilder(Fixture, configure); + } + + public class JetNonGenericOneToMany(JetModelBuilderFixture fixture) : JetOneToMany(fixture) + { + protected override TestModelBuilder CreateModelBuilder(Action? configure = null) + => new NonGenericTestModelBuilder(Fixture, configure); + } + + public class JetNonGenericManyToOne(JetModelBuilderFixture fixture) : JetManyToOne(fixture) + { + protected override TestModelBuilder CreateModelBuilder(Action? configure = null) + => new NonGenericTestModelBuilder(Fixture, configure); + } + + public class JetNonGenericOneToOne(JetModelBuilderFixture fixture) : JetOneToOne(fixture) + { + protected override TestModelBuilder CreateModelBuilder(Action? configure = null) + => new NonGenericTestModelBuilder(Fixture, configure); + } + + public class JetNonGenericManyToMany(JetModelBuilderFixture fixture) : JetManyToMany(fixture) + { + protected override TestModelBuilder CreateModelBuilder(Action? configure = null) + => new NonGenericTestModelBuilder(Fixture, configure); + } + + public class JetNonGenericOwnedTypes(JetModelBuilderFixture fixture) : JetOwnedTypes(fixture) + { + protected override TestModelBuilder CreateModelBuilder(Action? configure = null) + => new NonGenericTestModelBuilder(Fixture, configure); + } +} diff --git a/test/EFCore.Jet.FunctionalTests/ModelBuilding/JetModelBuilderTestBase.cs b/test/EFCore.Jet.FunctionalTests/ModelBuilding/JetModelBuilderTestBase.cs new file mode 100644 index 0000000..b2ca8e4 --- /dev/null +++ b/test/EFCore.Jet.FunctionalTests/ModelBuilding/JetModelBuilderTestBase.cs @@ -0,0 +1,1770 @@ +// 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.Collections.ObjectModel; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Conventions; +using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; +using Microsoft.EntityFrameworkCore.ModelBuilding; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; +using System.Linq; +using EntityFrameworkCore.Jet.FunctionalTests.TestUtilities; + +namespace EntityFrameworkCore.Jet.FunctionalTests.ModelBuilding; + +public class JetModelBuilderTestBase : RelationalModelBuilderTest +{ + public abstract class JetNonRelationship(JetModelBuilderFixture fixture) : RelationalNonRelationshipTestBase(fixture), IClassFixture + { + [ConditionalFact] + public virtual void Index_has_a_filter_if_nonclustered_unique_with_nullable_properties() + { + var modelBuilder = CreateModelBuilder(); + var entityTypeBuilder = modelBuilder + .Entity(); + var indexBuilder = entityTypeBuilder + .HasIndex(ix => ix.Name) + .IsUnique(); + + var entityType = modelBuilder.Model.FindEntityType(typeof(Customer))!; + var index = entityType.GetIndexes().Single(); + Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); + + indexBuilder.IsUnique(false); + + Assert.Null(index.GetFilter()); + + indexBuilder.IsUnique(); + + Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); + + /*indexBuilder.IsClustered(); + + Assert.Null(index.GetFilter()); + + indexBuilder.IsClustered(false);*/ + + Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); + + entityTypeBuilder.Property(e => e.Name).IsRequired(); + + Assert.Null(index.GetFilter()); + + entityTypeBuilder.Property(e => e.Name).IsRequired(false); + + Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); + + entityTypeBuilder.Property(e => e.Name).HasColumnName("RelationalName"); + + Assert.Equal("[RelationalName] IS NOT NULL", index.GetFilter()); + + entityTypeBuilder.Property(e => e.Name).HasColumnName("SqlServerName"); + + Assert.Equal("[SqlServerName] IS NOT NULL", index.GetFilter()); + + entityTypeBuilder.Property(e => e.Name).HasColumnName(null); + + Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); + + indexBuilder.HasFilter("Foo"); + + Assert.Equal("Foo", index.GetFilter()); + + indexBuilder.HasFilter(null); + + Assert.Null(index.GetFilter()); + } + + [ConditionalFact] + public void Indexes_can_have_same_name_across_tables() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity() + .HasIndex(e => e.Id, "Ix_Id") + .IsUnique(); + modelBuilder.Entity() + .HasIndex(e => e.CustomerId, "Ix_Id") + .IsUnique(); + + var model = modelBuilder.FinalizeModel(); + + var customerIndex = model.FindEntityType(typeof(Customer))!.GetIndexes().Single(); + Assert.Equal("Ix_Id", customerIndex.Name); + Assert.Equal("Ix_Id", customerIndex.GetDatabaseName()); + Assert.Equal( + "Ix_Id", customerIndex.GetDatabaseName( + StoreObjectIdentifier.Table("Customer"))); + + var detailsIndex = model.FindEntityType(typeof(CustomerDetails))!.GetIndexes().Single(); + Assert.Equal("Ix_Id", detailsIndex.Name); + Assert.Equal("Ix_Id", detailsIndex.GetDatabaseName()); + Assert.Equal( + "Ix_Id", detailsIndex.GetDatabaseName( + StoreObjectIdentifier.Table("CustomerDetails"))); + } + + [ConditionalFact] + public virtual void Can_set_store_type_for_property_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().HaveColumnType("smallint"); + c.Properties().HaveColumnType("nchar(max)"); + c.Properties(typeof(Nullable<>)).HavePrecision(2); + }); + + modelBuilder.Entity( + b => + { + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(Quarks))!; + + Assert.Equal("smallint", entityType.FindProperty(Customer.IdProperty.Name)!.GetColumnType()); + Assert.Equal("smallint", entityType.FindProperty("Up")!.GetColumnType()); + Assert.Equal("nchar(max)", entityType.FindProperty("Down")!.GetColumnType()); + var charm = entityType.FindProperty("Charm")!; + Assert.Equal("smallint", charm.GetColumnType()); + Assert.Null(charm.GetPrecision()); + Assert.Equal("nchar(max)", entityType.FindProperty("Strange")!.GetColumnType()); + var top = entityType.FindProperty("Top")!; + Assert.Equal("smallint", top.GetColumnType()); + Assert.Equal(2, top.GetPrecision()); + Assert.Equal("nchar(max)", entityType.FindProperty("Bottom")!.GetColumnType()); + } + + [ConditionalFact] + public virtual void Can_set_fixed_length_for_property_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().AreFixedLength(false); + c.Properties().AreFixedLength(); + }); + + modelBuilder.Entity( + b => + { + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(Quarks))!; + + Assert.False(entityType.FindProperty(Customer.IdProperty.Name)!.IsFixedLength()); + Assert.False(entityType.FindProperty("Up")!.IsFixedLength()); + Assert.True(entityType.FindProperty("Down")!.IsFixedLength()); + Assert.False(entityType.FindProperty("Charm")!.IsFixedLength()); + Assert.True(entityType.FindProperty("Strange")!.IsFixedLength()); + Assert.False(entityType.FindProperty("Top")!.IsFixedLength()); + Assert.True(entityType.FindProperty("Bottom")!.IsFixedLength()); + } + + [ConditionalFact] + public virtual void Can_set_collation_for_property_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().UseCollation("Latin1_General_CS_AS_KS_WS"); + c.Properties().UseCollation("Latin1_General_BIN"); + }); + + modelBuilder.Entity( + b => + { + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(Quarks))!; + + Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty(Customer.IdProperty.Name)!.GetCollation()); + Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Up")!.GetCollation()); + Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Down")!.GetCollation()); + Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Charm")!.GetCollation()); + Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Strange")!.GetCollation()); + Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Top")!.GetCollation()); + Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Bottom")!.GetCollation()); + } + + [ConditionalFact] + public virtual void Can_set_store_type_for_primitive_collection() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.Up).HasColumnType("national character varying(255)"); + b.PrimitiveCollection(e => e.Down).HasColumnType("nchar(10)"); + b.PrimitiveCollection("Charm").HasColumnType("nvarchar(25)"); + b.PrimitiveCollection("Strange").HasColumnType("text"); + b.PrimitiveCollection>("Top").HasColumnType("char(100)"); + ; + b.PrimitiveCollection?>("Bottom").HasColumnType("varchar(max)"); + ; + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(CollectionQuarks))!; + + Assert.Equal("integer", entityType.FindProperty(nameof(CollectionQuarks.Id))!.GetColumnType()); + Assert.Equal("national character varying(255)", entityType.FindProperty("Up")!.GetColumnType()); + Assert.Equal("nchar(10)", entityType.FindProperty("Down")!.GetColumnType()); + Assert.Equal("nvarchar(25)", entityType.FindProperty("Charm")!.GetColumnType()); + Assert.Equal("text", entityType.FindProperty("Strange")!.GetColumnType()); + Assert.Equal("char(100)", entityType.FindProperty("Top")!.GetColumnType()); + Assert.Equal("varchar(max)", entityType.FindProperty("Bottom")!.GetColumnType()); + } + + [ConditionalFact] + public virtual void Can_set_fixed_length_for_primitive_collection() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.Up).IsFixedLength(false); + b.PrimitiveCollection(e => e.Down).IsFixedLength(); + b.PrimitiveCollection("Charm").IsFixedLength(); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(CollectionQuarks))!; + + Assert.False(entityType.FindProperty("Up")!.IsFixedLength()); + Assert.True(entityType.FindProperty("Down")!.IsFixedLength()); + Assert.True(entityType.FindProperty("Charm")!.IsFixedLength()); + } + + [ConditionalFact] + public virtual void Can_set_collation_for_primitive_collection() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity( + b => + { + b.PrimitiveCollection(e => e.Up).UseCollation("Latin1_General_CS_AS_KS_WS"); + b.PrimitiveCollection(e => e.Down).UseCollation("Latin1_General_BIN"); + b.PrimitiveCollection("Charm").UseCollation("Latin1_General_CI_AI"); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(CollectionQuarks))!; + + Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Up")!.GetCollation()); + Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Down")!.GetCollation()); + Assert.Equal("Latin1_General_CI_AI", entityType.FindProperty("Charm")!.GetCollation()); + } + + /*[ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual void Can_avoid_attributes_when_discovering_properties(bool useAttributes) + { + var modelBuilder = CreateModelBuilder(c => c.Conventions.Replace( + s => new PropertyDiscoveryConvention( + s.GetService()!, useAttributes))); + modelBuilder.Entity(); + + if (useAttributes) + { + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(SqlVariantEntity))!; + + Assert.Equal([nameof(SqlVariantEntity.Id), nameof(SqlVariantEntity.Value),], + entityType.GetProperties().Select(p => p.Name)); + } + else + { + Assert.Equal(CoreStrings.PropertyNotAdded(nameof(SqlVariantEntity), nameof(SqlVariantEntity.Value), "object"), + Assert.Throws(modelBuilder.FinalizeModel).Message); + } + }*/ + + protected class SqlVariantEntity + { + public int Id { get; set; } + [Column(TypeName = "sql_variant")] + public object? Value { get; set; } + } + } + + public abstract class JetComplexType(JetModelBuilderFixture fixture) : RelationalComplexTypeTestBase(fixture), IClassFixture; + + public abstract class JetInheritance(JetModelBuilderFixture fixture) : RelationalInheritanceTestBase(fixture), IClassFixture + { + [ConditionalFact] // #7240 + public void Can_use_shadow_FK_that_collides_with_convention_shadow_FK_on_other_derived_type() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity(); + modelBuilder.Entity() + .HasOne(p => p.A) + .WithOne() + .HasForeignKey("ParentId"); + + var model = modelBuilder.FinalizeModel(); + + var property1 = model.FindEntityType(typeof(DisjointChildSubclass1))!.FindProperty("ParentId")!; + Assert.True(property1.IsForeignKey()); + Assert.Equal("ParentId", property1.GetColumnName()); + var property2 = model.FindEntityType(typeof(DisjointChildSubclass2))!.FindProperty("ParentId")!; + Assert.True(property2.IsForeignKey()); + Assert.Equal("ParentId", property2.GetColumnName()); + Assert.Equal("DisjointChildSubclass2_ParentId", property2.GetColumnName(StoreObjectIdentifier.Table(nameof(Child)))); + } + + [ConditionalFact] + public void Inherited_clr_properties_are_mapped_to_the_same_column() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity(); + modelBuilder.Ignore(); + modelBuilder.Entity(); + modelBuilder.Entity(); + + var model = modelBuilder.FinalizeModel(); + + var property1 = model.FindEntityType(typeof(DisjointChildSubclass1))!.FindProperty(nameof(Child.Name))!; + Assert.Equal(nameof(Child.Name), property1.GetColumnName()); + var property2 = model.FindEntityType(typeof(DisjointChildSubclass2))!.FindProperty(nameof(Child.Name))!; + Assert.Equal(nameof(Child.Name), property2.GetColumnName()); + } + + [ConditionalFact] //Issue#10659 + public void Index_convention_run_for_fk_when_derived_type_discovered_before_base_type() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Ignore(); + modelBuilder.Entity(); + modelBuilder.Entity(); + + var index = modelBuilder.Model.FindEntityType(typeof(CustomerDetails))!.GetIndexes().Single(); + + Assert.Equal("[CustomerId] IS NOT NULL", index.GetFilter()); + } + + [ConditionalFact] + public void Index_convention_sets_filter_for_unique_index_when_base_type_changed() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Ignore(); + modelBuilder.Entity() + .HasIndex(e => e.CustomerId) + .IsUnique(); + + modelBuilder.Entity(); + + var index = modelBuilder.Model.FindEntityType(typeof(CustomerDetails))!.GetIndexes().Single(); + + Assert.Equal("[CustomerId] IS NOT NULL", index.GetFilter()); + + modelBuilder.Ignore(); + + Assert.Null(index.GetFilter()); + } + + [ConditionalFact] + public virtual void Can_override_TPC_with_TPH() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity

(); + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity() + .UseTpcMappingStrategy() + .UseTphMappingStrategy(); + + var model = modelBuilder.FinalizeModel(); + + Assert.Equal("Discriminator", model.FindEntityType(typeof(PBase))!.GetDiscriminatorPropertyName()); + Assert.Equal(nameof(PBase), model.FindEntityType(typeof(PBase))!.GetDiscriminatorValue()); + Assert.Equal(nameof(P), model.FindEntityType(typeof(P))!.GetDiscriminatorValue()); + Assert.Equal(nameof(Q), model.FindEntityType(typeof(Q))!.GetDiscriminatorValue()); + } + + [ConditionalFact] + public virtual void TPT_identifying_FK_is_created_only_on_declaring_table() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity() + .Ignore(b => b.Bun) + .Ignore(b => b.Pickles); + modelBuilder.Entity( + b => + { + b.ToTable("Ingredients"); + b.Ignore(i => i.BigMak); + }); + modelBuilder.Entity( + b => + { + b.ToTable("Buns"); + b.HasOne(i => i.BigMak).WithOne().HasForeignKey(i => i.Id); + }); + modelBuilder.Entity( + b => + { + b.ToTable("SesameBuns"); + }); + + var model = modelBuilder.FinalizeModel(); + + var principalType = model.FindEntityType(typeof(BigMak))!; + Assert.Empty(principalType.GetForeignKeys()); + Assert.Empty(principalType.GetIndexes()); + Assert.Null(principalType.FindDiscriminatorProperty()); + + var ingredientType = model.FindEntityType(typeof(Ingredient))!; + + var bunType = model.FindEntityType(typeof(Bun))!; + Assert.Empty(bunType.GetIndexes()); + Assert.Null(bunType.FindDiscriminatorProperty()); + var bunFk = bunType.GetDeclaredForeignKeys().Single(fk => !fk.IsBaseLinking()); + Assert.Equal("FK_Buns_BigMak_Id", bunFk.GetConstraintName()); + Assert.Equal( + "FK_Buns_BigMak_Id", bunFk.GetConstraintName( + StoreObjectIdentifier.Create(bunType, StoreObjectType.Table)!.Value, + StoreObjectIdentifier.Create(principalType, StoreObjectType.Table)!.Value)); + Assert.Single(bunFk.GetMappedConstraints()); + + var bunLinkingFk = bunType.GetDeclaredForeignKeys().Single(fk => fk.IsBaseLinking()); + Assert.Equal("FK_Buns_Ingredients_Id", bunLinkingFk.GetConstraintName()); + Assert.Equal( + "FK_Buns_Ingredients_Id", bunLinkingFk.GetConstraintName( + StoreObjectIdentifier.Create(bunType, StoreObjectType.Table)!.Value, + StoreObjectIdentifier.Create(ingredientType, StoreObjectType.Table)!.Value)); + Assert.Single(bunLinkingFk.GetMappedConstraints()); + + var sesameBunType = model.FindEntityType(typeof(SesameBun))!; + Assert.Empty(sesameBunType.GetIndexes()); + var sesameBunFk = sesameBunType.GetDeclaredForeignKeys().Single(); + Assert.True(sesameBunFk.IsBaseLinking()); + Assert.Equal("FK_SesameBuns_Buns_Id", sesameBunFk.GetConstraintName()); + Assert.Equal( + "FK_SesameBuns_Buns_Id", sesameBunFk.GetConstraintName( + StoreObjectIdentifier.Create(sesameBunType, StoreObjectType.Table)!.Value, + StoreObjectIdentifier.Create(bunType, StoreObjectType.Table)!.Value)); + Assert.Single(sesameBunFk.GetMappedConstraints()); + } + + [ConditionalFact] + public virtual void TPC_identifying_FKs_are_created_on_all_tables() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity() + .Ignore(b => b.Bun) + .Ignore(b => b.Pickles); + modelBuilder.Entity( + b => + { + b.ToTable("Ingredients"); + b.Ignore(i => i.BigMak); + b.HasIndex(e => e.BurgerId); + b.UseTpcMappingStrategy(); + }); + modelBuilder.Entity( + b => + { + b.ToTable("Buns"); + b.HasOne(i => i.BigMak).WithOne().HasForeignKey(i => i.Id); + b.UseTpcMappingStrategy(); + }); + modelBuilder.Entity( + b => + { + b.ToTable("SesameBuns"); + }); + + var model = modelBuilder.FinalizeModel(); + + var principalType = model.FindEntityType(typeof(BigMak))!; + Assert.Empty(principalType.GetForeignKeys()); + Assert.Empty(principalType.GetIndexes()); + Assert.Null(principalType.FindDiscriminatorProperty()); + + var bunType = model.FindEntityType(typeof(Bun))!; + Assert.Empty(bunType.GetDeclaredIndexes()); + Assert.Null(bunType.FindDiscriminatorProperty()); + var bunFk = bunType.GetDeclaredForeignKeys().Single(); + Assert.Equal("FK_Buns_BigMak_Id", bunFk.GetConstraintName()); + Assert.Equal( + "FK_Buns_BigMak_Id", bunFk.GetConstraintName( + StoreObjectIdentifier.Create(bunType, StoreObjectType.Table)!.Value, + StoreObjectIdentifier.Create(principalType, StoreObjectType.Table)!.Value)); + Assert.Equal(2, bunFk.GetMappedConstraints().Count()); + + Assert.DoesNotContain(bunType.GetDeclaredForeignKeys(), fk => fk.IsBaseLinking()); + + var sesameBunType = model.FindEntityType(typeof(SesameBun))!; + Assert.Empty(sesameBunType.GetDeclaredIndexes()); + Assert.Empty(sesameBunType.GetDeclaredForeignKeys()); + Assert.Equal( + "FK_SesameBuns_BigMak_Id", bunFk.GetConstraintName( + StoreObjectIdentifier.Create(sesameBunType, StoreObjectType.Table)!.Value, + StoreObjectIdentifier.Create(principalType, StoreObjectType.Table)!.Value)); + + var ingredientType = model.FindEntityType(typeof(Ingredient))!; + var ingredientIndex = ingredientType.GetDeclaredIndexes().Single(); + Assert.Equal("IX_Ingredients_BurgerId", ingredientIndex.GetDatabaseName()); + Assert.Equal( + "IX_SesameBuns_BurgerId", + ingredientIndex.GetDatabaseName(StoreObjectIdentifier.Create(sesameBunType, StoreObjectType.Table)!.Value)); + Assert.Equal( + "IX_Buns_BurgerId", + ingredientIndex.GetDatabaseName(StoreObjectIdentifier.Create(bunType, StoreObjectType.Table)!.Value)); + } + + [ConditionalFact] + public virtual void TPT_index_can_use_inherited_properties() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity() + .Ignore(b => b.Bun) + .Ignore(b => b.Pickles); + modelBuilder.Entity( + b => + { + b.ToTable("Ingredients"); + b.Property("NullableProp"); + b.Ignore(i => i.BigMak); + }); + modelBuilder.Entity( + b => + { + b.ToTable("Buns"); + b.HasIndex(bun => bun.BurgerId); + b.HasIndex("NullableProp"); + b.HasOne(i => i.BigMak).WithOne().HasForeignKey(i => i.Id); + }); + + var model = modelBuilder.FinalizeModel(); + + var bunType = model.FindEntityType(typeof(Bun))!; + Assert.All(bunType.GetIndexes(), i => Assert.Null(i.GetFilter())); + } + + [ConditionalFact] + public void Can_add_check_constraints() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity() + .HasBaseType(null) + .ToTable(tb => tb.HasCheckConstraint("CK_ChildBase_LargeId", "Id > 1000").HasName("CK_LargeId")); + modelBuilder.Entity() + .ToTable( + tb => + { + tb.HasCheckConstraint("PositiveId", "Id > 0"); + tb.HasCheckConstraint("CK_ChildBase_LargeId", "Id > 1000"); + }); + modelBuilder.Entity() + .HasBaseType(); + modelBuilder.Entity(); + + var model = modelBuilder.FinalizeModel(); + + var @base = model.FindEntityType(typeof(ChildBase))!; + Assert.Equal(2, @base.GetCheckConstraints().Count()); + + var firstCheckConstraint = @base.FindCheckConstraint("PositiveId")!; + Assert.Equal("PositiveId", firstCheckConstraint.ModelName); + Assert.Equal("Id > 0", firstCheckConstraint.Sql); + Assert.Equal("PositiveId", firstCheckConstraint.Name); + + var secondCheckConstraint = @base.FindCheckConstraint("CK_ChildBase_LargeId")!; + Assert.Equal("CK_ChildBase_LargeId", secondCheckConstraint.ModelName); + Assert.Equal("Id > 1000", secondCheckConstraint.Sql); + Assert.Equal("CK_LargeId", secondCheckConstraint.Name); + + var child = model.FindEntityType(typeof(Child))!; + Assert.Equal(@base.GetCheckConstraints(), child.GetCheckConstraints()); + Assert.Empty(child.GetDeclaredCheckConstraints()); + } + + [ConditionalFact] + public void Adding_conflicting_check_constraint_to_derived_type_throws() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity() + .ToTable(tb => tb.HasCheckConstraint("LargeId", "Id > 100").HasName("CK_LargeId")); + + Assert.Equal( + RelationalStrings.DuplicateCheckConstraint("LargeId", nameof(Child), nameof(ChildBase)), + Assert.Throws( + () => modelBuilder.Entity().ToTable(tb => tb.HasCheckConstraint("LargeId", "Id > 1000"))).Message); + } + + [ConditionalFact] + public void Adding_conflicting_check_constraint_to_derived_type_before_base_throws() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity() + .HasBaseType(null) + .ToTable(tb => tb.HasCheckConstraint("LargeId", "Id > 1000")); + modelBuilder.Entity() + .ToTable(tb => tb.HasCheckConstraint("LargeId", "Id > 100").HasName("CK_LargeId")); + + Assert.Equal( + RelationalStrings.DuplicateCheckConstraint("LargeId", nameof(Child), nameof(ChildBase)), + Assert.Throws( + () => modelBuilder.Entity().HasBaseType()).Message); + } + + protected class Parent + { + public int Id { get; set; } + public DisjointChildSubclass1? A { get; set; } + public IList? B { get; set; } + } + + protected abstract class ChildBase + { + public int Id { get; set; } + } + + protected abstract class Child : ChildBase + { + public string? Name { get; set; } + } + + protected class DisjointChildSubclass1 : Child; + + protected class DisjointChildSubclass2 : Child; + } + + public abstract class JetOneToMany(JetModelBuilderFixture fixture) : RelationalOneToManyTestBase(fixture), IClassFixture + { + [ConditionalFact] + public virtual void Shadow_foreign_keys_to_generic_types_have_terrible_names_that_should_not_change() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().ToTable("Events"); + modelBuilder.Entity>().ToTable("CompanyActivities"); + modelBuilder.Entity>().ToTable("UserActivities"); + + var model = modelBuilder.FinalizeModel(); + + var companyActivityEventType = model.FindEntityType(typeof(ActivityEvent))!; + var eventTable = StoreObjectIdentifier.Create(companyActivityEventType, StoreObjectType.Table)!.Value; + var companyActivityEventFk = companyActivityEventType.GetForeignKeys().Single(); + var companyActivityEventFkProperty = companyActivityEventFk.Properties.Single(); + Assert.Equal("ActivityId", companyActivityEventFkProperty.GetColumnName(eventTable)); + Assert.Equal("FK_Events_CompanyActivities_ActivityId", companyActivityEventFk.GetConstraintName()); + Assert.Equal( + "FK_Events_CompanyActivities_ActivityId", companyActivityEventFk.GetConstraintName( + eventTable, + StoreObjectIdentifier.Create(companyActivityEventFk.PrincipalEntityType, StoreObjectType.Table)!.Value)); + + var userActivityEventType = model.FindEntityType(typeof(ActivityEvent))!; + var userActivityEventFk = userActivityEventType.GetForeignKeys().Single(); + var userActivityEventFkProperty = userActivityEventFk.Properties.Single(); + Assert.Equal("ActivityId", userActivityEventFkProperty.GetColumnName(eventTable)); + Assert.Equal("FK_Events_UserActivities_ActivityId", userActivityEventFk.GetConstraintName()); + Assert.Equal( + "FK_Events_UserActivities_ActivityId", userActivityEventFk.GetConstraintName( + eventTable, + StoreObjectIdentifier.Create(userActivityEventFk.PrincipalEntityType, StoreObjectType.Table)!.Value)); + } + + protected abstract class EventBase + { + public string? Id { get; set; } + } + + protected class Activity + { + public string? Id { get; set; } + public virtual List> Events { get; } = null!; + } + + protected class ActivityEvent : EventBase; + + protected class Company; + + protected class User; + } + + public abstract class JetManyToOne(JetModelBuilderFixture fixture) : RelationalManyToOneTestBase(fixture), IClassFixture; + + public abstract class JetOneToOne(JetModelBuilderFixture fixture) : RelationalOneToOneTestBase(fixture), IClassFixture; + + public abstract class JetManyToMany(JetModelBuilderFixture fixture) : RelationalManyToManyTestBase(fixture), IClassFixture + { + [ConditionalFact] + public virtual void Join_entity_type_uses_same_schema() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().ToTable("Category", "mySchema").Ignore(c => c.ProductCategories); + modelBuilder.Entity().ToTable("Product", "mySchema"); + modelBuilder.Entity(); + + var model = modelBuilder.FinalizeModel(); + + var productType = model.FindEntityType(typeof(Product))!; + var categoryType = model.FindEntityType(typeof(Category))!; + + var categoriesNavigation = productType.GetSkipNavigations().Single(); + var productsNavigation = categoryType.GetSkipNavigations().Single(); + + var categoriesFk = categoriesNavigation.ForeignKey; + var productsFk = productsNavigation.ForeignKey; + var productCategoryType = categoriesFk.DeclaringEntityType; + + Assert.Equal(typeof(Dictionary), productCategoryType.ClrType); + Assert.Equal("mySchema", productCategoryType.GetSchema()); + Assert.Same(categoriesFk, productCategoryType.GetForeignKeys().Last()); + Assert.Same(productsFk, productCategoryType.GetForeignKeys().First()); + Assert.Equal(2, productCategoryType.GetForeignKeys().Count()); + } + + [ConditionalFact] + public virtual void Join_entity_type_uses_default_schema_if_related_are_different() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().ToTable("Category").Ignore(c => c.ProductCategories); + modelBuilder.Entity().ToTable("Product", "dbo"); + modelBuilder.Entity(); + + var model = modelBuilder.FinalizeModel(); + + var productType = model.FindEntityType(typeof(Product))!; + var categoryType = model.FindEntityType(typeof(Category))!; + + var categoriesNavigation = productType.GetSkipNavigations().Single(); + var productsNavigation = categoryType.GetSkipNavigations().Single(); + + var categoriesFk = categoriesNavigation.ForeignKey; + var productsFk = productsNavigation.ForeignKey; + var productCategoryType = categoriesFk.DeclaringEntityType; + + Assert.Equal(typeof(Dictionary), productCategoryType.ClrType); + Assert.Null(productCategoryType.GetSchema()); + Assert.Same(categoriesFk, productCategoryType.GetForeignKeys().Last()); + Assert.Same(productsFk, productCategoryType.GetForeignKeys().First()); + Assert.Equal(2, productCategoryType.GetForeignKeys().Count()); + } + } + + public abstract class JetOwnedTypes(JetModelBuilderFixture fixture) : RelationalOwnedTypesTestBase(fixture), IClassFixture + { + [ConditionalFact] + public virtual void Owned_types_use_table_splitting_by_default() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().OwnsOne( + b => b.AlternateLabel, + b => + { + b.Ignore(l => l.Book); + b.OwnsOne( + l => l.AnotherBookLabel, + ab => + { + ab.Property(l => l.BookId).HasColumnName("BookId2"); + ab.Ignore(l => l.Book); + ab.OwnsOne( + s => s.SpecialBookLabel, + s => + { + s.Property(l => l.BookId).HasColumnName("BookId2"); + s.Ignore(l => l.Book); + s.Ignore(l => l.BookLabel); + }); + }); + }); + + modelBuilder.Entity().OwnsOne(b => b.Label) + .Ignore(l => l.Book) + .OwnsOne(l => l.SpecialBookLabel) + .Ignore(l => l.Book) + .OwnsOne(a => a.AnotherBookLabel) + .Ignore(l => l.Book); + + modelBuilder.Entity().OwnsOne(b => b.Label) + .OwnsOne(l => l.AnotherBookLabel) + .Ignore(l => l.Book) + .OwnsOne(a => a.SpecialBookLabel) + .Ignore(l => l.Book) + .Ignore(l => l.BookLabel); + + modelBuilder.Entity().OwnsOne( + b => b.AlternateLabel, + b => + { + b.Ignore(l => l.Book); + b.OwnsOne( + l => l.SpecialBookLabel, + ab => + { + ab.Property(l => l.BookId).HasColumnName("BookId2"); + ab.Ignore(l => l.Book); + ab.OwnsOne( + s => s.AnotherBookLabel, + s => + { + s.Property(l => l.BookId).HasColumnName("BookId2"); + s.Ignore(l => l.Book); + }); + }); + }); + + var model = (IModel)modelBuilder.Model; + var book = model.FindEntityType(typeof(Book))!; + var bookOwnership1 = book.FindNavigation(nameof(Book.Label))!.ForeignKey; + var bookOwnership2 = book.FindNavigation(nameof(Book.AlternateLabel))!.ForeignKey; + var bookLabel1Ownership1 = bookOwnership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel))!.ForeignKey; + var bookLabel1Ownership2 = bookOwnership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel))!.ForeignKey; + var bookLabel2Ownership1 = bookOwnership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel))!.ForeignKey; + var bookLabel2Ownership2 = bookOwnership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel))!.ForeignKey; + + Assert.Equal(book.GetTableName(), bookOwnership1.DeclaringEntityType.GetTableName()); + Assert.Equal(book.GetTableName(), bookOwnership2.DeclaringEntityType.GetTableName()); + Assert.Equal(book.GetTableName(), bookLabel1Ownership1.DeclaringEntityType.GetTableName()); + Assert.Equal(book.GetTableName(), bookLabel1Ownership2.DeclaringEntityType.GetTableName()); + Assert.Equal(book.GetTableName(), bookLabel2Ownership1.DeclaringEntityType.GetTableName()); + Assert.Equal(book.GetTableName(), bookLabel2Ownership2.DeclaringEntityType.GetTableName()); + + Assert.NotSame(bookOwnership1.DeclaringEntityType, bookOwnership2.DeclaringEntityType); + Assert.Single(bookOwnership1.DeclaringEntityType.GetForeignKeys()); + Assert.Single(bookOwnership1.DeclaringEntityType.GetForeignKeys()); + + Assert.NotSame(bookLabel1Ownership1.DeclaringEntityType, bookLabel2Ownership1.DeclaringEntityType); + Assert.NotSame(bookLabel1Ownership2.DeclaringEntityType, bookLabel2Ownership2.DeclaringEntityType); + Assert.Single(bookLabel1Ownership1.DeclaringEntityType.GetForeignKeys()); + Assert.Single(bookLabel1Ownership2.DeclaringEntityType.GetForeignKeys()); + Assert.Single(bookLabel2Ownership1.DeclaringEntityType.GetForeignKeys()); + Assert.Single(bookLabel2Ownership2.DeclaringEntityType.GetForeignKeys()); + + Assert.Equal(2, model.GetEntityTypes().Count(e => e.ClrType == typeof(BookLabel))); + Assert.Equal(4, model.GetEntityTypes().Count(e => e.ClrType == typeof(AnotherBookLabel))); + Assert.Equal(4, model.GetEntityTypes().Count(e => e.ClrType == typeof(SpecialBookLabel))); + + Assert.Null( + bookOwnership1.DeclaringEntityType.FindProperty(nameof(BookLabel.Id))! + .GetColumnName(StoreObjectIdentifier.Table("Label"))); + Assert.Null( + bookLabel2Ownership1.DeclaringEntityType.FindProperty(nameof(BookLabel.Id))! + .GetColumnName(StoreObjectIdentifier.Table("AlternateLabel"))); + + modelBuilder.Entity().OwnsOne(b => b.Label).ToTable("Label"); + modelBuilder.Entity().OwnsOne(b => b.AlternateLabel).ToTable("AlternateLabel"); + + model = modelBuilder.FinalizeModel(); + + Assert.Equal( + nameof(BookLabel.Id), + bookOwnership1.DeclaringEntityType.FindProperty(nameof(BookLabel.Id))! + .GetColumnName(StoreObjectIdentifier.Table("Label"))); + Assert.Equal( + nameof(BookLabel.AnotherBookLabel) + "_" + nameof(BookLabel.Id), + bookLabel2Ownership1.DeclaringEntityType.FindProperty(nameof(BookLabel.Id))! + .GetColumnName(StoreObjectIdentifier.Table("AlternateLabel"))); + + var alternateTable = model.GetRelationalModel().FindTable("AlternateLabel", null)!; + var bookId = alternateTable.FindColumn("BookId2")!; + + Assert.Equal(4, bookId.PropertyMappings.Count()); + Assert.All(bookId.PropertyMappings, m => Assert.Equal(ValueGenerated.OnUpdateSometimes, m.Property.ValueGenerated)); + } + + [ConditionalFact] + public virtual void Owned_types_can_be_mapped_to_different_tables() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + modelBuilder.Entity( + bb => + { + bb.ToTable( + "BT", "BS", t => + { + t.ExcludeFromMigrations(); + + Assert.Equal("BT", t.Name); + Assert.Equal("BS", t.Schema); + }); + bb.OwnsOne( + b => b.AlternateLabel, tb => + { + tb.Ignore(l => l.Book); + tb.WithOwner() + .HasConstraintName("AlternateLabelFK"); + tb.ToTable("TT", "TS"); + tb.OwnsOne( + l => l.AnotherBookLabel, ab => + { + ab.Ignore(l => l.Book); + ab.ToTable( + "AT1", "AS1", t => + { + t.ExcludeFromMigrations(false); + + Assert.Equal("AT1", t.Name); + Assert.Equal("AS1", t.Schema); + }); + ab.OwnsOne(s => s.SpecialBookLabel) + .ToTable("ST11", "SS11") + .Ignore(l => l.Book) + .Ignore(l => l.BookLabel); + + ab.OwnedEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel))! + .AddAnnotation("Foo", "Bar"); + }); + tb.OwnsOne( + l => l.SpecialBookLabel, sb => + { + sb.Ignore(l => l.Book); + sb.ToTable("ST2", "SS2"); + sb.OwnsOne(s => s.AnotherBookLabel) + .ToTable("AT21", "AS21") + .Ignore(l => l.Book); + }); + }); + bb.OwnsOne( + b => b.Label, lb => + { + lb.Ignore(l => l.Book); + lb.ToTable("LT", "LS"); + lb.OwnsOne( + l => l.SpecialBookLabel, sb => + { + sb.Ignore(l => l.Book); + sb.ToTable("ST1", "SS1"); + sb.OwnsOne(a => a.AnotherBookLabel) + .ToTable("AT11", "AS11") + .Ignore(l => l.Book); + }); + lb.OwnsOne( + l => l.AnotherBookLabel, ab => + { + ab.Ignore(l => l.Book); + ab.ToTable("AT2", "AS2"); + ab.OwnsOne(a => a.SpecialBookLabel) + .ToTable("ST21", "SS21") + .Ignore(l => l.BookLabel) + .Ignore(l => l.Book); + }); + }); + }); + + modelBuilder.FinalizeModel(); + + var book = model.FindEntityType(typeof(Book))!; + var bookOwnership1 = book.FindNavigation(nameof(Book.Label))!.ForeignKey; + var bookOwnership2 = book.FindNavigation(nameof(Book.AlternateLabel))!.ForeignKey; + var bookLabel1Ownership1 = bookOwnership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel))!.ForeignKey; + var bookLabel1Ownership2 = bookOwnership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel))!.ForeignKey; + var bookLabel2Ownership1 = bookOwnership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel))!.ForeignKey; + var bookLabel2Ownership2 = bookOwnership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel))!.ForeignKey; + var bookLabel1Ownership11 = bookLabel1Ownership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel))! + .ForeignKey; + var bookLabel1Ownership21 = bookLabel1Ownership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel))! + .ForeignKey; + var bookLabel2Ownership11 = bookLabel2Ownership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel))! + .ForeignKey; + var bookLabel2Ownership21 = bookLabel2Ownership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel))! + .ForeignKey; + + Assert.Equal("AlternateLabelFK", bookOwnership2.GetConstraintName()); + + Assert.Equal("BS", book.GetSchema()); + Assert.Equal("BT", book.GetTableName()); + Assert.True(book.IsTableExcludedFromMigrations()); + Assert.Equal("LS", bookOwnership1.DeclaringEntityType.GetSchema()); + Assert.Equal("LT", bookOwnership1.DeclaringEntityType.GetTableName()); + Assert.True(bookOwnership1.DeclaringEntityType.IsTableExcludedFromMigrations()); + Assert.Equal("TS", bookOwnership2.DeclaringEntityType.GetSchema()); + Assert.Equal("TT", bookOwnership2.DeclaringEntityType.GetTableName()); + Assert.True(bookOwnership2.DeclaringEntityType.IsTableExcludedFromMigrations()); + Assert.Equal("AS2", bookLabel1Ownership1.DeclaringEntityType.GetSchema()); + Assert.Equal("AT2", bookLabel1Ownership1.DeclaringEntityType.GetTableName()); + Assert.Equal("SS1", bookLabel1Ownership2.DeclaringEntityType.GetSchema()); + Assert.Equal("ST1", bookLabel1Ownership2.DeclaringEntityType.GetTableName()); + Assert.Equal("AS1", bookLabel2Ownership1.DeclaringEntityType.GetSchema()); + Assert.Equal("AT1", bookLabel2Ownership1.DeclaringEntityType.GetTableName()); + Assert.False(bookLabel2Ownership1.DeclaringEntityType.IsTableExcludedFromMigrations()); + Assert.Equal("SS2", bookLabel2Ownership2.DeclaringEntityType.GetSchema()); + Assert.Equal("ST2", bookLabel2Ownership2.DeclaringEntityType.GetTableName()); + Assert.Equal("SS21", bookLabel1Ownership11.DeclaringEntityType.GetSchema()); + Assert.Equal("ST21", bookLabel1Ownership11.DeclaringEntityType.GetTableName()); + Assert.Equal("AS11", bookLabel1Ownership21.DeclaringEntityType.GetSchema()); + Assert.Equal("AT11", bookLabel1Ownership21.DeclaringEntityType.GetTableName()); + Assert.Equal("SS11", bookLabel2Ownership11.DeclaringEntityType.GetSchema()); + Assert.Equal("ST11", bookLabel2Ownership11.DeclaringEntityType.GetTableName()); + Assert.Equal("AS21", bookLabel2Ownership21.DeclaringEntityType.GetSchema()); + Assert.Equal("AT21", bookLabel2Ownership21.DeclaringEntityType.GetTableName()); + + Assert.Equal("Bar", bookLabel2Ownership11.PrincipalToDependent?["Foo"]); + + Assert.NotSame(bookOwnership1.DeclaringEntityType, bookOwnership2.DeclaringEntityType); + Assert.Single(bookOwnership1.DeclaringEntityType.GetForeignKeys()); + Assert.Single(bookOwnership2.DeclaringEntityType.GetForeignKeys()); + + Assert.NotSame(bookLabel1Ownership1.DeclaringEntityType, bookLabel2Ownership1.DeclaringEntityType); + Assert.NotSame(bookLabel1Ownership2.DeclaringEntityType, bookLabel2Ownership2.DeclaringEntityType); + Assert.Single(bookLabel1Ownership1.DeclaringEntityType.GetForeignKeys()); + Assert.Single(bookLabel1Ownership2.DeclaringEntityType.GetForeignKeys()); + Assert.Single(bookLabel2Ownership1.DeclaringEntityType.GetForeignKeys()); + Assert.Single(bookLabel2Ownership2.DeclaringEntityType.GetForeignKeys()); + + Assert.NotSame(bookLabel1Ownership11.DeclaringEntityType, bookLabel2Ownership11.DeclaringEntityType); + Assert.NotSame(bookLabel1Ownership21.DeclaringEntityType, bookLabel2Ownership21.DeclaringEntityType); + Assert.Single(bookLabel1Ownership11.DeclaringEntityType.GetForeignKeys()); + Assert.Single(bookLabel1Ownership21.DeclaringEntityType.GetForeignKeys()); + Assert.Single(bookLabel2Ownership11.DeclaringEntityType.GetForeignKeys()); + Assert.Single(bookLabel2Ownership21.DeclaringEntityType.GetForeignKeys()); + + Assert.Equal(2, model.GetEntityTypes().Count(e => e.ClrType == typeof(BookLabel))); + Assert.Equal(4, model.GetEntityTypes().Count(e => e.ClrType == typeof(AnotherBookLabel))); + Assert.Equal(4, model.GetEntityTypes().Count(e => e.ClrType == typeof(SpecialBookLabel))); + + Assert.Equal(ValueGenerated.Never, bookOwnership1.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); + Assert.Equal(ValueGenerated.Never, bookOwnership2.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); + + Assert.Equal( + ValueGenerated.Never, bookLabel1Ownership1.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); + Assert.Equal( + ValueGenerated.Never, bookLabel1Ownership2.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); + Assert.Equal( + ValueGenerated.Never, bookLabel2Ownership1.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); + Assert.Equal( + ValueGenerated.Never, bookLabel2Ownership2.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); + + Assert.Equal( + ValueGenerated.Never, bookLabel1Ownership11.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); + Assert.Equal( + ValueGenerated.Never, bookLabel1Ownership21.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); + Assert.Equal( + ValueGenerated.Never, bookLabel2Ownership11.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); + Assert.Equal( + ValueGenerated.Never, bookLabel2Ownership21.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); + } + + [ConditionalFact] + public virtual void Owned_type_collections_can_be_mapped_to_different_tables() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + modelBuilder.Entity().OwnsMany( + c => c.Orders, + r => + { + r.HasKey(o => o.OrderId); + //r.ToTable(tb => tb.IsMemoryOptimized()); + r.Ignore(o => o.OrderCombination); + r.Ignore(o => o.Details); + }); + + var ownership = model.FindEntityType(typeof(Customer))!.FindNavigation(nameof(Customer.Orders))!.ForeignKey; + var owned = ownership.DeclaringEntityType; + Assert.True(ownership.IsOwnership); + Assert.Equal(nameof(Order.Customer), ownership.DependentToPrincipal?.Name); + Assert.Equal("FK_Order_Customer_CustomerId", ownership.GetConstraintName()); + + Assert.Single(owned.GetForeignKeys()); + Assert.Single(owned.GetIndexes()); + Assert.Equal( + new[] { nameof(Order.OrderId), nameof(Order.AnotherCustomerId), nameof(Order.CustomerId) }, + owned.GetProperties().Select(p => p.GetColumnName())); + Assert.Equal(nameof(Order), owned.GetTableName()); + Assert.Null(owned.GetSchema()); + + modelBuilder.Entity().OwnsMany( + c => c.Orders, + r => + { + r.WithOwner(o => o.Customer).HasConstraintName("Owned"); + r.ToTable("bar", "foo"); + }); + + Assert.Equal("bar", owned.GetTableName()); + Assert.Equal("foo", owned.GetSchema()); + Assert.Equal("Owned", ownership.GetConstraintName()); + + modelBuilder.Entity().OwnsMany( + c => c.Orders, + r => r.ToTable("blah")); + + modelBuilder.FinalizeModel(); + + Assert.Equal("blah", owned.GetTableName()); + Assert.Null(owned.GetSchema()); + } + + [ConditionalFact] + public virtual void Owned_type_collections_can_be_mapped_to_a_view() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().OwnsMany( + c => c.Orders, + r => + { + r.HasKey(o => o.OrderId); + r.Ignore(o => o.OrderCombination); + r.Ignore(o => o.Details); + r.ToView("bar", "foo"); + }); + + var model = modelBuilder.FinalizeModel(); + + var owner = model.FindEntityType(typeof(Customer))!; + var ownership = owner.FindNavigation(nameof(Customer.Orders))!.ForeignKey; + var owned = ownership.DeclaringEntityType; + Assert.True(ownership.IsOwnership); + Assert.Equal(nameof(Order.Customer), ownership.DependentToPrincipal?.Name); + Assert.Empty(ownership.GetMappedConstraints()); + + Assert.Equal(nameof(Customer), owner.GetTableName()); + Assert.Null(owner.GetSchema()); + + Assert.Null(owned.GetForeignKeys().Single().GetConstraintName()); + Assert.Single(owned.GetIndexes()); + Assert.Null(owned.FindPrimaryKey()!.GetName()); + Assert.Equal( + new[] { nameof(Order.OrderId), nameof(Order.AnotherCustomerId), nameof(Order.CustomerId) }, + owned.GetProperties().Select(p => p.GetColumnName())); + Assert.Null(owned.GetTableName()); + Assert.Null(owned.GetSchema()); + Assert.Equal("bar", owned.GetViewName()); + Assert.Equal("foo", owned.GetViewSchema()); + } + + [ConditionalFact] + public virtual void Owner_can_be_mapped_to_a_view() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().OwnsMany( + c => c.Orders, + r => + { + r.HasKey(o => o.OrderId); + r.Ignore(o => o.OrderCombination); + r.Ignore(o => o.Details); + }) + .ToView("bar", "foo"); + + var model = modelBuilder.FinalizeModel(); + + var owner = model.FindEntityType(typeof(Customer))!; + var ownership = owner.FindNavigation(nameof(Customer.Orders))!.ForeignKey; + var owned = ownership.DeclaringEntityType; + Assert.True(ownership.IsOwnership); + Assert.Equal(nameof(Order.Customer), ownership.DependentToPrincipal?.Name); + Assert.Empty(ownership.GetMappedConstraints()); + + Assert.Null(owner.GetTableName()); + Assert.Null(owner.GetSchema()); + Assert.Equal("bar", owner.GetViewName()); + Assert.Equal("foo", owner.GetViewSchema()); + + Assert.Null(owned.GetForeignKeys().Single().GetConstraintName()); + Assert.Equal("IX_Order_CustomerId", owned.GetIndexes().Single().GetDatabaseName()); + Assert.Equal("PK_Order", owned.FindPrimaryKey()!.GetName()); + Assert.Equal( + new[] { nameof(Order.OrderId), nameof(Order.AnotherCustomerId), nameof(Order.CustomerId) }, + owned.GetProperties().Select(p => p.GetColumnName())); + Assert.Equal(nameof(Order), owned.GetTableName()); + Assert.Null(owned.GetSchema()); + } + + [ConditionalFact] + public virtual void Json_entity_and_normal_owned_can_exist_side_by_side_on_same_entity() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.OwnsOne(x => x.OwnedReference1); + b.OwnsOne(x => x.OwnedReference2, bb => bb.ToJson("reference")); + b.OwnsMany(x => x.OwnedCollection1); + b.OwnsMany(x => x.OwnedCollection2, bb => bb.ToJson("collection")); + }); + + var model = modelBuilder.FinalizeModel(); + var owner = model.FindEntityType(typeof(JsonEntity))!; + Assert.False(owner.IsMappedToJson()); + Assert.True(owner.GetDeclaredProperties().All(x => x.GetJsonPropertyName() == null)); + + var ownedEntities = model.FindEntityTypes(typeof(OwnedEntity)); + Assert.Equal(4, ownedEntities.Count()); + Assert.Equal(2, ownedEntities.Where(e => e.IsMappedToJson()).Count()); + Assert.Equal(2, ownedEntities.Where(e => e.IsOwned() && !e.IsMappedToJson()).Count()); + var reference = ownedEntities.Where(e => e.GetContainerColumnName() == "reference").Single(); + Assert.Equal("Date", reference.GetProperty("Date").GetJsonPropertyName()); + Assert.Equal("Fraction", reference.GetProperty("Fraction").GetJsonPropertyName()); + Assert.Equal("Enum", reference.GetProperty("Enum").GetJsonPropertyName()); + + var collection = ownedEntities.Where(e => e.GetContainerColumnName() == "collection").Single(); + Assert.Equal("Date", collection.GetProperty("Date").GetJsonPropertyName()); + Assert.Equal("Fraction", collection.GetProperty("Fraction").GetJsonPropertyName()); + Assert.Equal("Enum", collection.GetProperty("Enum").GetJsonPropertyName()); + + var nonJson = ownedEntities.Where(e => !e.IsMappedToJson()).ToList(); + Assert.True(nonJson.All(x => x.GetProperty("Date").GetJsonPropertyName() == null)); + Assert.True(nonJson.All(x => x.GetProperty("Fraction").GetJsonPropertyName() == null)); + Assert.True(nonJson.All(x => x.GetProperty("Enum").GetJsonPropertyName() == null)); + } + + [ConditionalFact] + public virtual void Json_entity_with_tph_inheritance() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.OwnsOne(x => x.OwnedReferenceOnBase, bb => bb.ToJson("reference_on_base")); + b.OwnsMany(x => x.OwnedCollectionOnBase, bb => bb.ToJson("collection_on_base")); + }); + + modelBuilder.Entity( + b => + { + b.HasBaseType(); + b.OwnsOne(x => x.OwnedReferenceOnDerived, bb => bb.ToJson("reference_on_derived")); + b.OwnsMany(x => x.OwnedCollectionOnDerived, bb => bb.ToJson("collection_on_derived")); + }); + + var model = modelBuilder.FinalizeModel(); + var ownedEntities = model.FindEntityTypes(typeof(OwnedEntity)).ToList(); + Assert.Equal(4, ownedEntities.Count()); + + foreach (var ownedEntity in ownedEntities) + { + Assert.Equal("Date", ownedEntity.GetProperty("Date").GetJsonPropertyName()); + Assert.Equal("Fraction", ownedEntity.GetProperty("Fraction").GetJsonPropertyName()); + Assert.Equal("Enum", ownedEntity.GetProperty("Enum").GetJsonPropertyName()); + } + + var jsonColumnNames = ownedEntities.Select(x => x.GetContainerColumnName()).OrderBy(x => x).ToList(); + Assert.Equal("collection_on_base", jsonColumnNames[0]); + Assert.Equal("collection_on_derived", jsonColumnNames[1]); + Assert.Equal("reference_on_base", jsonColumnNames[2]); + Assert.Equal("reference_on_derived", jsonColumnNames[3]); + } + + [ConditionalFact] + public virtual void Json_entity_with_nested_structure_same_property_names() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity( + b => + { + b.OwnsOne( + x => x.OwnedReference1, bb => + { + bb.ToJson("ref1"); + bb.OwnsOne(x => x.Reference1); + bb.OwnsOne(x => x.Reference2); + bb.OwnsMany(x => x.Collection1); + bb.OwnsMany(x => x.Collection2); + }); + + b.OwnsOne( + x => x.OwnedReference2, bb => + { + bb.ToJson("ref2"); + bb.OwnsOne(x => x.Reference1); + bb.OwnsOne(x => x.Reference2); + bb.OwnsMany(x => x.Collection1); + bb.OwnsMany(x => x.Collection2); + }); + + b.OwnsMany( + x => x.OwnedCollection1, bb => + { + bb.ToJson("col1"); + bb.OwnsOne(x => x.Reference1); + bb.OwnsOne(x => x.Reference2); + bb.OwnsMany(x => x.Collection1); + bb.OwnsMany(x => x.Collection2); + }); + + b.OwnsMany( + x => x.OwnedCollection2, bb => + { + bb.ToJson("col2"); + bb.OwnsOne(x => x.Reference1); + bb.OwnsOne(x => x.Reference2); + bb.OwnsMany(x => x.Collection1); + bb.OwnsMany(x => x.Collection2); + }); + }); + + var model = modelBuilder.FinalizeModel(); + var outerOwnedEntities = model.FindEntityTypes(typeof(OwnedEntityExtraLevel)); + Assert.Equal(4, outerOwnedEntities.Count()); + + foreach (var outerOwnedEntity in outerOwnedEntities) + { + Assert.Equal("Date", outerOwnedEntity.GetProperty("Date").GetJsonPropertyName()); + Assert.Equal("Fraction", outerOwnedEntity.GetProperty("Fraction").GetJsonPropertyName()); + Assert.Equal("Enum", outerOwnedEntity.GetProperty("Enum").GetJsonPropertyName()); + Assert.Equal( + "Reference1", + outerOwnedEntity.GetNavigations().Single(n => n.Name == "Reference1").TargetEntityType.GetJsonPropertyName()); + Assert.Equal( + "Reference2", + outerOwnedEntity.GetNavigations().Single(n => n.Name == "Reference2").TargetEntityType.GetJsonPropertyName()); + Assert.Equal( + "Collection1", + outerOwnedEntity.GetNavigations().Single(n => n.Name == "Collection1").TargetEntityType.GetJsonPropertyName()); + Assert.Equal( + "Collection2", + outerOwnedEntity.GetNavigations().Single(n => n.Name == "Collection2").TargetEntityType.GetJsonPropertyName()); + } + + var ownedEntities = model.FindEntityTypes(typeof(OwnedEntity)); + Assert.Equal(16, ownedEntities.Count()); + + foreach (var ownedEntity in ownedEntities) + { + Assert.Equal("Date", ownedEntity.GetProperty("Date").GetJsonPropertyName()); + Assert.Equal("Fraction", ownedEntity.GetProperty("Fraction").GetJsonPropertyName()); + Assert.Equal("Enum", ownedEntity.GetProperty("Enum").GetJsonPropertyName()); + } + } + + [ConditionalFact] + public virtual void Json_entity_nested_enums_have_conversions_to_int_by_default_ToJson_first() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity( + b => + { + b.OwnsOne( + x => x.OwnedReference1, bb => + { + bb.ToJson(); + bb.OwnsOne(x => x.Reference1); + bb.OwnsOne(x => x.Reference2); + bb.OwnsMany(x => x.Collection1); + bb.OwnsMany(x => x.Collection2); + }); + + b.Ignore(x => x.OwnedReference2); + b.OwnsMany( + x => x.OwnedCollection1, bb => + { + bb.ToJson(); + bb.OwnsOne(x => x.Reference1); + bb.OwnsOne(x => x.Reference2); + bb.OwnsMany(x => x.Collection1); + bb.OwnsMany(x => x.Collection2); + }); + + b.Ignore(x => x.OwnedCollection2); + }); + + var model = modelBuilder.FinalizeModel(); + var outerOwnedEntities = model.FindEntityTypes(typeof(OwnedEntityExtraLevel)); + Assert.Equal(2, outerOwnedEntities.Count()); + + foreach (var outerOwnedEntity in outerOwnedEntities) + { + Assert.True(outerOwnedEntity.IsMappedToJson()); + var myEnum = outerOwnedEntity.GetDeclaredProperties().Where(p => p.ClrType.IsEnum).Single(); + var typeMapping = myEnum.FindRelationalTypeMapping()!; + Assert.True(typeMapping.Converter is EnumToNumberConverter); + } + + var ownedEntities = model.FindEntityTypes(typeof(OwnedEntity)); + Assert.Equal(8, ownedEntities.Count()); + + foreach (var ownedEntity in ownedEntities) + { + Assert.True(ownedEntity.IsMappedToJson()); + var myEnum = ownedEntity.GetDeclaredProperties().Where(p => p.ClrType.IsEnum).Single(); + var typeMapping = myEnum.FindRelationalTypeMapping()!; + Assert.True(typeMapping.Converter is EnumToNumberConverter); + } + } + + [ConditionalFact] + public virtual void Json_entity_nested_enums_have_conversions_to_int_by_default_ToJson_last() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity( + b => + { + b.OwnsOne( + x => x.OwnedReference1, bb => + { + bb.OwnsOne(x => x.Reference1); + bb.OwnsOne(x => x.Reference2); + bb.OwnsMany(x => x.Collection1); + bb.OwnsMany(x => x.Collection2); + bb.ToJson(); + }); + + b.Ignore(x => x.OwnedReference2); + b.OwnsMany( + x => x.OwnedCollection1, bb => + { + bb.OwnsOne(x => x.Reference1); + bb.OwnsOne(x => x.Reference2); + bb.OwnsMany(x => x.Collection1); + bb.OwnsMany(x => x.Collection2); + bb.ToJson(); + }); + + b.Ignore(x => x.OwnedCollection2); + }); + + var model = modelBuilder.FinalizeModel(); + var outerOwnedEntities = model.FindEntityTypes(typeof(OwnedEntityExtraLevel)); + Assert.Equal(2, outerOwnedEntities.Count()); + + foreach (var outerOwnedEntity in outerOwnedEntities) + { + Assert.True(outerOwnedEntity.IsMappedToJson()); + var myEnum = outerOwnedEntity.GetDeclaredProperties().Where(p => p.ClrType.IsEnum).Single(); + var typeMapping = myEnum.FindRelationalTypeMapping()!; + Assert.True(typeMapping.Converter is EnumToNumberConverter); + } + + var ownedEntities = model.FindEntityTypes(typeof(OwnedEntity)); + Assert.Equal(8, ownedEntities.Count()); + + foreach (var ownedEntity in ownedEntities) + { + Assert.True(ownedEntity.IsMappedToJson()); + var myEnum = ownedEntity.GetDeclaredProperties().Where(p => p.ClrType.IsEnum).Single(); + var typeMapping = myEnum.FindRelationalTypeMapping()!; + Assert.True(typeMapping.Converter is EnumToNumberConverter); + } + } + + [ConditionalFact] + public virtual void Entity_mapped_to_json_and_unwound_afterwards_properly_cleans_up_its_state() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity( + b => + { + b.OwnsOne( + x => x.OwnedReference1, bb => + { + bb.ToJson(); + bb.OwnsOne(x => x.Reference1); + bb.OwnsOne(x => x.Reference2); + bb.OwnsMany(x => x.Collection1); + bb.OwnsMany(x => x.Collection2); + bb.ToJson(null); + }); + + b.Ignore(x => x.OwnedReference2); + b.OwnsMany( + x => x.OwnedCollection1, bb => + { + bb.OwnsOne(x => x.Reference1); + bb.OwnsOne(x => x.Reference2); + bb.OwnsMany(x => x.Collection1); + bb.OwnsMany(x => x.Collection2); + bb.ToJson(); + bb.ToJson(null); + }); + + b.Ignore(x => x.OwnedCollection2); + }); + + var model = modelBuilder.FinalizeModel(); + var outerOwnedEntities = model.FindEntityTypes(typeof(OwnedEntityExtraLevel)); + Assert.Equal(2, outerOwnedEntities.Count()); + + foreach (var outerOwnedEntity in outerOwnedEntities) + { + Assert.False(outerOwnedEntity.IsMappedToJson()); +#pragma warning disable CS0618 + Assert.Null(outerOwnedEntity.GetContainerColumnTypeMapping()); +#pragma warning restore CS0618 + var myEnum = outerOwnedEntity.GetDeclaredProperties().Where(p => p.ClrType.IsEnum).Single(); + var typeMapping = myEnum.FindRelationalTypeMapping()!; + + Assert.True(typeMapping.Converter is EnumToNumberConverter); + } + + var ownedEntities = model.FindEntityTypes(typeof(OwnedEntity)); + Assert.Equal(8, ownedEntities.Count()); + + foreach (var ownedEntity in ownedEntities) + { + Assert.False(ownedEntity.IsMappedToJson()); +#pragma warning disable CS0618 + Assert.Null(ownedEntity.GetContainerColumnTypeMapping()); +#pragma warning restore CS0618 + var myEnum = ownedEntity.GetDeclaredProperties().Where(p => p.ClrType.IsEnum).Single(); + var typeMapping = myEnum.FindRelationalTypeMapping()!; + Assert.True(typeMapping.Converter is EnumToNumberConverter); + } + } + + [ConditionalFact] + public virtual void Json_entity_mapped_to_view() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity( + b => + { + b.ToView("MyView"); + b.OwnsOne(x => x.OwnedReference1, bb => bb.ToJson()); + b.Ignore(x => x.OwnedReference2); + b.OwnsMany(x => x.OwnedCollection1, bb => bb.ToJson()); + b.Ignore(x => x.OwnedCollection2); + }); + + var model = modelBuilder.FinalizeModel(); + + var owner = model.FindEntityType(typeof(JsonEntity))!; + Assert.Equal("MyView", owner.GetViewName()); + + var ownedEntities = model.FindEntityTypes(typeof(OwnedEntity)); + Assert.Equal(2, ownedEntities.Count()); + Assert.Equal(2, ownedEntities.Where(e => e.IsMappedToJson()).Count()); + Assert.True(ownedEntities.All(x => x.GetViewName() == "MyView")); + } + + [ConditionalFact] + public virtual void Json_entity_with_custom_property_names() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity( + b => + { + b.OwnsOne( + x => x.OwnedReference1, bb => + { + bb.ToJson(); + bb.Property(x => x.Date).HasJsonPropertyName("OuterDate"); + bb.Property(x => x.Fraction).HasJsonPropertyName("OuterFraction"); + bb.Property(x => x.Enum).HasJsonPropertyName("OuterEnum"); + bb.OwnsOne( + x => x.Reference1, bbb => + { + bbb.HasJsonPropertyName("RenamedReference1"); + bbb.Property(x => x.Date).HasJsonPropertyName("InnerDate"); + bbb.Property(x => x.Fraction).HasJsonPropertyName("InnerFraction"); + bbb.Property(x => x.Enum).HasJsonPropertyName("InnerEnum"); + }); + bb.OwnsOne( + x => x.Reference2, bbb => + { + bbb.HasJsonPropertyName("RenamedReference2"); + bbb.Property(x => x.Date).HasJsonPropertyName("InnerDate"); + bbb.Property(x => x.Fraction).HasJsonPropertyName("InnerFraction"); + bbb.Property(x => x.Enum).HasJsonPropertyName("InnerEnum"); + }); + bb.OwnsMany( + x => x.Collection1, bbb => + { + bbb.HasJsonPropertyName("RenamedCollection1"); + bbb.Property(x => x.Date).HasJsonPropertyName("InnerDate"); + bbb.Property(x => x.Fraction).HasJsonPropertyName("InnerFraction"); + bbb.Property(x => x.Enum).HasJsonPropertyName("InnerEnum"); + }); + bb.OwnsMany( + x => x.Collection2, bbb => + { + bbb.HasJsonPropertyName("RenamedCollection2"); + bbb.Property(x => x.Date).HasJsonPropertyName("InnerDate"); + bbb.Property(x => x.Fraction).HasJsonPropertyName("InnerFraction"); + bbb.Property(x => x.Enum).HasJsonPropertyName("InnerEnum"); + }); + }); + + b.OwnsMany( + x => x.OwnedCollection1, bb => + { + bb.Property(x => x.Date).HasJsonPropertyName("OuterDate"); + bb.Property(x => x.Fraction).HasJsonPropertyName("OuterFraction"); + bb.Property(x => x.Enum).HasJsonPropertyName("OuterEnum"); + bb.OwnsOne( + x => x.Reference1, bbb => + { + bbb.HasJsonPropertyName("RenamedReference1"); + bbb.Property(x => x.Date).HasJsonPropertyName("InnerDate"); + bbb.Property(x => x.Fraction).HasJsonPropertyName("InnerFraction"); + bbb.Property(x => x.Enum).HasJsonPropertyName("InnerEnum"); + }); + bb.OwnsOne( + x => x.Reference2, bbb => + { + bbb.HasJsonPropertyName("RenamedReference2"); + bbb.Property(x => x.Date).HasJsonPropertyName("InnerDate"); + bbb.Property(x => x.Fraction).HasJsonPropertyName("InnerFraction"); + bbb.Property(x => x.Enum).HasJsonPropertyName("InnerEnum"); + }); + bb.OwnsMany( + x => x.Collection1, bbb => + { + bbb.HasJsonPropertyName("RenamedCollection1"); + bbb.Property(x => x.Date).HasJsonPropertyName("InnerDate"); + bbb.Property(x => x.Fraction).HasJsonPropertyName("InnerFraction"); + bbb.Property(x => x.Enum).HasJsonPropertyName("InnerEnum"); + }); + bb.OwnsMany( + x => x.Collection2, bbb => + { + bbb.HasJsonPropertyName("RenamedCollection2"); + bbb.Property(x => x.Date).HasJsonPropertyName("InnerDate"); + bbb.Property(x => x.Fraction).HasJsonPropertyName("InnerFraction"); + bbb.Property(x => x.Enum).HasJsonPropertyName("InnerEnum"); + }); + bb.ToJson(); + }); + + b.Ignore(x => x.OwnedReference2); + b.Ignore(x => x.OwnedCollection2); + }); + + var model = modelBuilder.FinalizeModel(); + var outerOwnedEntities = model.FindEntityTypes(typeof(OwnedEntityExtraLevel)); + Assert.Equal(2, outerOwnedEntities.Count()); + + foreach (var outerOwnedEntity in outerOwnedEntities) + { + Assert.Equal("OuterDate", outerOwnedEntity.GetProperty("Date").GetJsonPropertyName()); + Assert.Equal("OuterFraction", outerOwnedEntity.GetProperty("Fraction").GetJsonPropertyName()); + Assert.Equal("OuterEnum", outerOwnedEntity.GetProperty("Enum").GetJsonPropertyName()); + Assert.Equal( + "RenamedReference1", + outerOwnedEntity.GetNavigations().Single(n => n.Name == "Reference1").TargetEntityType.GetJsonPropertyName()); + Assert.Equal( + "RenamedReference2", + outerOwnedEntity.GetNavigations().Single(n => n.Name == "Reference2").TargetEntityType.GetJsonPropertyName()); + Assert.Equal( + "RenamedCollection1", + outerOwnedEntity.GetNavigations().Single(n => n.Name == "Collection1").TargetEntityType.GetJsonPropertyName()); + Assert.Equal( + "RenamedCollection2", + outerOwnedEntity.GetNavigations().Single(n => n.Name == "Collection2").TargetEntityType.GetJsonPropertyName()); + } + + var ownedEntities = model.FindEntityTypes(typeof(OwnedEntity)); + Assert.Equal(8, ownedEntities.Count()); + + foreach (var ownedEntity in ownedEntities) + { + Assert.Equal("InnerDate", ownedEntity.GetProperty("Date").GetJsonPropertyName()); + Assert.Equal("InnerFraction", ownedEntity.GetProperty("Fraction").GetJsonPropertyName()); + Assert.Equal("InnerEnum", ownedEntity.GetProperty("Enum").GetJsonPropertyName()); + } + } + + [ConditionalFact] + public virtual void Json_entity_and_normal_owned_can_exist_side_to_side_on_same_entity() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + b => + { + b.OwnsOne(x => x.OwnedReference1); + b.OwnsOne(x => x.OwnedReference2, bb => bb.ToJson("reference")); + b.OwnsMany(x => x.OwnedCollection1); + b.OwnsMany(x => x.OwnedCollection2, bb => bb.ToJson("collection")); + }); + + var model = modelBuilder.FinalizeModel(); + + var ownedEntities = model.FindEntityTypes(typeof(OwnedEntity)); + Assert.Equal(4, ownedEntities.Count()); + Assert.Equal(2, ownedEntities.Where(e => e.IsMappedToJson()).Count()); + Assert.Equal(2, ownedEntities.Where(e => e.IsOwned() && !e.IsMappedToJson()).Count()); + } + + [ConditionalFact] + public virtual void Json_entity_with_nested_structure_same_property_names_() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity( + b => + { + b.OwnsOne( + x => x.OwnedReference1, bb => + { + bb.ToJson("ref1"); + bb.OwnsOne(x => x.Reference1); + bb.OwnsOne(x => x.Reference2); + bb.OwnsMany(x => x.Collection1); + bb.OwnsMany(x => x.Collection2); + }); + + b.OwnsOne( + x => x.OwnedReference2, bb => + { + bb.ToJson("ref2"); + bb.OwnsOne(x => x.Reference1); + bb.OwnsOne(x => x.Reference2); + bb.OwnsMany(x => x.Collection1); + bb.OwnsMany(x => x.Collection2); + }); + + b.OwnsMany( + x => x.OwnedCollection1, bb => + { + bb.ToJson("col1"); + bb.OwnsOne(x => x.Reference1); + bb.OwnsOne(x => x.Reference2); + bb.OwnsMany(x => x.Collection1); + bb.OwnsMany(x => x.Collection2); + }); + + b.OwnsMany( + x => x.OwnedCollection2, bb => + { + bb.ToJson("col2"); + bb.OwnsOne(x => x.Reference1); + bb.OwnsOne(x => x.Reference2); + bb.OwnsMany(x => x.Collection1); + bb.OwnsMany(x => x.Collection2); + }); + }); + + var model = modelBuilder.FinalizeModel(); + var outerOwnedEntities = model.FindEntityTypes(typeof(OwnedEntityExtraLevel)); + Assert.Equal(4, outerOwnedEntities.Count()); + + var ownedEntities = model.FindEntityTypes(typeof(OwnedEntity)); + Assert.Equal(16, ownedEntities.Count()); + } + } + + public class JetModelBuilderFixture : RelationalModelBuilderFixture + { + public override TestHelpers TestHelpers => JetTestHelpers.Instance; + } +} diff --git a/test/EFCore.Jet.FunctionalTests/ModelBuilding/JetTestModelBuilderExtensions.cs b/test/EFCore.Jet.FunctionalTests/ModelBuilding/JetTestModelBuilderExtensions.cs new file mode 100644 index 0000000..1145292 --- /dev/null +++ b/test/EFCore.Jet.FunctionalTests/ModelBuilding/JetTestModelBuilderExtensions.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace EntityFrameworkCore.Jet.FunctionalTests.ModelBuilding; + +public static class JetTestModelBuilderExtensions +{ + +} diff --git a/test/EFCore.Jet.FunctionalTests/Query/OptionalDependentQueryJetFixture.cs b/test/EFCore.Jet.FunctionalTests/Query/OptionalDependentQueryJetFixture.cs new file mode 100644 index 0000000..b1a934e --- /dev/null +++ b/test/EFCore.Jet.FunctionalTests/Query/OptionalDependentQueryJetFixture.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using EntityFrameworkCore.Jet.FunctionalTests.TestUtilities; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.TestUtilities; + +namespace EntityFrameworkCore.Jet.FunctionalTests.Query; + +#nullable disable + +public class OptionalDependentQueryJetFixture : OptionalDependentQueryFixtureBase +{ + protected override ITestStoreFactory TestStoreFactory + => JetTestStoreFactory.Instance; +} diff --git a/test/EFCore.Jet.FunctionalTests/Query/OptionalDependentQueryJetTest.cs b/test/EFCore.Jet.FunctionalTests/Query/OptionalDependentQueryJetTest.cs new file mode 100644 index 0000000..5446779 --- /dev/null +++ b/test/EFCore.Jet.FunctionalTests/Query/OptionalDependentQueryJetTest.cs @@ -0,0 +1,140 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Query; +using Xunit.Abstractions; + +namespace EntityFrameworkCore.Jet.FunctionalTests.Query; + +#nullable disable + +public class OptionalDependentQueryJetTest : OptionalDependentQueryTestBase +{ + public OptionalDependentQueryJetTest(OptionalDependentQueryJetFixture fixture, ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + public override async Task Basic_projection_entity_with_all_optional(bool async) + { + await base.Basic_projection_entity_with_all_optional(async); + + AssertSql( + """ +SELECT `e`.`Id`, `e`.`Name`, `e`.`Json` +FROM `EntitiesAllOptional` AS `e` +"""); + } + + public override async Task Basic_projection_entity_with_some_required(bool async) + { + await base.Basic_projection_entity_with_some_required(async); + + AssertSql( + """ +SELECT `e`.`Id`, `e`.`Name`, `e`.`Json` +FROM `EntitiesSomeRequired` AS `e` +"""); + } + + public override async Task Filter_optional_dependent_with_all_optional_compared_to_null(bool async) + { + await base.Filter_optional_dependent_with_all_optional_compared_to_null(async); + + AssertSql( + """ +SELECT `e`.`Id`, `e`.`Name`, `e`.`Json` +FROM `EntitiesAllOptional` AS `e` +WHERE (`e`.`Json`) IS NULL +"""); + } + + public override async Task Filter_optional_dependent_with_all_optional_compared_to_not_null(bool async) + { + await base.Filter_optional_dependent_with_all_optional_compared_to_not_null(async); + + AssertSql( + """ +SELECT `e`.`Id`, `e`.`Name`, `e`.`Json` +FROM `EntitiesAllOptional` AS `e` +WHERE (`e`.`Json`) IS NOT NULL +"""); + } + + public override async Task Filter_optional_dependent_with_some_required_compared_to_null(bool async) + { + await base.Filter_optional_dependent_with_some_required_compared_to_null(async); + + AssertSql( + """ +SELECT `e`.`Id`, `e`.`Name`, `e`.`Json` +FROM `EntitiesSomeRequired` AS `e` +WHERE (`e`.`Json`) IS NULL +"""); + } + + public override async Task Filter_optional_dependent_with_some_required_compared_to_not_null(bool async) + { + await base.Filter_optional_dependent_with_some_required_compared_to_not_null(async); + + AssertSql( + """ +SELECT `e`.`Id`, `e`.`Name`, `e`.`Json` +FROM `EntitiesSomeRequired` AS `e` +WHERE (`e`.`Json`) IS NOT NULL +"""); + } + + public override async Task Filter_nested_optional_dependent_with_all_optional_compared_to_null(bool async) + { + await base.Filter_nested_optional_dependent_with_all_optional_compared_to_null(async); + + AssertSql( + """ +SELECT [e].[Id], [e].[Name], [e].[Json] +FROM [EntitiesAllOptional] AS [e] +WHERE JSON_QUERY([e].[Json], '$.OpNav1') IS NULL +"""); + } + + public override async Task Filter_nested_optional_dependent_with_all_optional_compared_to_not_null(bool async) + { + await base.Filter_nested_optional_dependent_with_all_optional_compared_to_not_null(async); + + AssertSql( + """ +SELECT [e].[Id], [e].[Name], [e].[Json] +FROM [EntitiesAllOptional] AS [e] +WHERE JSON_QUERY([e].[Json], '$.OpNav2') IS NOT NULL +"""); + } + + public override async Task Filter_nested_optional_dependent_with_some_required_compared_to_null(bool async) + { + await base.Filter_nested_optional_dependent_with_some_required_compared_to_null(async); + + AssertSql( + """ +SELECT [e].[Id], [e].[Name], [e].[Json] +FROM [EntitiesSomeRequired] AS [e] +WHERE JSON_QUERY([e].[Json], '$.ReqNav1') IS NULL +"""); + } + + public override async Task Filter_nested_optional_dependent_with_some_required_compared_to_not_null(bool async) + { + await base.Filter_nested_optional_dependent_with_some_required_compared_to_not_null(async); + + AssertSql( + """ +SELECT [e].[Id], [e].[Name], [e].[Json] +FROM [EntitiesSomeRequired] AS [e] +WHERE JSON_QUERY([e].[Json], '$.ReqNav2') IS NOT NULL +"""); + } + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); +} diff --git a/test/EFCore.Jet.FunctionalTests/Query/OwnedEntityQuerySqlServerTest.cs b/test/EFCore.Jet.FunctionalTests/Query/OwnedEntityQueryJetTest.cs similarity index 100% rename from test/EFCore.Jet.FunctionalTests/Query/OwnedEntityQuerySqlServerTest.cs rename to test/EFCore.Jet.FunctionalTests/Query/OwnedEntityQueryJetTest.cs diff --git a/test/EFCore.Jet.FunctionalTests/Query/PrecompiledSqlPregenerationQueryJetTest.cs b/test/EFCore.Jet.FunctionalTests/Query/PrecompiledSqlPregenerationQueryJetTest.cs new file mode 100644 index 0000000..2a6e81f --- /dev/null +++ b/test/EFCore.Jet.FunctionalTests/Query/PrecompiledSqlPregenerationQueryJetTest.cs @@ -0,0 +1,276 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading.Tasks; +using EntityFrameworkCore.Jet.FunctionalTests.TestUtilities; +using EntityFrameworkCore.Jet.Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Query.Internal; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; +using Xunit.Abstractions; + +namespace EntityFrameworkCore.Jet.FunctionalTests.Query; + +// ReSharper disable InconsistentNaming + +public class PrecompiledSqlPregenerationQueryJetTest( + PrecompiledSqlPregenerationQueryJetTest.PrecompiledSqlPregenerationQueryJetFixture fixture, + ITestOutputHelper testOutputHelper) + : PrecompiledSqlPregenerationQueryRelationalTestBase(fixture, testOutputHelper), + IClassFixture +{ + protected override bool AlwaysPrintGeneratedSources + => false; + + public override async Task No_parameters() + { + await base.No_parameters(); + + AssertSql( + """ +SELECT [b].[Id], [b].[Name] +FROM [Blogs] AS [b] +WHERE [b].[Name] = N'foo' +"""); + } + + public override async Task Non_nullable_value_type() + { + await base.Non_nullable_value_type(); + + AssertSql( + """ +@__id_0='8' + +SELECT [b].[Id], [b].[Name] +FROM [Blogs] AS [b] +WHERE [b].[Id] = @__id_0 +"""); + } + + public override async Task Nullable_value_type() + { + await base.Nullable_value_type(); + + AssertSql( + """ +@__id_0='8' (Nullable = true) + +SELECT [b].[Id], [b].[Name] +FROM [Blogs] AS [b] +WHERE [b].[Id] = @__id_0 +"""); + } + + public override async Task Nullable_reference_type() + { + await base.Nullable_reference_type(); + + AssertSql( + """ +@__name_0='bar' (Size = 4000) + +SELECT [b].[Id], [b].[Name] +FROM [Blogs] AS [b] +WHERE [b].[Name] = @__name_0 +"""); + } + + public override async Task Non_nullable_reference_type() + { + await base.Non_nullable_reference_type(); + + AssertSql( + """ +@__name_0='bar' (Nullable = false) (Size = 4000) + +SELECT [b].[Id], [b].[Name] +FROM [Blogs] AS [b] +WHERE [b].[Name] = @__name_0 +"""); + } + + public override async Task Nullable_and_non_nullable_value_types() + { + await base.Nullable_and_non_nullable_value_types(); + + AssertSql( + """ +@__id1_0='8' (Nullable = true) +@__id2_1='9' + +SELECT [b].[Id], [b].[Name] +FROM [Blogs] AS [b] +WHERE [b].[Id] = @__id1_0 OR [b].[Id] = @__id2_1 +"""); + } + + public override async Task Two_nullable_reference_types() + { + await base.Two_nullable_reference_types(); + + AssertSql( + """ +@__name1_0='foo' (Size = 4000) +@__name2_1='bar' (Size = 4000) + +SELECT [b].[Id], [b].[Name] +FROM [Blogs] AS [b] +WHERE [b].[Name] = @__name1_0 OR [b].[Name] = @__name2_1 +"""); + } + + public override async Task Two_non_nullable_reference_types() + { + await base.Two_non_nullable_reference_types(); + + AssertSql( + """ +@__name1_0='foo' (Nullable = false) (Size = 4000) +@__name2_1='bar' (Nullable = false) (Size = 4000) + +SELECT [b].[Id], [b].[Name] +FROM [Blogs] AS [b] +WHERE [b].[Name] = @__name1_0 OR [b].[Name] = @__name2_1 +"""); + } + + public override async Task Nullable_and_non_nullable_reference_types() + { + await base.Nullable_and_non_nullable_reference_types(); + + AssertSql( + """ +@__name1_0='foo' (Size = 4000) +@__name2_1='bar' (Nullable = false) (Size = 4000) + +SELECT [b].[Id], [b].[Name] +FROM [Blogs] AS [b] +WHERE [b].[Name] = @__name1_0 OR [b].[Name] = @__name2_1 +"""); + } + + public override async Task Too_many_nullable_parameters_prevent_pregeneration() + { + await base.Too_many_nullable_parameters_prevent_pregeneration(); + + AssertSql( + """ +@__name1_0='foo' (Size = 4000) +@__name2_1='bar' (Size = 4000) +@__name3_2='baz' (Size = 4000) +@__name4_3='baq' (Size = 4000) + +SELECT [b].[Id], [b].[Name] +FROM [Blogs] AS [b] +WHERE [b].[Name] = @__name1_0 OR [b].[Name] = @__name2_1 OR [b].[Name] = @__name3_2 OR [b].[Name] = @__name4_3 +"""); + } + + public override async Task Many_non_nullable_parameters_do_not_prevent_pregeneration() + { + await base.Many_non_nullable_parameters_do_not_prevent_pregeneration(); + + AssertSql( + """ +@__name1_0='foo' (Nullable = false) (Size = 4000) +@__name2_1='bar' (Nullable = false) (Size = 4000) +@__name3_2='baz' (Nullable = false) (Size = 4000) +@__name4_3='baq' (Nullable = false) (Size = 4000) + +SELECT [b].[Id], [b].[Name] +FROM [Blogs] AS [b] +WHERE [b].[Name] = @__name1_0 OR [b].[Name] = @__name2_1 OR [b].[Name] = @__name3_2 OR [b].[Name] = @__name4_3 +"""); + } + + #region Tests for the different querying enumerables + + public override async Task Include_single_query() + { + await base.Include_single_query(); + + AssertSql( + """ +SELECT [b].[Id], [b].[Name], [p].[Id], [p].[BlogId], [p].[Title] +FROM [Blogs] AS [b] +LEFT JOIN [Post] AS [p] ON [b].[Id] = [p].[BlogId] +ORDER BY [b].[Id] +"""); + } + + public override async Task Include_split_query() + { + await base.Include_split_query(); + + AssertSql( + """ +SELECT [b].[Id], [b].[Name] +FROM [Blogs] AS [b] +ORDER BY [b].[Id] +""", + // + """ +SELECT [p].[Id], [p].[BlogId], [p].[Title], [b].[Id] +FROM [Blogs] AS [b] +INNER JOIN [Post] AS [p] ON [b].[Id] = [p].[BlogId] +ORDER BY [b].[Id] +"""); + } + + public override async Task Final_GroupBy() + { + await base.Final_GroupBy(); + + AssertSql( + """ +SELECT [b].[Name], [b].[Id] +FROM [Blogs] AS [b] +ORDER BY [b].[Name] +"""); + } + + #endregion Tests for the different querying enumerables + + [ConditionalFact] + public virtual async Task Do_not_cache_is_respected() + { + // The "do not cache" flag in the 2nd part of the query pipeline is turned on in provider-specific situations, so we test it + // here in SQL Server; note that SQL Server compatibility mode is set low to trigger this. + await Test( + """ +string[] names = ["foo", "bar"]; +var blogs = await context.Blogs.Where(b => names.Contains(b.Name)).ToListAsync(); +""", + interceptorCodeAsserter: code => Assert.Contains(nameof(RelationalCommandCache), code)); + + AssertSql( + """ +SELECT [b].[Id], [b].[Name] +FROM [Blogs] AS [b] +WHERE [b].[Name] IN (N'foo', N'bar') +"""); + } + + public class PrecompiledSqlPregenerationQueryJetFixture : PrecompiledSqlPregenerationQueryRelationalFixture + { + protected override ITestStoreFactory TestStoreFactory + => JetTestStoreFactory.Instance; + + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + { + builder = base.AddOptions(builder); + + // TODO: Figure out if there's a nice way to continue using the retrying strategy + var jetOptionsBuilder = new JetDbContextOptionsBuilder(builder); + jetOptionsBuilder + .ExecutionStrategy(d => new NonRetryingExecutionStrategy(d)); + return builder; + } + + public override PrecompiledQueryTestHelpers PrecompiledQueryTestHelpers => JetPrecompiledQueryTestHelpers.Instance; + } +}