diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs index 75386a35107..1a0e2e2b6a9 100644 --- a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs +++ b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs @@ -64,19 +64,7 @@ public virtual bool EnsureCreated() InsertData(); } - var coreOptionsExtension = - _contextOptions.FindExtension() - ?? new CoreOptionsExtension(); - - var seed = coreOptionsExtension.Seeder; - if (seed != null) - { - seed(_currentContext.Context, created); - } - else if (coreOptionsExtension.AsyncSeeder != null) - { - throw new InvalidOperationException(CoreStrings.MissingSeeder); - } + SeedData(created); return created; } @@ -104,19 +92,7 @@ public virtual async Task EnsureCreatedAsync(CancellationToken cancellatio await InsertDataAsync(cancellationToken).ConfigureAwait(false); } - var coreOptionsExtension = - _contextOptions.FindExtension() - ?? new CoreOptionsExtension(); - - var seedAsync = coreOptionsExtension.AsyncSeeder; - if (seedAsync != null) - { - await seedAsync(_currentContext.Context, created, cancellationToken).ConfigureAwait(false); - } - else if (coreOptionsExtension.Seeder != null) - { - throw new InvalidOperationException(CoreStrings.MissingSeeder); - } + await SeedDataAsync(created, cancellationToken).ConfigureAwait(false); return created; } @@ -223,6 +199,52 @@ private IUpdateAdapter AddModelData() return updateAdapter; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void SeedData(bool created) + { + var coreOptionsExtension = + _contextOptions.FindExtension() + ?? new CoreOptionsExtension(); + + var seed = coreOptionsExtension.Seeder; + if (seed != null) + { + seed(_currentContext.Context, created); + } + else if (coreOptionsExtension.AsyncSeeder != null) + { + throw new InvalidOperationException(CoreStrings.MissingSeeder); + } + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual async Task SeedDataAsync(bool created, CancellationToken cancellationToken = default) + { + var coreOptionsExtension = + _contextOptions.FindExtension() + ?? new CoreOptionsExtension(); + + var seedAsync = coreOptionsExtension.AsyncSeeder; + if (seedAsync != null) + { + await seedAsync(_currentContext.Context, created, cancellationToken).ConfigureAwait(false); + } + else if (coreOptionsExtension.Seeder != null) + { + throw new InvalidOperationException(CoreStrings.MissingSeeder); + } + } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.SqlServer/Extensions/SqlServerEntityTypeBuilderExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerEntityTypeBuilderExtensions.cs index c5ce3eac1fc..0ed61f44e41 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerEntityTypeBuilderExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerEntityTypeBuilderExtensions.cs @@ -140,6 +140,111 @@ public static bool CanSetIsMemoryOptimized( bool fromDataAnnotation = false) => entityTypeBuilder.CanSetAnnotation(SqlServerAnnotationNames.MemoryOptimized, memoryOptimized, fromDataAnnotation); + /// + /// Sets a value indicating whether to use the SQL OUTPUT clause when saving changes to the table. + /// The OUTPUT clause is incompatible with certain SQL Server features, such as tables with triggers. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The value to set. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionEntityTypeBuilder? UseSqlOutputClause( + this IConventionEntityTypeBuilder entityTypeBuilder, + bool? useSqlOutputClause, + bool fromDataAnnotation = false) + { + if (!entityTypeBuilder.CanUseSqlOutputClause(useSqlOutputClause, fromDataAnnotation)) + { + return null; + } + + entityTypeBuilder.Metadata.UseSqlOutputClause(useSqlOutputClause, fromDataAnnotation); + return entityTypeBuilder; + } + + /// + /// Sets a value indicating whether to use the SQL OUTPUT clause when saving changes to the table. + /// The OUTPUT clause is incompatible with certain SQL Server features, such as tables with triggers. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The value to set. + /// The identifier of the table-like store object. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionEntityTypeBuilder? UseSqlOutputClause( + this IConventionEntityTypeBuilder entityTypeBuilder, + bool? useSqlOutputClause, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + { + if (!entityTypeBuilder.CanUseSqlOutputClause(useSqlOutputClause, storeObject, fromDataAnnotation)) + { + return null; + } + + entityTypeBuilder.Metadata.UseSqlOutputClause(useSqlOutputClause, storeObject, fromDataAnnotation); + return entityTypeBuilder; + } + + /// + /// Returns a value indicating whether this entity type can be configured to use the SQL OUTPUT clause + /// using the specified configuration source. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The value to set. + /// Indicates whether the configuration was specified using a data annotation. + /// if the configuration can be applied. + public static bool CanUseSqlOutputClause( + this IConventionEntityTypeBuilder entityTypeBuilder, + bool? useSqlOutputClause, + bool fromDataAnnotation = false) + => entityTypeBuilder.CanSetAnnotation( + SqlServerAnnotationNames.UseSqlOutputClause, + useSqlOutputClause, + fromDataAnnotation); + + /// + /// Returns a value indicating whether this entity type can be configured to use the SQL OUTPUT clause + /// using the specified configuration source. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The value to set. + /// The identifier of the table-like store object. + /// Indicates whether the configuration was specified using a data annotation. + /// if the configuration can be applied. + public static bool CanUseSqlOutputClause( + this IConventionEntityTypeBuilder entityTypeBuilder, + bool? useSqlOutputClause, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + => StoreObjectIdentifier.Create(entityTypeBuilder.Metadata, storeObject.StoreObjectType) == storeObject + ? entityTypeBuilder.CanSetAnnotation( + SqlServerAnnotationNames.UseSqlOutputClause, + useSqlOutputClause, + fromDataAnnotation) + : entityTypeBuilder.Metadata.GetOrCreateMappingFragment(storeObject, fromDataAnnotation).Builder.CanSetAnnotation( + SqlServerAnnotationNames.UseSqlOutputClause, + useSqlOutputClause, + fromDataAnnotation); + /// /// Configures the table as temporal. /// diff --git a/src/EFCore.SqlServer/Extensions/SqlServerEntityTypeExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerEntityTypeExtensions.cs index 1f69486cb80..f138c09ba8f 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerEntityTypeExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerEntityTypeExtensions.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; // ReSharper disable once CheckNamespace @@ -344,6 +345,18 @@ public static void UseSqlOutputClause(this IMutableEntityType entityType, bool? public static ConfigurationSource? GetUseSqlOutputClauseConfigurationSource(this IConventionEntityType entityType) => entityType.FindAnnotation(SqlServerAnnotationNames.UseSqlOutputClause)?.GetConfigurationSource(); + /// + /// Gets the configuration source for whether to use the SQL OUTPUT clause when saving changes to the table. + /// + /// The entity type. + /// The identifier of the table-like store object. + /// The configuration source for the memory-optimized setting. + public static ConfigurationSource? GetUseSqlOutputClauseConfigurationSource( + this IConventionEntityType entityType, in StoreObjectIdentifier storeObject) + => StoreObjectIdentifier.Create(entityType, storeObject.StoreObjectType) == storeObject + ? entityType.GetUseSqlOutputClauseConfigurationSource() + : (entityType.FindMappingFragment(storeObject)?.GetUseSqlOutputClauseConfigurationSource()); + /// /// Returns a value indicating whether to use the SQL OUTPUT clause when saving changes to the specified table. /// The OUTPUT clause is incompatible with certain SQL Server features, such as tables with triggers. diff --git a/src/EFCore.SqlServer/Extensions/SqlServerEntityTypeMappingFragmentExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerEntityTypeMappingFragmentExtensions.cs index 828d0123a3d..3ae7148c699 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerEntityTypeMappingFragmentExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerEntityTypeMappingFragmentExtensions.cs @@ -27,7 +27,7 @@ public static bool IsSqlOutputClauseUsed(this IReadOnlyEntityTypeMappingFragment /// The entity type mapping fragment. /// The value to set. public static void UseSqlOutputClause(this IMutableEntityTypeMappingFragment fragment, bool? useSqlOutputClause) - => fragment.SetAnnotation(SqlServerAnnotationNames.UseSqlOutputClause, useSqlOutputClause); + => fragment.SetOrRemoveAnnotation(SqlServerAnnotationNames.UseSqlOutputClause, useSqlOutputClause); /// /// Sets whether to use the SQL OUTPUT clause when saving changes to the associated table. @@ -41,7 +41,7 @@ public static void UseSqlOutputClause(this IMutableEntityTypeMappingFragment fra this IConventionEntityTypeMappingFragment fragment, bool? useSqlOutputClause, bool fromDataAnnotation = false) - => (bool?)fragment.SetAnnotation(SqlServerAnnotationNames.UseSqlOutputClause, useSqlOutputClause, fromDataAnnotation)?.Value; + => (bool?)fragment.SetOrRemoveAnnotation(SqlServerAnnotationNames.UseSqlOutputClause, useSqlOutputClause, fromDataAnnotation)?.Value; /// /// Gets the configuration source for the setting whether to use the SQL OUTPUT clause when saving changes to the associated table. diff --git a/src/EFCore.Sqlite.Core/Extensions/SqliteEntityTypeBuilderExtensions.cs b/src/EFCore.Sqlite.Core/Extensions/SqliteEntityTypeBuilderExtensions.cs new file mode 100644 index 00000000000..f227d5d9546 --- /dev/null +++ b/src/EFCore.Sqlite.Core/Extensions/SqliteEntityTypeBuilderExtensions.cs @@ -0,0 +1,123 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Sqlite.Metadata.Internal; + +// ReSharper disable once CheckNamespace +namespace Microsoft.EntityFrameworkCore; + +/// +/// Entity type builder extension methods for Sqlite-specific metadata. +/// +/// +/// See Modeling entity types and relationships, and +/// Accessing Sqlite databases with EF Core for more information and examples. +/// +public static class SqliteEntityTypeBuilderExtensions +{ + /// + /// Sets a value indicating whether to use the SQL RETURNING clause when saving changes to the table. + /// The RETURNING clause is incompatible with certain Sqlite features, such as virtual tables or tables with AFTER triggers. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The value to set. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionEntityTypeBuilder? UseSqlReturningClause( + this IConventionEntityTypeBuilder entityTypeBuilder, + bool? useSqlReturningClause, + bool fromDataAnnotation = false) + { + if (!entityTypeBuilder.CanUseSqlReturningClause(useSqlReturningClause, fromDataAnnotation)) + { + return null; + } + + entityTypeBuilder.Metadata.UseSqlReturningClause(useSqlReturningClause, fromDataAnnotation); + return entityTypeBuilder; + } + + /// + /// Sets a value indicating whether to use the SQL RETURNING clause when saving changes to the table. + /// The RETURNING clause is incompatible with certain Sqlite features, such as virtual tables or tables with AFTER triggers. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The value to set. + /// The identifier of the table-like store object. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionEntityTypeBuilder? UseSqlReturningClause( + this IConventionEntityTypeBuilder entityTypeBuilder, + bool? useSqlReturningClause, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + { + if (!entityTypeBuilder.CanUseSqlReturningClause(useSqlReturningClause, storeObject, fromDataAnnotation)) + { + return null; + } + + entityTypeBuilder.Metadata.UseSqlReturningClause(useSqlReturningClause, storeObject, fromDataAnnotation); + return entityTypeBuilder; + } + + /// + /// Returns a value indicating whether this entity type can be configured to use the SQL RETURNING clause + /// using the specified configuration source. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The value to set. + /// Indicates whether the configuration was specified using a data annotation. + /// if the configuration can be applied. + public static bool CanUseSqlReturningClause( + this IConventionEntityTypeBuilder entityTypeBuilder, + bool? useSqlReturningClause, + bool fromDataAnnotation = false) + => entityTypeBuilder.CanSetAnnotation( + SqliteAnnotationNames.UseSqlReturningClause, + useSqlReturningClause, + fromDataAnnotation); + + /// + /// Returns a value indicating whether this entity type can be configured to use the SQL RETURNING clause + /// using the specified configuration source. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The value to set. + /// The identifier of the table-like store object. + /// Indicates whether the configuration was specified using a data annotation. + /// if the configuration can be applied. + public static bool CanUseSqlReturningClause( + this IConventionEntityTypeBuilder entityTypeBuilder, + bool? useSqlReturningClause, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + => StoreObjectIdentifier.Create(entityTypeBuilder.Metadata, storeObject.StoreObjectType) == storeObject + ? entityTypeBuilder.CanSetAnnotation( + SqliteAnnotationNames.UseSqlReturningClause, + useSqlReturningClause, + fromDataAnnotation) + : entityTypeBuilder.Metadata.GetOrCreateMappingFragment(storeObject, fromDataAnnotation).Builder.CanSetAnnotation( + SqliteAnnotationNames.UseSqlReturningClause, + useSqlReturningClause, + fromDataAnnotation); +} diff --git a/src/EFCore.Sqlite.Core/Extensions/SqliteEntityTypeExtensions.cs b/src/EFCore.Sqlite.Core/Extensions/SqliteEntityTypeExtensions.cs index 02d732c030f..e651d823060 100644 --- a/src/EFCore.Sqlite.Core/Extensions/SqliteEntityTypeExtensions.cs +++ b/src/EFCore.Sqlite.Core/Extensions/SqliteEntityTypeExtensions.cs @@ -77,6 +77,18 @@ public static void UseSqlReturningClause(this IMutableEntityType entityType, boo public static ConfigurationSource? GetUseSqlReturningClauseConfigurationSource(this IConventionEntityType entityType) => entityType.FindAnnotation(SqliteAnnotationNames.UseSqlReturningClause)?.GetConfigurationSource(); + /// + /// Gets the configuration source for whether to use the SQL RETURNING clause when saving changes to the table. + /// + /// The entity type. + /// The identifier of the table-like store object. + /// The configuration source for the memory-optimized setting. + public static ConfigurationSource? GetUseSqlReturningClauseConfigurationSource( + this IConventionEntityType entityType, in StoreObjectIdentifier storeObject) + => StoreObjectIdentifier.Create(entityType, storeObject.StoreObjectType) == storeObject + ? entityType.GetUseSqlReturningClauseConfigurationSource() + : (entityType.FindMappingFragment(storeObject)?.GetUseSqlReturningClauseConfigurationSource()); + /// /// Returns a value indicating whether to use the SQL RETURNING clause when saving changes to the table. /// The RETURNING clause is incompatible with certain Sqlite features, such as virtual tables or tables with AFTER triggers. diff --git a/src/EFCore.Sqlite.Core/Extensions/SqliteEntityTypeMappingFragmentExtensions.cs b/src/EFCore.Sqlite.Core/Extensions/SqliteEntityTypeMappingFragmentExtensions.cs index 2d2ee626bd2..831cff6dffc 100644 --- a/src/EFCore.Sqlite.Core/Extensions/SqliteEntityTypeMappingFragmentExtensions.cs +++ b/src/EFCore.Sqlite.Core/Extensions/SqliteEntityTypeMappingFragmentExtensions.cs @@ -27,7 +27,7 @@ public static bool IsSqlReturningClauseUsed(this IReadOnlyEntityTypeMappingFragm /// The entity type mapping fragment. /// The value to set. public static void UseSqlReturningClause(this IMutableEntityTypeMappingFragment fragment, bool? useSqlReturningClause) - => fragment.SetAnnotation(SqliteAnnotationNames.UseSqlReturningClause, useSqlReturningClause); + => fragment.SetOrRemoveAnnotation(SqliteAnnotationNames.UseSqlReturningClause, useSqlReturningClause); /// /// Sets a value indicating whether to use the SQL RETURNING clause when saving changes to the table. @@ -41,7 +41,7 @@ public static void UseSqlReturningClause(this IMutableEntityTypeMappingFragment this IConventionEntityTypeMappingFragment fragment, bool? useSqlReturningClause, bool fromDataAnnotation = false) - => (bool?)fragment.SetAnnotation(SqliteAnnotationNames.UseSqlReturningClause, useSqlReturningClause, fromDataAnnotation)?.Value; + => (bool?)fragment.SetOrRemoveAnnotation(SqliteAnnotationNames.UseSqlReturningClause, useSqlReturningClause, fromDataAnnotation)?.Value; /// /// Gets the configuration source for whether to use the SQL RETURNING clause when saving changes to the associated table. diff --git a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs index 32ab3c987f0..330dceb2031 100644 --- a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs +++ b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs @@ -334,6 +334,23 @@ public virtual IndexBuilder HasIndex(params string[] propertyNames) DependentEntityType.Builder.HasIndex( Check.NotEmpty(propertyNames, nameof(propertyNames)), ConfigurationSource.Explicit)!.Metadata); + /// + /// Configures an index on the specified properties and with the given name. + /// If there is an existing index on the given list of properties and with + /// the given name, then the existing index will be returned for configuration. + /// + /// The names of the properties that make up the index. + /// The name to assign to the index. + /// An object that can be used to configure the index. + public virtual IndexBuilder HasIndex( + string[] propertyNames, + string name) + => new( + DependentEntityType.Builder.HasIndex( + Check.NotEmpty(propertyNames, nameof(propertyNames)), + Check.NotEmpty(name, nameof(name)), + ConfigurationSource.Explicit)!.Metadata); + /// /// Configures the relationship to the owner. /// diff --git a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs index 2801970f893..7c316152070 100644 --- a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs +++ b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs @@ -200,6 +200,32 @@ public virtual IndexBuilder HasIndex(Expression + /// Configures an index on the specified properties with the given name. + /// If there is an existing index on the given list of properties and with + /// the given name, then the existing index will be returned for configuration. + /// + /// + /// + /// A lambda expression representing the property(s) to be included in the index + /// (blog => blog.Url). + /// + /// + /// If the index is made up of multiple properties then specify an anonymous type including the + /// properties (post => new { post.Title, post.BlogId }). + /// + /// + /// The name to assign to the index. + /// An object that can be used to configure the index. + public virtual IndexBuilder HasIndex( + Expression> indexExpression, + string name) + => new( + DependentEntityType.Builder.HasIndex( + Check.NotNull(indexExpression, nameof(indexExpression)).GetMemberAccessList(), + name, + ConfigurationSource.Explicit)!.Metadata); + /// /// Configures an index on the specified properties. If there is an existing index on the given /// set of properties, then the existing index will be returned for configuration. @@ -211,6 +237,23 @@ public virtual IndexBuilder HasIndex(Expression + /// Configures an index on the specified properties with the given name. + /// If there is an existing index on the given list of properties and with + /// the given name, then the existing index will be returned for configuration. + /// + /// The names of the properties that make up the index. + /// The name to assign to the index. + /// An object that can be used to configure the index. + public new virtual IndexBuilder HasIndex( + string[] propertyNames, + string name) + => new( + DependentEntityType.Builder.HasIndex( + Check.NotEmpty(propertyNames, nameof(propertyNames)), + name, + ConfigurationSource.Explicit)!.Metadata); + /// /// Configures the relationship to the owner. /// diff --git a/test/EFCore.Cosmos.FunctionalTests/ModelBuilding/CosmosModelBuilderGenericTest.cs b/test/EFCore.Cosmos.FunctionalTests/ModelBuilding/CosmosModelBuilderGenericTest.cs index 204e96295a2..94011d3df9e 100644 --- a/test/EFCore.Cosmos.FunctionalTests/ModelBuilding/CosmosModelBuilderGenericTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/ModelBuilding/CosmosModelBuilderGenericTest.cs @@ -978,7 +978,7 @@ public override void Can_configure_owned_type() public override void Can_configure_owned_type_collection() => Assert.Equal( - CosmosStrings.IndexesExist(nameof(Order), "foo"), + CosmosStrings.IndexesExist(nameof(Order), "CustomerId"), Assert.Throws( base.Can_configure_owned_type_collection).Message); diff --git a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs index 521ba638f25..dc940a3ef9b 100644 --- a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs +++ b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs @@ -503,6 +503,7 @@ private static async Task SeedAsync(DbContext context) { var creator = (CosmosDatabaseCreator)context.GetService(); await creator.InsertDataAsync().ConfigureAwait(false); + await creator.SeedDataAsync(created: true).ConfigureAwait(false); } public override void Dispose() diff --git a/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs b/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs index 28576155f1c..c9e63d3b76e 100644 --- a/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs +++ b/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs @@ -579,9 +579,7 @@ private string ValidateConventionBuilderMethods(IReadOnlyList method var expectedName = methodName.StartsWith("HasNo", StringComparison.Ordinal) ? "CanRemove" + methodName[5..] - : methodName.StartsWith("Ignore", StringComparison.Ordinal) - ? methodName - : "CanSet" + : "CanSet" + (methodName.StartsWith("Has", StringComparison.Ordinal) || methodName.StartsWith("Use", StringComparison.Ordinal) ? methodName[3..] @@ -593,18 +591,33 @@ private string ValidateConventionBuilderMethods(IReadOnlyList method if (!methodLookup.TryGetValue(expectedName, out var canSetMethod)) { + if (methodName.StartsWith("HasNo", StringComparison.Ordinal) + || methodName.StartsWith("To", StringComparison.Ordinal) + || methodName.StartsWith("With", StringComparison.Ordinal)) + { + return $"{declaringType.Name} expected to have a {expectedName} method"; + } + + var otherExpectedName = "Can" + methodName; if (methodName.StartsWith("Has", StringComparison.Ordinal)) { - var otherExpectedName = "CanHave" + methodName[3..]; - if (!methodLookup.TryGetValue(otherExpectedName, out canSetMethod)) - { - return $"{declaringType.Name} expected to have a {expectedName} or {otherExpectedName} method"; - } + otherExpectedName = "CanHave" + methodName[3..]; } - else + else if (methodName.StartsWith("HasNo", StringComparison.Ordinal)) { - return $"{declaringType.Name} expected to have a {expectedName} method"; + otherExpectedName = "CanHaveNo" + methodName[3..]; } + + if (!methodLookup.TryGetValue(otherExpectedName, out canSetMethod)) + { + return $"{declaringType.Name} expected to have a {expectedName} or {otherExpectedName} method"; + } + } + + if (canSetMethod.ReturnType != typeof(bool)) + { + return $"{declaringType.Name}.{canSetMethod.Name}({Format(canSetMethod.GetParameters())})" + + $" expected to have return type of 'bool'"; } var parameterIndex = method.IsStatic ? 1 : 0; diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.Generic.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.Generic.cs index 98877a481a7..bd656f5d05e 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.Generic.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.Generic.cs @@ -1407,9 +1407,15 @@ public override TestOwnedNavigationBuilder Ignore( public override TestIndexBuilder HasIndex(params string[] propertyNames) => new GenericTestIndexBuilder(OwnedNavigationBuilder.HasIndex(propertyNames)); + public override TestIndexBuilder HasIndex(string[] propertyNames, string name) + => new GenericTestIndexBuilder(OwnedNavigationBuilder.HasIndex(propertyNames, name)); + public override TestIndexBuilder HasIndex(Expression> indexExpression) => new GenericTestIndexBuilder(OwnedNavigationBuilder.HasIndex(indexExpression)); + public override TestIndexBuilder HasIndex(Expression> indexExpression, string name) + => new GenericTestIndexBuilder(OwnedNavigationBuilder.HasIndex(indexExpression, name)); + public override TestOwnershipBuilder WithOwner(string? ownerReference) => new GenericTestOwnershipBuilder( OwnedNavigationBuilder.WithOwner(ownerReference)); diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonGeneric.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonGeneric.cs index 1cea14c05df..44a00f536f6 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonGeneric.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.NonGeneric.cs @@ -1482,11 +1482,19 @@ public override TestOwnedNavigationBuilder Ignore( public override TestIndexBuilder HasIndex(params string[] propertyNames) => new NonGenericTestIndexBuilder(OwnedNavigationBuilder.HasIndex(propertyNames)); + public override TestIndexBuilder HasIndex(string[] propertyNames, string name) + => new NonGenericTestIndexBuilder(OwnedNavigationBuilder.HasIndex(propertyNames, name)); + public override TestIndexBuilder HasIndex(Expression> indexExpression) => new NonGenericTestIndexBuilder( OwnedNavigationBuilder.HasIndex( indexExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray())); + public override TestIndexBuilder HasIndex(Expression> indexExpression, string name) + => new NonGenericTestIndexBuilder( + OwnedNavigationBuilder.HasIndex( + indexExpression.GetMemberAccessList().Select(p => p.GetSimpleMemberName()).ToArray(), name)); + public override TestOwnershipBuilder WithOwner(string? ownerReference) => new NonGenericTestOwnershipBuilder( OwnedNavigationBuilder.WithOwner(ownerReference)); diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.OwnedTypes.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.OwnedTypes.cs index 32c2f2f2c40..deef5a8683b 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.OwnedTypes.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.OwnedTypes.cs @@ -445,6 +445,9 @@ public virtual void Can_configure_owned_type_collection() .Ignore(o => o.Details); entityBuilder.Property("foo"); entityBuilder.HasIndex("foo"); + entityBuilder.HasIndex(["foo"], "Foo2"); + entityBuilder.HasIndex(o => new { o.CustomerId }); + entityBuilder.HasIndex(o => new { o.CustomerId }, "Customer2"); entityBuilder.HasKey(o => o.AnotherCustomerId); entityBuilder.WithOwner(o => o.Customer) .HasPrincipalKey(c => c.AlternateKey); @@ -462,17 +465,25 @@ public virtual void Can_configure_owned_type_collection() Assert.Null(owner.FindProperty("foo")); Assert.Equal(nameof(Order.AnotherCustomerId), owned.FindPrimaryKey().Properties.Single().Name); + var unnamedIndexes = owned.GetIndexes().Where(i => i.Name == null).ToList(); if (Fixture.ForeignKeysHaveIndexes) { - Assert.Equal(2, owned.GetIndexes().Count()); - Assert.Equal("CustomerAlternateKey", owned.GetIndexes().First().Properties.Single().Name); - } - else - { - Assert.Single(owned.GetIndexes()); + var fkIndex = unnamedIndexes.Single(i => i.Properties.Contains(ownership.Properties.Single())); + Assert.Equal("CustomerAlternateKey", fkIndex.Properties.Single().Name); + unnamedIndexes.Remove(fkIndex); } - Assert.Equal("foo", owned.GetIndexes().Last().Properties.Single().Name); + Assert.Equal(2, unnamedIndexes.Count()); + Assert.Equal(nameof(Order.CustomerId), unnamedIndexes.First().Properties.Single().Name); + Assert.Equal("foo", unnamedIndexes.Last().Properties.Single().Name); + + var namedIndexes = owned.GetIndexes().Where(i => i.Name != null).ToList(); + Assert.Equal(2, namedIndexes.Count()); + Assert.Equal(nameof(Order.CustomerId), namedIndexes.First().Properties.Single().Name); + Assert.Equal("Customer2", namedIndexes.First().Name); + Assert.Equal("foo", namedIndexes.Last().Properties.Single().Name); + Assert.Equal("Foo2", namedIndexes.Last().Name); + Assert.Equal(PropertyAccessMode.FieldDuringConstruction, owned.GetPropertyAccessMode()); Assert.Equal(ChangeTrackingStrategy.ChangedNotifications, owned.GetChangeTrackingStrategy()); diff --git a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.cs b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.cs index da145a8e973..c7b1d0f0d2f 100644 --- a/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.cs +++ b/test/EFCore.Specification.Tests/ModelBuilding/ModelBuilderTest.cs @@ -931,7 +931,10 @@ public abstract TestOwnedNavigationBuilder Ignore( Expression> propertyExpression); public abstract TestIndexBuilder HasIndex(params string[] propertyNames); + public abstract TestIndexBuilder HasIndex(string[] propertyNames, string name); + public abstract TestIndexBuilder HasIndex(Expression> indexExpression); + public abstract TestIndexBuilder HasIndex(Expression> indexExpression, string name); public abstract TestOwnershipBuilder WithOwner(string? ownerReference); diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs index 1519bc85790..c22f9e261f3 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs @@ -31,6 +31,7 @@ public class SqlServerApiConsistencyFixture : ApiConsistencyFixtureBase typeof(SqlServerEntityTypeBuilderExtensions), typeof(SqlServerServiceCollectionExtensions), typeof(SqlServerDbFunctionsExtensions), + typeof(SqlServerTableBuilderExtensions), typeof(OwnedNavigationTemporalPeriodPropertyBuilder), typeof(OwnedNavigationTemporalTableBuilder), typeof(OwnedNavigationTemporalTableBuilder<,>), diff --git a/test/EFCore.SqlServer.Tests/Metadata/SqlServerMetadataBuilderExtensionsTest.cs b/test/EFCore.SqlServer.Tests/Metadata/SqlServerMetadataBuilderExtensionsTest.cs index f138a6465f4..c744b846c0d 100644 --- a/test/EFCore.SqlServer.Tests/Metadata/SqlServerMetadataBuilderExtensionsTest.cs +++ b/test/EFCore.SqlServer.Tests/Metadata/SqlServerMetadataBuilderExtensionsTest.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.SqlServer.Internal; // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.Metadata; @@ -257,7 +256,7 @@ public void Can_access_model_performance_level() } [ConditionalFact] - public void Can_access_entity_type() + public void Can_change_entity_type_IsMemoryOptimized() { var typeBuilder = CreateBuilder().Entity(typeof(Splot)); @@ -280,6 +279,50 @@ public void Can_access_entity_type() Assert.Null(typeBuilder.Metadata.GetIsMemoryOptimizedConfigurationSource()); } + [ConditionalFact] + public void Can_change_entity_type_UseSqlOutputClause() + { + var typeBuilder = CreateBuilder().Entity(typeof(Splot)); + + Assert.Null(typeBuilder.Metadata.GetUseSqlOutputClauseConfigurationSource()); + + Assert.NotNull(typeBuilder.UseSqlOutputClause(true)); + Assert.True(typeBuilder.Metadata.IsSqlOutputClauseUsed()); + Assert.Equal(ConfigurationSource.Convention, typeBuilder.Metadata.GetUseSqlOutputClauseConfigurationSource()); + + Assert.NotNull(typeBuilder.UseSqlOutputClause(false, fromDataAnnotation: true)); + Assert.False(typeBuilder.Metadata.IsSqlOutputClauseUsed()); + Assert.Equal(ConfigurationSource.DataAnnotation, typeBuilder.Metadata.GetUseSqlOutputClauseConfigurationSource()); + + Assert.Null(typeBuilder.UseSqlOutputClause(true)); + Assert.False(typeBuilder.Metadata.IsSqlOutputClauseUsed()); + Assert.NotNull(typeBuilder.UseSqlOutputClause(false)); + + Assert.NotNull(typeBuilder.UseSqlOutputClause(null, fromDataAnnotation: true)); + Assert.True(typeBuilder.Metadata.IsSqlOutputClauseUsed()); + Assert.Null(typeBuilder.Metadata.GetUseSqlOutputClauseConfigurationSource()); + + var fragmentId = StoreObjectIdentifier.Table("Split"); + + Assert.Null(typeBuilder.Metadata.GetUseSqlOutputClauseConfigurationSource(fragmentId)); + + Assert.NotNull(typeBuilder.UseSqlOutputClause(true, fragmentId)); + Assert.True(typeBuilder.Metadata.IsSqlOutputClauseUsed(fragmentId)); + Assert.Equal(ConfigurationSource.Convention, typeBuilder.Metadata.GetUseSqlOutputClauseConfigurationSource(fragmentId)); + + Assert.NotNull(typeBuilder.UseSqlOutputClause(false, fragmentId, fromDataAnnotation: true)); + Assert.False(typeBuilder.Metadata.IsSqlOutputClauseUsed(fragmentId)); + Assert.Equal(ConfigurationSource.DataAnnotation, typeBuilder.Metadata.GetUseSqlOutputClauseConfigurationSource(fragmentId)); + + Assert.Null(typeBuilder.UseSqlOutputClause(true, fragmentId)); + Assert.False(typeBuilder.Metadata.IsSqlOutputClauseUsed(fragmentId)); + Assert.NotNull(typeBuilder.UseSqlOutputClause(false, fragmentId)); + + Assert.NotNull(typeBuilder.UseSqlOutputClause(null, fragmentId, fromDataAnnotation: true)); + Assert.True(typeBuilder.Metadata.IsSqlOutputClauseUsed(fragmentId)); + Assert.Null(typeBuilder.Metadata.GetUseSqlOutputClauseConfigurationSource(fragmentId)); + } + [ConditionalFact] public void Can_access_property() { diff --git a/test/EFCore.Sqlite.FunctionalTests/SqliteApiConsistencyTest.cs b/test/EFCore.Sqlite.FunctionalTests/SqliteApiConsistencyTest.cs index 78caa579e80..262a390bc20 100644 --- a/test/EFCore.Sqlite.FunctionalTests/SqliteApiConsistencyTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/SqliteApiConsistencyTest.cs @@ -22,7 +22,9 @@ public class SqliteApiConsistencyFixture : ApiConsistencyFixtureBase typeof(SqliteServiceCollectionExtensions), typeof(SqliteDbContextOptionsBuilderExtensions), typeof(SqliteDbContextOptionsBuilder), - typeof(SqlitePropertyBuilderExtensions) + typeof(SqlitePropertyBuilderExtensions), + typeof(SqliteEntityTypeBuilderExtensions), + typeof(SqliteTableBuilderExtensions) ]; public override @@ -31,7 +33,8 @@ public override Type MutableExtensions, Type ConventionExtensions, Type ConventionBuilderExtensions, - Type RuntimeExtensions)> MetadataExtensionTypes { get; } + Type RuntimeExtensions)> MetadataExtensionTypes + { get; } = new() { { @@ -42,6 +45,15 @@ public override typeof(SqlitePropertyBuilderExtensions), null ) + }, + { + typeof(IReadOnlyEntityType), ( + typeof(SqliteEntityTypeExtensions), + typeof(SqliteEntityTypeExtensions), + typeof(SqliteEntityTypeExtensions), + typeof(SqliteEntityTypeBuilderExtensions), + null + ) } }; diff --git a/test/EFCore.Sqlite.Tests/Metadata/Builders/SqliteMetadataBuilderExtensionsTest.cs b/test/EFCore.Sqlite.Tests/Metadata/Builders/SqliteMetadataBuilderExtensionsTest.cs new file mode 100644 index 00000000000..48cb541c748 --- /dev/null +++ b/test/EFCore.Sqlite.Tests/Metadata/Builders/SqliteMetadataBuilderExtensionsTest.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +public class SqliteMetadataBuilderExtensionsTest +{ + private IConventionModelBuilder CreateBuilder() + => new InternalModelBuilder(new Model()); + + [ConditionalFact] + public void Can_change_entity_type_UseSqlReturningClause() + { + var typeBuilder = CreateBuilder().Entity(typeof(Splot))!; + + Assert.Null(typeBuilder.Metadata.GetUseSqlReturningClauseConfigurationSource()); + + Assert.NotNull(typeBuilder.UseSqlReturningClause(true)); + Assert.True(typeBuilder.Metadata.IsSqlReturningClauseUsed()); + Assert.Equal(ConfigurationSource.Convention, typeBuilder.Metadata.GetUseSqlReturningClauseConfigurationSource()); + + Assert.NotNull(typeBuilder.UseSqlReturningClause(false, fromDataAnnotation: true)); + Assert.False(typeBuilder.Metadata.IsSqlReturningClauseUsed()); + Assert.Equal(ConfigurationSource.DataAnnotation, typeBuilder.Metadata.GetUseSqlReturningClauseConfigurationSource()); + + Assert.Null(typeBuilder.UseSqlReturningClause(true)); + Assert.False(typeBuilder.Metadata.IsSqlReturningClauseUsed()); + Assert.NotNull(typeBuilder.UseSqlReturningClause(false)); + + Assert.NotNull(typeBuilder.UseSqlReturningClause(null, fromDataAnnotation: true)); + Assert.True(typeBuilder.Metadata.IsSqlReturningClauseUsed()); + Assert.Null(typeBuilder.Metadata.GetUseSqlReturningClauseConfigurationSource()); + + var fragmentId = StoreObjectIdentifier.Table("Split"); + + Assert.Null(typeBuilder.Metadata.GetUseSqlReturningClauseConfigurationSource(fragmentId)); + + Assert.NotNull(typeBuilder.UseSqlReturningClause(true, fragmentId)); + Assert.True(typeBuilder.Metadata.IsSqlReturningClauseUsed(fragmentId)); + Assert.Equal(ConfigurationSource.Convention, typeBuilder.Metadata.GetUseSqlReturningClauseConfigurationSource(fragmentId)); + + Assert.NotNull(typeBuilder.UseSqlReturningClause(false, fragmentId, fromDataAnnotation: true)); + Assert.False(typeBuilder.Metadata.IsSqlReturningClauseUsed(fragmentId)); + Assert.Equal(ConfigurationSource.DataAnnotation, typeBuilder.Metadata.GetUseSqlReturningClauseConfigurationSource(fragmentId)); + + Assert.Null(typeBuilder.UseSqlReturningClause(true, fragmentId)); + Assert.False(typeBuilder.Metadata.IsSqlReturningClauseUsed(fragmentId)); + Assert.NotNull(typeBuilder.UseSqlReturningClause(false, fragmentId)); + + Assert.NotNull(typeBuilder.UseSqlReturningClause(null, fragmentId, fromDataAnnotation: true)); + Assert.True(typeBuilder.Metadata.IsSqlReturningClauseUsed(fragmentId)); + Assert.Null(typeBuilder.Metadata.GetUseSqlReturningClauseConfigurationSource(fragmentId)); + } + + private class Splot; +}