diff --git a/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs b/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs index 8978925adcc..608f971b7a5 100644 --- a/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs +++ b/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs @@ -10,8 +10,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Conventions; -using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Migrations.Internal @@ -26,7 +24,7 @@ public class SnapshotModelProcessor : ISnapshotModelProcessor { private readonly IOperationReporter _operationReporter; private readonly HashSet _relationalNames; - private readonly IConventionSetBuilder _conventionSetBuilder; + private readonly IModelRuntimeInitializer _modelRuntimeInitializer; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -36,7 +34,7 @@ public class SnapshotModelProcessor : ISnapshotModelProcessor /// public SnapshotModelProcessor( [NotNull] IOperationReporter operationReporter, - [NotNull] IConventionSetBuilder conventionSetBuilder) + [NotNull] IModelRuntimeInitializer modelRuntimeInitializer) { _operationReporter = operationReporter; _relationalNames = new HashSet( @@ -44,7 +42,7 @@ public SnapshotModelProcessor( .GetRuntimeFields() .Where(p => p.Name != nameof(RelationalAnnotationNames.Prefix)) .Select(p => ((string)p.GetValue(null)).Substring(RelationalAnnotationNames.Prefix.Length - 1))); - _conventionSetBuilder = conventionSetBuilder; + _modelRuntimeInitializer = modelRuntimeInitializer; } /// @@ -82,27 +80,12 @@ public virtual IModel Process(IModel model) } } - if (model is IConventionModel conventionModel) + if (model is IMutableModel mutableModel) { - var conventionSet = _conventionSetBuilder.CreateConventionSet(); - - var typeMappingConvention = conventionSet.ModelFinalizingConventions.OfType().FirstOrDefault(); - if (typeMappingConvention != null) - { - typeMappingConvention.ProcessModelFinalizing(conventionModel.Builder, null); - } - - var relationalModelConvention = - conventionSet.ModelFinalizedConventions.OfType().FirstOrDefault(); - if (relationalModelConvention != null) - { - model = relationalModelConvention.ProcessModelFinalized(conventionModel); - } + model = mutableModel.FinalizeModel(); } - return model is IMutableModel mutableModel - ? mutableModel.FinalizeModel() - : model; + return _modelRuntimeInitializer.Initialize(model, validationLogger: null); } private void ProcessCollection(IEnumerable metadata, string version) diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs index aff4e16ebee..be4440770ca 100644 --- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs @@ -29,23 +29,9 @@ public class AnnotationCodeGenerator : IAnnotationCodeGenerator { private static readonly ISet _ignoredRelationalAnnotations = new HashSet { - RelationalAnnotationNames.RelationalModel, RelationalAnnotationNames.CheckConstraints, RelationalAnnotationNames.Sequences, RelationalAnnotationNames.DbFunctions, - RelationalAnnotationNames.DefaultMappings, - RelationalAnnotationNames.DefaultColumnMappings, - RelationalAnnotationNames.TableMappings, - RelationalAnnotationNames.TableColumnMappings, - RelationalAnnotationNames.ViewMappings, - RelationalAnnotationNames.ViewColumnMappings, - RelationalAnnotationNames.FunctionMappings, - RelationalAnnotationNames.FunctionColumnMappings, - RelationalAnnotationNames.SqlQueryMappings, - RelationalAnnotationNames.SqlQueryColumnMappings, - RelationalAnnotationNames.ForeignKeyMappings, - RelationalAnnotationNames.TableIndexMappings, - RelationalAnnotationNames.UniqueConstraintMappings, RelationalAnnotationNames.RelationalOverrides }; diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs index 5fc70755186..f746f0a3d6b 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs @@ -247,7 +247,7 @@ public static void SetSchema([NotNull] this IMutableEntityType entityType, [CanB /// The entity type to get the table mappings for. /// The tables to which the entity type is mapped. public static IEnumerable GetDefaultMappings([NotNull] this IEntityType entityType) - => (IEnumerable?)entityType[RelationalAnnotationNames.DefaultMappings] + => (IEnumerable?)entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.DefaultMappings) ?? Array.Empty(); /// @@ -256,7 +256,7 @@ public static IEnumerable GetDefaultMappings([NotNull] this I /// The entity type to get the table mappings for. /// The tables to which the entity type is mapped. public static IEnumerable GetTableMappings([NotNull] this IEntityType entityType) - => (IEnumerable?)entityType[RelationalAnnotationNames.TableMappings] + => (IEnumerable?)entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableMappings) ?? Array.Empty(); /// @@ -419,7 +419,7 @@ public static void SetViewSchema([NotNull] this IMutableEntityType entityType, [ /// The entity type to get the view mappings for. /// The views to which the entity type is mapped. public static IEnumerable GetViewMappings([NotNull] this IEntityType entityType) - => (IEnumerable?)entityType[RelationalAnnotationNames.ViewMappings] + => (IEnumerable?)entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.ViewMappings) ?? Array.Empty(); /// @@ -493,7 +493,7 @@ public static void SetSqlQuery([NotNull] this IMutableEntityType entityType, [Ca /// The entity type to get the function mappings for. /// The functions to which the entity type is mapped. public static IEnumerable GetSqlQueryMappings([NotNull] this IEntityType entityType) - => (IEnumerable?)entityType[RelationalAnnotationNames.SqlQueryMappings] + => (IEnumerable?)entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.SqlQueryMappings) ?? Array.Empty(); /// @@ -558,7 +558,7 @@ public static void SetFunctionName([NotNull] this IMutableEntityType entityType, /// The entity type to get the function mappings for. /// The functions to which the entity type is mapped. public static IEnumerable GetFunctionMappings([NotNull] this IEntityType entityType) - => (IEnumerable?)entityType[RelationalAnnotationNames.FunctionMappings] + => (IEnumerable?)entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.FunctionMappings) ?? Array.Empty(); /// diff --git a/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs index c7d1d611c38..f38ce583a8a 100644 --- a/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs @@ -190,7 +190,7 @@ public static void SetConstraintName([NotNull] this IMutableForeignKey foreignKe /// The foreign key. /// The foreign key constraints to which the foreign key is mapped. public static IEnumerable GetMappedConstraints([NotNull] this IForeignKey foreignKey) - => (IEnumerable?)foreignKey[RelationalAnnotationNames.ForeignKeyMappings] + => (IEnumerable?)foreignKey.FindRuntimeAnnotationValue(RelationalAnnotationNames.ForeignKeyMappings) ?? Enumerable.Empty(); /// diff --git a/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs b/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs index 1f8ad74164a..afd84227752 100644 --- a/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs @@ -270,7 +270,7 @@ public static void SetFilter([NotNull] this IMutableIndex index, [CanBeNull] str /// The index. /// The table indexes to which the index is mapped. public static IEnumerable GetMappedTableIndexes([NotNull] this IIndex index) - => (IEnumerable?)index[RelationalAnnotationNames.TableIndexMappings] + => (IEnumerable?)index.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableIndexMappings) ?? Enumerable.Empty(); /// diff --git a/src/EFCore.Relational/Extensions/RelationalKeyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalKeyExtensions.cs index 90b87736472..400589cbc17 100644 --- a/src/EFCore.Relational/Extensions/RelationalKeyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalKeyExtensions.cs @@ -184,7 +184,7 @@ public static void SetName([NotNull] this IMutableKey key, [CanBeNull] string? n /// The key. /// The unique constraints to which the key is mapped. public static IEnumerable GetMappedConstraints([NotNull] this IKey key) - => (IEnumerable?)key[RelationalAnnotationNames.UniqueConstraintMappings] + => (IEnumerable?)key.FindRuntimeAnnotationValue(RelationalAnnotationNames.UniqueConstraintMappings) ?? Enumerable.Empty(); /// diff --git a/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs b/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs index 64f24cd5582..d50758ca98d 100644 --- a/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs @@ -134,10 +134,10 @@ public static void SetDefaultSchema([NotNull] this IMutableModel model, [CanBeNu /// The database model. public static IRelationalModel GetRelationalModel([NotNull] this IModel model) { - var databaseModel = (IRelationalModel?)model[RelationalAnnotationNames.RelationalModel]; + var databaseModel = (IRelationalModel?)model.FindRuntimeAnnotationValue(RelationalAnnotationNames.RelationalModel); if (databaseModel == null) { - throw new InvalidOperationException(RelationalStrings.DatabaseModelMissing); + throw new InvalidOperationException(CoreStrings.ModelNotFinalized(nameof(GetRelationalModel))); } return databaseModel; diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs index 123e6421ded..fac0596b325 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs @@ -362,7 +362,7 @@ public static void SetColumnType([NotNull] this IMutableProperty property, [CanB /// The property. /// The default columns to which the property would be mapped. public static IEnumerable GetDefaultColumnMappings([NotNull] this IProperty property) - => (IEnumerable?)property[RelationalAnnotationNames.DefaultColumnMappings] + => (IEnumerable?)property.FindRuntimeAnnotationValue(RelationalAnnotationNames.DefaultColumnMappings) ?? Enumerable.Empty(); /// @@ -371,7 +371,7 @@ public static IEnumerable GetDefaultColumnMappings([NotNull] /// The property. /// The table columns to which the property is mapped. public static IEnumerable GetTableColumnMappings([NotNull] this IProperty property) - => (IEnumerable?)property[RelationalAnnotationNames.TableColumnMappings] + => (IEnumerable?)property.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableColumnMappings) ?? Enumerable.Empty(); /// @@ -380,7 +380,7 @@ public static IEnumerable GetTableColumnMappings([NotNull] this /// The property. /// The view columns to which the property is mapped. public static IEnumerable GetViewColumnMappings([NotNull] this IProperty property) - => (IEnumerable?)property[RelationalAnnotationNames.ViewColumnMappings] + => (IEnumerable?)property.FindRuntimeAnnotationValue(RelationalAnnotationNames.ViewColumnMappings) ?? Enumerable.Empty(); /// @@ -389,7 +389,7 @@ public static IEnumerable GetViewColumnMappings([NotNull] th /// The property. /// The SQL query columns to which the property is mapped. public static IEnumerable GetSqlQueryColumnMappings([NotNull] this IProperty property) - => (IEnumerable?)property[RelationalAnnotationNames.SqlQueryColumnMappings] + => (IEnumerable?)property.FindRuntimeAnnotationValue(RelationalAnnotationNames.SqlQueryColumnMappings) ?? Enumerable.Empty(); /// @@ -398,7 +398,7 @@ public static IEnumerable GetSqlQueryColumnMappings([Not /// The property. /// The function columns to which the property is mapped. public static IEnumerable GetFunctionColumnMappings([NotNull] this IProperty property) - => (IEnumerable?)property[RelationalAnnotationNames.FunctionColumnMappings] + => (IEnumerable?)property.FindRuntimeAnnotationValue(RelationalAnnotationNames.FunctionColumnMappings) ?? Enumerable.Empty(); /// diff --git a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs index 75d154a2ee6..2f79f8d0156 100644 --- a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs +++ b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs @@ -134,6 +134,7 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices() TryAdd(); TryAdd(); TryAdd(); + TryAdd(); TryAdd(); TryAdd(); TryAdd(); @@ -198,6 +199,8 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices() .AddDependencySingleton() .AddDependencySingleton() .AddDependencySingleton() + .AddDependencySingleton() + .AddDependencySingleton() .AddDependencyScoped() .AddDependencyScoped() .AddDependencyScoped() diff --git a/src/EFCore.Relational/Infrastructure/ModelRuntimeInitializerDependencies.cs b/src/EFCore.Relational/Infrastructure/ModelRuntimeInitializerDependencies.cs new file mode 100644 index 00000000000..f9150922dda --- /dev/null +++ b/src/EFCore.Relational/Infrastructure/ModelRuntimeInitializerDependencies.cs @@ -0,0 +1,79 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Utilities; +using Microsoft.Extensions.DependencyInjection; + +#nullable enable + +namespace Microsoft.EntityFrameworkCore.Infrastructure +{ + /// + /// + /// Service dependencies parameter class for + /// + /// + /// This type is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// Do not construct instances of this class directly from either provider or application code as the + /// constructor signature may change as new dependencies are added. Instead, use this type in + /// your constructor so that an instance will be created and injected automatically by the + /// dependency injection container. To create an instance with some dependent services replaced, + /// first resolve the object from the dependency injection container, then replace selected + /// services using the 'With...' methods. Do not call the constructor at any point in this process. + /// + /// + /// The service lifetime is . + /// This means a single instance of each service is used by many instances. + /// The implementation must be thread-safe. + /// This service cannot depend on services registered as . + /// + /// + public sealed record RelationalModelRuntimeInitializerDependencies + { + /// + /// + /// Creates the service dependencies parameter object for a . + /// + /// + /// 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. + /// + /// + /// Do not call this constructor directly from either provider or application code as it may change + /// as new dependencies are added. Instead, use this type in your constructor so that an instance + /// will be created and injected automatically by the dependency injection container. To create + /// an instance with some dependent services replaced, first resolve the object from the dependency + /// injection container, then replace selected services using the 'With...' methods. Do not call + /// the constructor at any point in this process. + /// + /// + [EntityFrameworkInternal] + public RelationalModelRuntimeInitializerDependencies( + [NotNull] RelationalModelDependencies singletonModelDependencies, + [NotNull] IRelationalAnnotationProvider relationalAnnotationProvider) + { + Check.NotNull(singletonModelDependencies, nameof(singletonModelDependencies)); + Check.NotNull(relationalAnnotationProvider, nameof(relationalAnnotationProvider)); + + RelationalModelDependencies = singletonModelDependencies; + RelationalAnnotationProvider = relationalAnnotationProvider; + } + + /// + /// The relational model dependencies. + /// + public RelationalModelDependencies RelationalModelDependencies { get; [param: NotNull] init; } + + /// + /// The relational annotation provider. + /// + public IRelationalAnnotationProvider RelationalAnnotationProvider { get; [param: NotNull] init; } + } +} diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelDependencies.cs b/src/EFCore.Relational/Infrastructure/RelationalModelDependencies.cs new file mode 100644 index 00000000000..a399735798f --- /dev/null +++ b/src/EFCore.Relational/Infrastructure/RelationalModelDependencies.cs @@ -0,0 +1,59 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.Extensions.DependencyInjection; + +#nullable enable + +namespace Microsoft.EntityFrameworkCore.Infrastructure +{ + /// + /// + /// The relational model service dependencies. + /// + /// + /// This type is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// Do not construct instances of this class directly from either provider or application code as the + /// constructor signature may change as new dependencies are added. Instead, use this type in + /// your constructor so that an instance will be created and injected automatically by the + /// dependency injection container. To create an instance with some dependent services replaced, + /// first resolve the object from the dependency injection container, then replace selected + /// services using the 'With...' methods. Do not call the constructor at any point in this process. + /// + /// + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. + /// + /// + public sealed record RelationalModelDependencies + { + /// + /// + /// Creates the relational model service dependencies. + /// + /// + /// Do not call this constructor directly from either provider or application code as it may change + /// as new dependencies are added. Instead, use this type in your constructor so that an instance + /// will be created and injected automatically by the dependency injection container. To create + /// an instance with some dependent services replaced, first resolve the object from the dependency + /// injection container, then replace selected services using the 'With...' methods. Do not call + /// the constructor at any point in this process. + /// + /// + /// 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. + /// + /// + [EntityFrameworkInternal] + public RelationalModelDependencies() + { + } + } +} diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelExtensions.cs b/src/EFCore.Relational/Infrastructure/RelationalModelExtensions.cs new file mode 100644 index 00000000000..62480af6bf6 --- /dev/null +++ b/src/EFCore.Relational/Infrastructure/RelationalModelExtensions.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Metadata; + +#nullable enable + +namespace Microsoft.EntityFrameworkCore.Infrastructure +{ + /// + /// Relational-specific extension methods for and extension methods for . + /// + public static class RelationalModelExtensions + { + /// + /// Returns the relational service dependencies. + /// + /// The model. + /// The name of the calling method. + /// The relational service dependencies. + public static RelationalModelDependencies GetRelationalDependencies( + [NotNull] this IModel model, [CallerMemberName][CanBeNull] string methodName = "") + => (RelationalModelDependencies?)model + .FindRuntimeAnnotation(RelationalAnnotationNames.ModelDependencies)?.Value + ?? throw new InvalidOperationException(CoreStrings.ModelNotFinalized(methodName)); + } +} diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelRuntimeInitializer.cs b/src/EFCore.Relational/Infrastructure/RelationalModelRuntimeInitializer.cs new file mode 100644 index 00000000000..dc0bf40d0bf --- /dev/null +++ b/src/EFCore.Relational/Infrastructure/RelationalModelRuntimeInitializer.cs @@ -0,0 +1,70 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Utilities; +using Microsoft.Extensions.DependencyInjection; + +#nullable enable + +namespace Microsoft.EntityFrameworkCore.Infrastructure +{ + /// + /// + /// Initializes a with the runtime dependencies. + /// + /// + /// This type is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The service lifetime is . This means a single instance + /// is used by many instances. The implementation must be thread-safe. + /// This service cannot depend on services registered as . + /// + /// + public class RelationalModelRuntimeInitializer : ModelRuntimeInitializer + { + /// + /// Creates a new instance. + /// + /// The dependencies to use. + /// The relational dependencies to use. + public RelationalModelRuntimeInitializer( + [NotNull] ModelRuntimeInitializerDependencies dependencies, + [NotNull] RelationalModelRuntimeInitializerDependencies relationalDependencies) + : base(dependencies) + { + Check.NotNull(relationalDependencies, nameof(relationalDependencies)); + + RelationalDependencies = relationalDependencies; + } + + /// + /// The relational dependencies. + /// + protected virtual RelationalModelRuntimeInitializerDependencies RelationalDependencies { get; } + + /// + /// Initializes the given model with runtime dependencies. + /// + /// The model to initialize. + /// + /// indicates that only pre-validation initialization should be performed; + /// indicates that only post-validation initialization should be performed; + /// + protected override void InitializeModel(IModel model, bool preValidation) + { + if (preValidation) + { + model.AddRuntimeAnnotation(RelationalAnnotationNames.ModelDependencies, RelationalDependencies); + } + else + { + RelationalModel.Add((IConventionModel)model, RelationalDependencies.RelationalAnnotationProvider); + } + } + } +} diff --git a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs index 676c00f7e91..6a14683ff0b 100644 --- a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs +++ b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs @@ -95,17 +95,12 @@ public override ConventionSet CreateConventionSet() var dbFunctionAttributeConvention = new RelationalDbFunctionAttributeConvention(Dependencies, RelationalDependencies); conventionSet.ModelInitializedConventions.Add(dbFunctionAttributeConvention); - // Use TypeMappingConvention to add the relational store type mapping - // to the generated concurrency token property - ConventionSet.AddBefore( - conventionSet.ModelFinalizingConventions, - new TableSharingConcurrencyTokenConvention(Dependencies, RelationalDependencies), - typeof(TypeMappingConvention)); // ModelCleanupConvention would remove the entity types added by TableValuedDbFunctionConvention #15898 ConventionSet.AddAfter( conventionSet.ModelFinalizingConventions, new TableValuedDbFunctionConvention(Dependencies, RelationalDependencies), typeof(ModelCleanupConvention)); + conventionSet.ModelFinalizingConventions.Add(new TableSharingConcurrencyTokenConvention(Dependencies, RelationalDependencies)); conventionSet.ModelFinalizingConventions.Add(dbFunctionAttributeConvention); conventionSet.ModelFinalizingConventions.Add(tableNameFromDbSetConvention); conventionSet.ModelFinalizingConventions.Add(storeGenerationConvention); @@ -118,11 +113,6 @@ public override ConventionSet CreateConventionSet() (QueryFilterRewritingConvention)new RelationalQueryFilterRewritingConvention( Dependencies, RelationalDependencies)); - ConventionSet.AddAfter( - conventionSet.ModelFinalizedConventions, - new RelationalModelConvention(Dependencies, RelationalDependencies), - typeof(ValidatingConvention)); - return conventionSet; } } diff --git a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilderDependencies.cs b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilderDependencies.cs index e3c71fc1452..81faed1173a 100644 --- a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilderDependencies.cs +++ b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilderDependencies.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Utilities; @@ -61,16 +62,14 @@ public sealed record RelationalConventionSetBuilderDependencies /// /// [EntityFrameworkInternal] - public RelationalConventionSetBuilderDependencies([NotNull] IRelationalAnnotationProvider relationalAnnotationProvider) + public RelationalConventionSetBuilderDependencies() { - Check.NotNull(relationalAnnotationProvider, nameof(relationalAnnotationProvider)); - - RelationalAnnotationProvider = relationalAnnotationProvider; } /// /// The relational annotation provider. /// + [Obsolete("This is now part of RelationalModelRuntimeInitializerDependencies")] public IRelationalAnnotationProvider RelationalAnnotationProvider { get; [param: NotNull] init; } } } diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalModelConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalModelConvention.cs index 3d8e7dcfc4c..f65bb333161 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalModelConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalModelConvention.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -12,6 +13,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions /// /// A convention that precomputes a relational model. /// + [Obsolete("Use IModelRuntimeInitializer.Initialize instead.")] public class RelationalModelConvention : IModelFinalizedConvention { /// diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeExtensions.cs index 764254adef4..e1c8604271c 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeExtensions.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeExtensions.cs @@ -32,8 +32,8 @@ public static class RelationalEntityTypeExtensions /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public static IEnumerable GetViewOrTableMappings([NotNull] this IEntityType entityType) - => (IEnumerable?)(entityType[RelationalAnnotationNames.ViewMappings] - ?? entityType[RelationalAnnotationNames.TableMappings]) + => (IEnumerable?)(entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.ViewMappings) + ?? entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableMappings)) ?? Enumerable.Empty(); /// diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index a3ef6d364b8..b271241a2f6 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -112,16 +112,10 @@ public RelationalModel([NotNull] IModel model) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public static IModel Add( - [NotNull] IConventionModel model, + [NotNull] IModel model, [CanBeNull] IRelationalAnnotationProvider? relationalAnnotationProvider) { - if (model.FindAnnotation(RelationalAnnotationNames.RelationalModel) != null) - { - return model; - } - var databaseModel = new RelationalModel(model); - model.SetAnnotation(RelationalAnnotationNames.RelationalModel, databaseModel); foreach (var entityType in model.GetEntityTypes()) { @@ -225,10 +219,11 @@ public static IModel Add( databaseModel.AddAnnotations(relationalAnnotationProvider.For(databaseModel)); } + model.AddRuntimeAnnotation(RelationalAnnotationNames.RelationalModel, databaseModel); return model; } - private static void AddDefaultMappings(RelationalModel databaseModel, IConventionEntityType entityType) + private static void AddDefaultMappings(RelationalModel databaseModel, IEntityType entityType) { var rootType = entityType.GetRootType(); var name = rootType.HasSharedClrType ? rootType.FullName() : rootType.ShortName(); @@ -254,7 +249,7 @@ private static void AddDefaultMappings(RelationalModel databaseModel, IConventio var column = (ColumnBase?)defaultTable.FindColumn(columnName); if (column == null) { - column = new ColumnBase(columnName, property.GetColumnType()!, defaultTable); + column = new (columnName, property.GetColumnType()!, defaultTable); column.IsNullable = property.IsColumnNullable(); defaultTable.Columns.Add(columnName, column); } @@ -268,21 +263,21 @@ private static void AddDefaultMappings(RelationalModel databaseModel, IConventio tableMapping.ColumnMappings.Add(columnMapping); column.PropertyMappings.Add(columnMapping); - var columnMappings = property[RelationalAnnotationNames.DefaultColumnMappings] as SortedSet; - if (columnMappings == null) + if (property.FindRuntimeAnnotationValue(RelationalAnnotationNames.DefaultColumnMappings) + is not SortedSet columnMappings) { columnMappings = new SortedSet(ColumnMappingBaseComparer.Instance); - property.SetAnnotation(RelationalAnnotationNames.DefaultColumnMappings, columnMappings); + property.AddRuntimeAnnotation(RelationalAnnotationNames.DefaultColumnMappings, columnMappings); } columnMappings.Add(columnMapping); } - var tableMappings = entityType[RelationalAnnotationNames.DefaultMappings] as List; - if (tableMappings == null) + if (entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.DefaultMappings) + is not List tableMappings) { tableMappings = new List(); - entityType.SetAnnotation(RelationalAnnotationNames.DefaultMappings, tableMappings); + entityType.AddRuntimeAnnotation(RelationalAnnotationNames.DefaultMappings, tableMappings); } if (tableMapping.ColumnMappings.Count != 0 @@ -295,7 +290,7 @@ private static void AddDefaultMappings(RelationalModel databaseModel, IConventio tableMappings.Reverse(); } - private static void AddTables(RelationalModel databaseModel, IConventionEntityType entityType) + private static void AddTables(RelationalModel databaseModel, IEntityType entityType) { var tableName = entityType.GetTableName(); if (tableName != null) @@ -348,7 +343,7 @@ private static void AddTables(RelationalModel databaseModel, IConventionEntityTy var column = (Column?)table.FindColumn(columnName); if (column == null) { - column = new Column(columnName, property.GetColumnType(mappedTable)!, table); + column = new (columnName, property.GetColumnType(mappedTable)!, table); column.IsNullable = property.IsColumnNullable(mappedTable); table.Columns.Add(columnName, column); } @@ -362,11 +357,11 @@ private static void AddTables(RelationalModel databaseModel, IConventionEntityTy tableMapping.ColumnMappings.Add(columnMapping); column.PropertyMappings.Add(columnMapping); - var columnMappings = property[RelationalAnnotationNames.TableColumnMappings] as SortedSet; - if (columnMappings == null) + if (property.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableColumnMappings) + is not SortedSet columnMappings) { columnMappings = new SortedSet(ColumnMappingBaseComparer.Instance); - property.SetAnnotation(RelationalAnnotationNames.TableColumnMappings, columnMappings); + property.AddRuntimeAnnotation(RelationalAnnotationNames.TableColumnMappings, columnMappings); } columnMappings.Add(columnMapping); @@ -374,11 +369,12 @@ private static void AddTables(RelationalModel databaseModel, IConventionEntityTy mappedType = mappedType.BaseType; - tableMappings = entityType[RelationalAnnotationNames.TableMappings] as List; + tableMappings = entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableMappings) + as List; if (tableMappings == null) { tableMappings = new List(); - entityType.SetAnnotation(RelationalAnnotationNames.TableMappings, tableMappings); + entityType.AddRuntimeAnnotation(RelationalAnnotationNames.TableMappings, tableMappings); } if (tableMapping.ColumnMappings.Count != 0 @@ -393,7 +389,7 @@ private static void AddTables(RelationalModel databaseModel, IConventionEntityTy } } - private static void AddViews(RelationalModel databaseModel, IConventionEntityType entityType) + private static void AddViews(RelationalModel databaseModel, IEntityType entityType) { var viewName = entityType.GetViewName(); if (viewName == null) @@ -439,7 +435,7 @@ private static void AddViews(RelationalModel databaseModel, IConventionEntityTyp var column = (ViewColumn?)view.FindColumn(columnName); if (column == null) { - column = new ViewColumn(columnName, property.GetColumnType(mappedView)!, view); + column = new (columnName, property.GetColumnType(mappedView)!, view); column.IsNullable = property.IsColumnNullable(mappedView); view.Columns.Add(columnName, column); } @@ -453,11 +449,11 @@ private static void AddViews(RelationalModel databaseModel, IConventionEntityTyp viewMapping.ColumnMappings.Add(columnMapping); column.PropertyMappings.Add(columnMapping); - var columnMappings = property[RelationalAnnotationNames.ViewColumnMappings] as SortedSet; - if (columnMappings == null) + if (property.FindRuntimeAnnotationValue(RelationalAnnotationNames.ViewColumnMappings) + is not SortedSet columnMappings) { columnMappings = new SortedSet(ColumnMappingBaseComparer.Instance); - property.SetAnnotation(RelationalAnnotationNames.ViewColumnMappings, columnMappings); + property.AddRuntimeAnnotation(RelationalAnnotationNames.ViewColumnMappings, columnMappings); } columnMappings.Add(columnMapping); @@ -465,11 +461,11 @@ private static void AddViews(RelationalModel databaseModel, IConventionEntityTyp mappedType = mappedType.BaseType; - viewMappings = entityType[RelationalAnnotationNames.ViewMappings] as List; + viewMappings = entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.ViewMappings) as List; if (viewMappings == null) { viewMappings = new List(); - entityType.SetAnnotation(RelationalAnnotationNames.ViewMappings, viewMappings); + entityType.AddRuntimeAnnotation(RelationalAnnotationNames.ViewMappings, viewMappings); } if (viewMapping.ColumnMappings.Count != 0 @@ -483,7 +479,7 @@ private static void AddViews(RelationalModel databaseModel, IConventionEntityTyp viewMappings?.Reverse(); } - private static void AddSqlQueries(RelationalModel databaseModel, IConventionEntityType entityType) + private static void AddSqlQueries(RelationalModel databaseModel, IEntityType entityType) { var entityTypeSqlQuery = entityType.GetSqlQuery(); if (entityTypeSqlQuery == null) @@ -545,7 +541,7 @@ private static void AddSqlQueries(RelationalModel databaseModel, IConventionEnti var column = (SqlQueryColumn?)sqlQuery.FindColumn(columnName); if (column == null) { - column = new SqlQueryColumn(columnName, property.GetColumnType(mappedQuery)!, sqlQuery); + column = new (columnName, property.GetColumnType(mappedQuery)!, sqlQuery); column.IsNullable = property.IsColumnNullable(mappedQuery); sqlQuery.Columns.Add(columnName, column); } @@ -559,11 +555,11 @@ private static void AddSqlQueries(RelationalModel databaseModel, IConventionEnti queryMapping.ColumnMappings.Add(columnMapping); column.PropertyMappings.Add(columnMapping); - var columnMappings = property[RelationalAnnotationNames.SqlQueryColumnMappings] as SortedSet; - if (columnMappings == null) + if (property.FindRuntimeAnnotationValue(RelationalAnnotationNames.SqlQueryColumnMappings) + is not SortedSet columnMappings) { columnMappings = new SortedSet(ColumnMappingBaseComparer.Instance); - property.SetAnnotation(RelationalAnnotationNames.SqlQueryColumnMappings, columnMappings); + property.AddRuntimeAnnotation(RelationalAnnotationNames.SqlQueryColumnMappings, columnMappings); } columnMappings.Add(columnMapping); @@ -571,11 +567,11 @@ private static void AddSqlQueries(RelationalModel databaseModel, IConventionEnti mappedType = mappedType.BaseType; - queryMappings = entityType[RelationalAnnotationNames.SqlQueryMappings] as List; + queryMappings = entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.SqlQueryMappings) as List; if (queryMappings == null) { queryMappings = new List(); - entityType.SetAnnotation(RelationalAnnotationNames.SqlQueryMappings, queryMappings); + entityType.AddRuntimeAnnotation(RelationalAnnotationNames.SqlQueryMappings, queryMappings); } if (queryMapping.ColumnMappings.Count != 0 @@ -589,7 +585,7 @@ private static void AddSqlQueries(RelationalModel databaseModel, IConventionEnti queryMappings?.Reverse(); } - private static void AddMappedFunctions(RelationalModel databaseModel, IConventionEntityType entityType) + private static void AddMappedFunctions(RelationalModel databaseModel, IEntityType entityType) { var model = databaseModel.Model; var functionName = entityType.GetFunctionName(); @@ -615,11 +611,11 @@ private static void AddMappedFunctions(RelationalModel databaseModel, IConventio mappedType = mappedType.BaseType; - functionMappings = entityType[RelationalAnnotationNames.FunctionMappings] as List; + functionMappings = entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.FunctionMappings) as List; if (functionMappings == null) { functionMappings = new List(); - entityType.SetAnnotation(RelationalAnnotationNames.FunctionMappings, functionMappings); + entityType.AddRuntimeAnnotation(RelationalAnnotationNames.FunctionMappings, functionMappings); } if (functionMapping.ColumnMappings.Count != 0 @@ -654,11 +650,11 @@ private static void AddTVFs(RelationalModel relationalModel) var functionMapping = CreateFunctionMapping(entityType, entityType, function, relationalModel, @default: false); - var functionMappings = entityType[RelationalAnnotationNames.FunctionMappings] as List; - if (functionMappings == null) + if (entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.FunctionMappings) + is not List functionMappings) { functionMappings = new List(); - entityType.SetAnnotation(RelationalAnnotationNames.FunctionMappings, functionMappings); + entityType.AddRuntimeAnnotation(RelationalAnnotationNames.FunctionMappings, functionMappings); } functionMappings.Add(functionMapping); @@ -667,8 +663,8 @@ private static void AddTVFs(RelationalModel relationalModel) } private static FunctionMapping CreateFunctionMapping( - IConventionEntityType entityType, - IConventionEntityType mappedType, + IEntityType entityType, + IEntityType mappedType, DbFunction dbFunction, RelationalModel model, bool @default) @@ -695,7 +691,7 @@ private static FunctionMapping CreateFunctionMapping( var column = (FunctionColumn?)storeFunction.FindColumn(columnName); if (column == null) { - column = new FunctionColumn(columnName, property.GetColumnType(mappedFunction)!, storeFunction); + column = new (columnName, property.GetColumnType(mappedFunction)!, storeFunction); column.IsNullable = property.IsColumnNullable(mappedFunction); storeFunction.Columns.Add(columnName, column); } @@ -709,11 +705,11 @@ private static FunctionMapping CreateFunctionMapping( functionMapping.ColumnMappings.Add(columnMapping); column.PropertyMappings.Add(columnMapping); - var columnMappings = property[RelationalAnnotationNames.FunctionColumnMappings] as SortedSet; - if (columnMappings == null) + if (property.FindRuntimeAnnotationValue(RelationalAnnotationNames.FunctionColumnMappings) + is not SortedSet columnMappings) { columnMappings = new SortedSet(ColumnMappingBaseComparer.Instance); - property.SetAnnotation(RelationalAnnotationNames.FunctionColumnMappings, columnMappings); + property.AddRuntimeAnnotation(RelationalAnnotationNames.FunctionColumnMappings, columnMappings); } columnMappings.Add(columnMapping); @@ -785,14 +781,14 @@ private static void PopulateConstraints(Table table) continue; } - var foreignKeyConstraints = - foreignKey[RelationalAnnotationNames.ForeignKeyMappings] as SortedSet; + var foreignKeyConstraints = foreignKey.FindRuntimeAnnotationValue(RelationalAnnotationNames.ForeignKeyMappings) + as SortedSet; if (table.ForeignKeyConstraints.TryGetValue(name, out var constraint)) { if (foreignKeyConstraints == null) { foreignKeyConstraints = new SortedSet(ForeignKeyConstraintComparer.Instance); - foreignKey.SetOrRemoveAnnotation(RelationalAnnotationNames.ForeignKeyMappings, foreignKeyConstraints); + foreignKey.AddRuntimeAnnotation(RelationalAnnotationNames.ForeignKeyMappings, foreignKeyConstraints); } foreignKeyConstraints.Add(constraint); @@ -862,7 +858,7 @@ private static void PopulateConstraints(Table table) if (foreignKeyConstraints == null) { foreignKeyConstraints = new SortedSet(ForeignKeyConstraintComparer.Instance); - foreignKey.SetOrRemoveAnnotation(RelationalAnnotationNames.ForeignKeyMappings, foreignKeyConstraints); + foreignKey.AddRuntimeAnnotation(RelationalAnnotationNames.ForeignKeyMappings, foreignKeyConstraints); } foreignKeyConstraints.Add(constraint); @@ -910,10 +906,11 @@ private static void PopulateConstraints(Table table) table.UniqueConstraints.Add(name, constraint); } - if (!(key[RelationalAnnotationNames.UniqueConstraintMappings] is SortedSet uniqueConstraints)) + if (key.FindRuntimeAnnotationValue(RelationalAnnotationNames.UniqueConstraintMappings) + is not SortedSet uniqueConstraints) { uniqueConstraints = new SortedSet(UniqueConstraintComparer.Instance); - key.SetOrRemoveAnnotation(RelationalAnnotationNames.UniqueConstraintMappings, uniqueConstraints); + key.AddRuntimeAnnotation(RelationalAnnotationNames.UniqueConstraintMappings, uniqueConstraints); } uniqueConstraints.Add(constraint); @@ -954,10 +951,11 @@ private static void PopulateConstraints(Table table) table.Indexes.Add(name, tableIndex); } - if (!(index[RelationalAnnotationNames.TableIndexMappings] is SortedSet tableIndexes)) + if (index.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableIndexMappings) + is not SortedSet tableIndexes) { tableIndexes = new SortedSet(TableIndexComparer.Instance); - index.SetOrRemoveAnnotation(RelationalAnnotationNames.TableIndexMappings, tableIndexes); + index.AddRuntimeAnnotation(RelationalAnnotationNames.TableIndexMappings, tableIndexes); } tableIndexes.Add(tableIndex); @@ -1113,24 +1111,14 @@ private static void PopulateRowInternalForeignKeys(TableBase table) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public static ReferentialAction ToReferentialAction(DeleteBehavior deleteBehavior) - { - switch (deleteBehavior) + => deleteBehavior switch { - case DeleteBehavior.SetNull: - return ReferentialAction.SetNull; - case DeleteBehavior.Cascade: - return ReferentialAction.Cascade; - case DeleteBehavior.NoAction: - case DeleteBehavior.ClientNoAction: - return ReferentialAction.NoAction; - case DeleteBehavior.Restrict: - case DeleteBehavior.ClientSetNull: - case DeleteBehavior.ClientCascade: - return ReferentialAction.Restrict; - default: - throw new NotSupportedException(deleteBehavior.ToString()); - } - } + DeleteBehavior.SetNull => ReferentialAction.SetNull, + DeleteBehavior.Cascade => ReferentialAction.Cascade, + DeleteBehavior.NoAction or DeleteBehavior.ClientNoAction => ReferentialAction.NoAction, + DeleteBehavior.Restrict or DeleteBehavior.ClientSetNull or DeleteBehavior.ClientCascade => ReferentialAction.Restrict, + _ => throw new NotSupportedException(deleteBehavior.ToString()), + }; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs index bbfd296fcbb..9371f5c2cde 100644 --- a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs +++ b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs @@ -224,5 +224,10 @@ public static class RelationalAnnotationNames /// The name for the annotation that contains table-specific facet overrides. /// public const string RelationalOverrides = Prefix + "RelationalOverrides"; + + /// + /// The name for relational model dependencies annotation. + /// + public const string ModelDependencies = Prefix + "ModelDependencies"; } } diff --git a/src/EFCore.Relational/Migrations/HistoryRepository.cs b/src/EFCore.Relational/Migrations/HistoryRepository.cs index e39f5e8a7a6..9b2bdd4996e 100644 --- a/src/EFCore.Relational/Migrations/HistoryRepository.cs +++ b/src/EFCore.Relational/Migrations/HistoryRepository.cs @@ -109,7 +109,7 @@ private IModel EnsureModel() x.ToTable(TableName, TableSchema); }); - _model = modelBuilder.FinalizeModel(); + _model = Dependencies.ModelRuntimeInitializer.Initialize(modelBuilder.FinalizeModel(), validationLogger: null); } return _model; diff --git a/src/EFCore.Relational/Migrations/HistoryRepositoryDependencies.cs b/src/EFCore.Relational/Migrations/HistoryRepositoryDependencies.cs index a70b2832bb4..c450314a070 100644 --- a/src/EFCore.Relational/Migrations/HistoryRepositoryDependencies.cs +++ b/src/EFCore.Relational/Migrations/HistoryRepositoryDependencies.cs @@ -70,7 +70,7 @@ public sealed record HistoryRepositoryDependencies /// doing so can result in application failures when updating to a new Entity Framework Core release. /// /// -#pragma warning disable CS0612 // Type or member is obsolete +#pragma warning disable CS0618 // Type or member is obsolete [EntityFrameworkInternal] public HistoryRepositoryDependencies( [NotNull] IRelationalDatabaseCreator databaseCreator, @@ -84,6 +84,7 @@ public HistoryRepositoryDependencies( [NotNull] ModelDependencies modelDependencies, [NotNull] IRelationalTypeMappingSource typeMappingSource, [NotNull] ICurrentDbContext currentContext, + [NotNull] IModelRuntimeInitializer modelRuntimeInitializer, [NotNull] IDiagnosticsLogger modelLogger, [NotNull] IDiagnosticsLogger commandLogger) { @@ -98,6 +99,7 @@ public HistoryRepositoryDependencies( Check.NotNull(modelDependencies, nameof(modelDependencies)); Check.NotNull(typeMappingSource, nameof(typeMappingSource)); Check.NotNull(currentContext, nameof(currentContext)); + Check.NotNull(modelRuntimeInitializer, nameof(modelRuntimeInitializer)); Check.NotNull(modelLogger, nameof(modelLogger)); Check.NotNull(commandLogger, nameof(commandLogger)); @@ -112,10 +114,11 @@ public HistoryRepositoryDependencies( ModelDependencies = modelDependencies; TypeMappingSource = typeMappingSource; CurrentContext = currentContext; + ModelRuntimeInitializer = modelRuntimeInitializer; ModelLogger = modelDependencies.Logger; CommandLogger = commandLogger; } -#pragma warning restore CS0612 // Type or member is obsolete +#pragma warning restore CS0618 // Type or member is obsolete /// /// The database creator. @@ -172,10 +175,15 @@ public HistoryRepositoryDependencies( /// public ICurrentDbContext CurrentContext { get; [param: NotNull] init; } + /// + /// The model runtime initializer + /// + public IModelRuntimeInitializer ModelRuntimeInitializer { get; [param: NotNull] init; } + /// /// The model logger /// - [Obsolete] + [Obsolete("This is contained in ModelDependencies now.")] public IDiagnosticsLogger ModelLogger { get; [param: NotNull] init; } /// diff --git a/src/EFCore.Relational/Migrations/Internal/Migrator.cs b/src/EFCore.Relational/Migrations/Internal/Migrator.cs index 506ecc6bb98..5ac8170e1fc 100644 --- a/src/EFCore.Relational/Migrations/Internal/Migrator.cs +++ b/src/EFCore.Relational/Migrations/Internal/Migrator.cs @@ -11,8 +11,6 @@ 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.Storage; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.Extensions.DependencyInjection; @@ -44,7 +42,7 @@ public class Migrator : IMigrator private readonly IRelationalConnection _connection; private readonly ISqlGenerationHelper _sqlGenerationHelper; private readonly ICurrentDbContext _currentContext; - private readonly IConventionSetBuilder _conventionSetBuilder; + private readonly IModelRuntimeInitializer _modelRuntimeInitializer; private readonly IDiagnosticsLogger _logger; private readonly IDiagnosticsLogger _commandLogger; private readonly string _activeProvider; @@ -65,7 +63,7 @@ public Migrator( [NotNull] IRelationalConnection connection, [NotNull] ISqlGenerationHelper sqlGenerationHelper, [NotNull] ICurrentDbContext currentContext, - [NotNull] IConventionSetBuilder conventionSetBuilder, + [NotNull] IModelRuntimeInitializer modelRuntimeInitializer, [NotNull] IDiagnosticsLogger logger, [NotNull] IDiagnosticsLogger commandLogger, [NotNull] IDatabaseProvider databaseProvider) @@ -79,7 +77,7 @@ public Migrator( Check.NotNull(connection, nameof(connection)); Check.NotNull(sqlGenerationHelper, nameof(sqlGenerationHelper)); Check.NotNull(currentContext, nameof(currentContext)); - Check.NotNull(conventionSetBuilder, nameof(conventionSetBuilder)); + Check.NotNull(modelRuntimeInitializer, nameof(modelRuntimeInitializer)); Check.NotNull(logger, nameof(logger)); Check.NotNull(commandLogger, nameof(commandLogger)); Check.NotNull(databaseProvider, nameof(databaseProvider)); @@ -93,7 +91,7 @@ public Migrator( _connection = connection; _sqlGenerationHelper = sqlGenerationHelper; _currentContext = currentContext; - _conventionSetBuilder = conventionSetBuilder; + _modelRuntimeInitializer = modelRuntimeInitializer; _logger = logger; _commandLogger = commandLogger; _activeProvider = databaseProvider.Name; @@ -493,34 +491,19 @@ protected virtual IReadOnlyList GenerateDownSql( _historyRepository.GetDeleteScript(migration.GetId())); return _migrationsSqlGenerator - .Generate(migration.DownOperations, FinalizeModel(previousMigration?.TargetModel), options) + .Generate(migration.DownOperations, previousMigration == null ? null : FinalizeModel(previousMigration.TargetModel), options) .Concat(new[] { new MigrationCommand(deleteCommand, _currentContext.Context, _commandLogger) }) .ToList(); } private IModel FinalizeModel(IModel model) { - if (model is IConventionModel conventionModel) + if (model is IMutableModel mutableModel) { - var conventionSet = _conventionSetBuilder.CreateConventionSet(); - - var typeMappingConvention = conventionSet.ModelFinalizingConventions.OfType().FirstOrDefault(); - if (typeMappingConvention != null) - { - typeMappingConvention.ProcessModelFinalizing(conventionModel.Builder, null); - } - - var relationalModelConvention = - conventionSet.ModelFinalizedConventions.OfType().FirstOrDefault(); - if (relationalModelConvention != null) - { - relationalModelConvention.ProcessModelFinalized(conventionModel); - } - - return conventionModel.FinalizeModel(); + model = mutableModel.FinalizeModel(); } - return model; + return _modelRuntimeInitializer.Initialize(model, validationLogger: null); } } } diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index 64e2006bce7..cce1c1d5909 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -130,6 +130,7 @@ public static string ConflictingRowValuesSensitive([CanBeNull] object? firstEnti /// /// The database model hasn't been initialized. The model needs to be finalized and processed with 'RelationalModelConvention' before the database model can be accessed. /// + [Obsolete] public static string DatabaseModelMissing => GetString("DatabaseModelMissing"); @@ -720,12 +721,6 @@ public static string LastUsedWithoutOrderBy([CanBeNull] object? method) GetString("LastUsedWithoutOrderBy", nameof(method)), method); - /// - /// The 'Down' method for this migration has not been implemented. Both the 'Up' abd 'Down' methods must be implemented to support reverting migrations. - /// - public static string MigrationDownMissing - => GetString("MigrationDownMissing"); - /// /// The entity type '{entityType}' is mapped to the DbFunction named '{functionName}', but no DbFunction with that name was found in the model. Ensure that the entity type mapping is configured using the model name of a function in the model. /// @@ -742,6 +737,12 @@ public static string MethodOnNonTPHRootNotSupported([CanBeNull] object? methodNa GetString("MethodOnNonTPHRootNotSupported", nameof(methodName), nameof(entityType)), methodName, entityType); + /// + /// The 'Down' method for this migration has not been implemented. Both the 'Up' abd 'Down' methods must be implemented to support reverting migrations. + /// + public static string MigrationDownMissing + => GetString("MigrationDownMissing"); + /// /// The migration '{migrationName}' was not found. /// @@ -753,7 +754,7 @@ public static string MigrationNotFound([CanBeNull] object? migrationName) /// /// SQL generation for the operation '{operation}' is not supported by the current database provider. Database providers must implement the appropriate method in 'MigrationsSqlGenerator' to support this operation. /// - public static string MigrationSqlGenerationMissing([CanBeNull] object operation) + public static string MigrationSqlGenerationMissing([CanBeNull] object? operation) => string.Format( GetString("MigrationSqlGenerationMissing", nameof(operation)), operation); diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index ef22fb61b71..3c051fac652 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -162,6 +162,7 @@ The database model hasn't been initialized. The model needs to be finalized and processed with 'RelationalModelConvention' before the database model can be accessed. + Obsolete There is no property mapped to the column '{table}.{column}' which is used in a data operation. Either add a property mapped to this column, or specify the column types in the data operation. diff --git a/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs b/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs index 69c00494f6b..319d1556a1e 100644 --- a/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs +++ b/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs @@ -200,12 +200,6 @@ protected override CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo) /// The type mapping, or if none was found. public override CoreTypeMapping? FindMapping(IProperty property) { - var mapping = property.FindRelationalTypeMapping(); - if (mapping != null) - { - return mapping; - } - var principals = property.FindPrincipals(); string? storeTypeName = null; diff --git a/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs index 698c8ca8dca..60993785704 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs @@ -6,8 +6,10 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; #nullable enable @@ -365,6 +367,12 @@ public static SqlServerValueGenerationStrategy GetValueGenerationStrategy([NotNu public static SqlServerValueGenerationStrategy GetValueGenerationStrategy( [NotNull] this IProperty property, in StoreObjectIdentifier storeObject) + => GetValueGenerationStrategy(property, storeObject, null); + + internal static SqlServerValueGenerationStrategy GetValueGenerationStrategy( + [NotNull] this IProperty property, + in StoreObjectIdentifier storeObject, + ITypeMappingSource typeMappingSource) { var annotation = property.FindAnnotation(SqlServerAnnotationNames.ValueGenerationStrategy); if (annotation != null) @@ -391,7 +399,7 @@ public static SqlServerValueGenerationStrategy GetValueGenerationStrategy( return SqlServerValueGenerationStrategy.None; } - return GetDefaultValueGenerationStrategy(property); + return GetDefaultValueGenerationStrategy(property, storeObject, typeMappingSource); } private static SqlServerValueGenerationStrategy GetDefaultValueGenerationStrategy(IProperty property) @@ -410,6 +418,25 @@ private static SqlServerValueGenerationStrategy GetDefaultValueGenerationStrateg : SqlServerValueGenerationStrategy.None; } + private static SqlServerValueGenerationStrategy GetDefaultValueGenerationStrategy( + IProperty property, + in StoreObjectIdentifier storeObject, + ITypeMappingSource typeMappingSource) + { + var modelStrategy = property.DeclaringEntityType.Model.GetValueGenerationStrategy(); + + if (modelStrategy == SqlServerValueGenerationStrategy.SequenceHiLo + && IsCompatibleWithValueGeneration(property, storeObject, typeMappingSource)) + { + return SqlServerValueGenerationStrategy.SequenceHiLo; + } + + return modelStrategy == SqlServerValueGenerationStrategy.IdentityColumn + && IsCompatibleWithValueGeneration(property, storeObject, typeMappingSource) + ? SqlServerValueGenerationStrategy.IdentityColumn + : SqlServerValueGenerationStrategy.None; + } + /// /// Sets the to use for the property. /// @@ -491,5 +518,20 @@ public static bool IsCompatibleWithValueGeneration([NotNull] IProperty property) ?? property.FindTypeMapping()?.Converter) == null; } + + private static bool IsCompatibleWithValueGeneration( + [NotNull] IProperty property, + in StoreObjectIdentifier storeObject, + ITypeMappingSource typeMappingSource) + { + var type = property.ClrType; + + return (type.IsInteger() + || type == typeof(decimal)) + && (property.GetValueConverter() + ?? (property.FindRelationalTypeMapping(storeObject) + ?? typeMappingSource?.FindMapping(property))?.Converter) + == null; + } } } diff --git a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs index 39a0886f218..128d9a93a27 100644 --- a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs +++ b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs @@ -74,8 +74,7 @@ protected virtual void ValidateDecimalColumns( { foreach (IConventionProperty property in model.GetEntityTypes() .SelectMany(t => t.GetDeclaredProperties()) - .Where( - p => p.ClrType.UnwrapNullableType() == typeof(decimal) + .Where(p => p.ClrType.UnwrapNullableType() == typeof(decimal) && !p.IsForeignKey())) { var valueConverterConfigurationSource = property.GetValueConverterConfigurationSource(); @@ -116,6 +115,7 @@ protected virtual void ValidateByteIdentityMapping( { foreach (var entityType in model.GetEntityTypes()) { + // TODO: Validate this per table foreach (var property in entityType.GetDeclaredProperties() .Where( p => p.ClrType.UnwrapNullableType() == typeof(byte) diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerStoreGenerationConvention.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerStoreGenerationConvention.cs index 7671ceabdb2..3c7672d6ea5 100644 --- a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerStoreGenerationConvention.cs +++ b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerStoreGenerationConvention.cs @@ -5,7 +5,6 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.SqlServer.Extensions.Internal; -using Microsoft.EntityFrameworkCore.SqlServer.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; #nullable enable @@ -104,7 +103,7 @@ protected override void Validate(IConventionProperty property, in StoreObjectIde { if (property.GetValueGenerationStrategyConfigurationSource() != null) { - var generationStrategy = property.GetValueGenerationStrategy(storeObject); + var generationStrategy = property.GetValueGenerationStrategy(storeObject, Dependencies.TypeMappingSource); if (generationStrategy == SqlServerValueGenerationStrategy.None) { base.Validate(property, storeObject); diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationConvention.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationConvention.cs index 9b42f0568b2..81bca81262e 100644 --- a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationConvention.cs +++ b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationConvention.cs @@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Storage; #nullable enable @@ -68,7 +69,10 @@ public override void ProcessPropertyAnnotationChanged( return null; } - return GetValueGenerated(property, StoreObjectIdentifier.Table(tableName, property.DeclaringEntityType.GetSchema())); + return GetValueGenerated( + property, + StoreObjectIdentifier.Table(tableName, property.DeclaringEntityType.GetSchema()), + Dependencies.TypeMappingSource); } /// @@ -82,5 +86,14 @@ public override void ProcessPropertyAnnotationChanged( ?? (property.GetValueGenerationStrategy(storeObject) != SqlServerValueGenerationStrategy.None ? ValueGenerated.OnAdd : (ValueGenerated?)null); + + private ValueGenerated? GetValueGenerated( + [NotNull] IProperty property, + in StoreObjectIdentifier storeObject, + ITypeMappingSource typeMappingSource) + => RelationalValueGenerationConvention.GetValueGenerated(property, storeObject) + ?? (property.GetValueGenerationStrategy(storeObject, typeMappingSource) != SqlServerValueGenerationStrategy.None + ? ValueGenerated.OnAdd + : (ValueGenerated?)null); } } diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationStrategyConvention.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationStrategyConvention.cs index 54b844c885e..32d85583843 100644 --- a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationStrategyConvention.cs +++ b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationStrategyConvention.cs @@ -26,8 +26,11 @@ public SqlServerValueGenerationStrategyConvention( [NotNull] ProviderConventionSetBuilderDependencies dependencies, [NotNull] RelationalConventionSetBuilderDependencies relationalDependencies) { + Dependencies = dependencies; } + private ProviderConventionSetBuilderDependencies Dependencies { get; } + /// /// Called after a model is initialized. /// @@ -54,7 +57,7 @@ public virtual void ProcessModelFinalizing( if (table != null) { var storeObject = StoreObjectIdentifier.Table(table, entityType.GetSchema()); - strategy = property.GetValueGenerationStrategy(storeObject); + strategy = property.GetValueGenerationStrategy(storeObject, Dependencies.TypeMappingSource); if (strategy == SqlServerValueGenerationStrategy.None && !IsStrategyNoneNeeded(property, storeObject)) { @@ -67,7 +70,7 @@ public virtual void ProcessModelFinalizing( if (view != null) { var storeObject = StoreObjectIdentifier.View(view, entityType.GetViewSchema()); - strategy = property.GetValueGenerationStrategy(storeObject); + strategy = property.GetValueGenerationStrategy(storeObject, Dependencies.TypeMappingSource); if (strategy == SqlServerValueGenerationStrategy.None && !IsStrategyNoneNeeded(property, storeObject)) { @@ -84,7 +87,7 @@ public virtual void ProcessModelFinalizing( } } - static bool IsStrategyNoneNeeded(IProperty property, StoreObjectIdentifier storeObject) + bool IsStrategyNoneNeeded(IProperty property, StoreObjectIdentifier storeObject) { if (property.ValueGenerated == ValueGenerated.OnAdd && property.GetDefaultValue(storeObject) == null @@ -92,7 +95,9 @@ static bool IsStrategyNoneNeeded(IProperty property, StoreObjectIdentifier store && property.GetComputedColumnSql(storeObject) == null && property.DeclaringEntityType.Model.GetValueGenerationStrategy() == SqlServerValueGenerationStrategy.IdentityColumn) { - var providerClrType = (property.GetValueConverter() ?? property.FindRelationalTypeMapping(storeObject)?.Converter) + var providerClrType = (property.GetValueConverter() + ?? (property.FindRelationalTypeMapping(storeObject) + ?? Dependencies.TypeMappingSource.FindMapping(property))?.Converter) ?.ProviderClrType.UnwrapNullableType(); return providerClrType != null diff --git a/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs b/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs index a742a1379a3..19b4733e0d3 100644 --- a/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs +++ b/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs @@ -52,12 +52,13 @@ public virtual ResultSetMapping AppendBulkInsertOperation( IReadOnlyList modificationCommands, int commandPosition) { + var table = StoreObjectIdentifier.Table(modificationCommands[0].TableName, modificationCommands[0].Schema); if (modificationCommands.Count == 1 && modificationCommands[0].ColumnModifications.All( o => !o.IsKey || !o.IsRead - || o.Property?.GetValueGenerationStrategy() == SqlServerValueGenerationStrategy.IdentityColumn)) + || o.Property?.GetValueGenerationStrategy(table) == SqlServerValueGenerationStrategy.IdentityColumn)) { return AppendInsertOperation(commandStringBuilder, modificationCommands[0], commandPosition); } @@ -68,7 +69,7 @@ public virtual ResultSetMapping AppendBulkInsertOperation( var defaultValuesOnly = writeOperations.Count == 0; var nonIdentityOperations = modificationCommands[0].ColumnModifications - .Where(o => o.Property?.GetValueGenerationStrategy() != SqlServerValueGenerationStrategy.IdentityColumn) + .Where(o => o.Property?.GetValueGenerationStrategy(table) != SqlServerValueGenerationStrategy.IdentityColumn) .ToList(); if (defaultValuesOnly) diff --git a/src/EFCore/ChangeTracking/Internal/DependentsMapFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/DependentsMapFactoryFactory.cs deleted file mode 100644 index 2de2916eb70..00000000000 --- a/src/EFCore/ChangeTracking/Internal/DependentsMapFactoryFactory.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Reflection; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal -{ - /// - /// 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 class DependentsMapFactoryFactory - { - /// - /// 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 Func Create([NotNull] IForeignKey foreignKey) - => (Func)typeof(DependentsMapFactoryFactory).GetTypeInfo() - .GetDeclaredMethod(nameof(CreateFactory)) - .MakeGenericMethod(foreignKey.PrincipalKey.GetKeyType()) - .Invoke(null, new object[] { foreignKey }); - - [UsedImplicitly] - private static Func CreateFactory(IForeignKey foreignKey) - { - var principalKeyValueFactory = foreignKey.PrincipalKey.GetPrincipalKeyValueFactory(); - var dependentKeyValueFactory = foreignKey.GetDependentKeyValueFactory(); - - return () => new DependentsMap(foreignKey, principalKeyValueFactory, dependentKeyValueFactory); - } - } -} diff --git a/src/EFCore/ChangeTracking/Internal/ValueGenerationManager.cs b/src/EFCore/ChangeTracking/Internal/ValueGenerationManager.cs index 95603dfb2e9..32582267b9e 100644 --- a/src/EFCore/ChangeTracking/Internal/ValueGenerationManager.cs +++ b/src/EFCore/ChangeTracking/Internal/ValueGenerationManager.cs @@ -33,8 +33,6 @@ public class ValueGenerationManager : IValueGenerationManager private readonly IKeyPropagator _keyPropagator; private readonly IDiagnosticsLogger _logger; private readonly ILoggingOptions _loggingOptions; - private readonly Dictionary> _entityTypePropagatingPropertiesMap = new(); - private readonly Dictionary> _entityTypeGeneratingPropertiesMap = new(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -163,43 +161,11 @@ public virtual async Task GenerateAsync( } } - private List FindCandidatePropagatingProperties(InternalEntityEntry entry) - { - if (!_entityTypePropagatingPropertiesMap.TryGetValue(entry.EntityType, out var candidateProperties)) - { - candidateProperties = new List(); - foreach (var property in entry.EntityType.GetProperties()) - { - if (property.IsForeignKey()) - { - candidateProperties.Add(property); - } - } - - _entityTypePropagatingPropertiesMap[entry.EntityType] = candidateProperties; - } - - return candidateProperties; - } + private IReadOnlyList FindCandidatePropagatingProperties(InternalEntityEntry entry) + => entry.EntityType.GetPropagatingProperties(); - private List FindCandidateGeneratingProperties(InternalEntityEntry entry) - { - if (!_entityTypeGeneratingPropertiesMap.TryGetValue(entry.EntityType, out var candidateProperties)) - { - candidateProperties = new List(); - foreach (var property in entry.EntityType.GetProperties()) - { - if (property.RequiresValueGenerator()) - { - candidateProperties.Add(property); - } - } - - _entityTypeGeneratingPropertiesMap[entry.EntityType] = candidateProperties; - } - - return candidateProperties; - } + private IReadOnlyList FindCandidateGeneratingProperties(InternalEntityEntry entry) + => entry.EntityType.GetGeneratingProperties(); private ValueGenerator GetValueGenerator(InternalEntityEntry entry, IProperty property) => _valueGeneratorSelector.Select( diff --git a/src/EFCore/Extensions/PropertyExtensions.cs b/src/EFCore/Extensions/PropertyExtensions.cs index 3931a0f811f..062c47a66f8 100644 --- a/src/EFCore/Extensions/PropertyExtensions.cs +++ b/src/EFCore/Extensions/PropertyExtensions.cs @@ -37,8 +37,7 @@ public static CoreTypeMapping GetTypeMapping([NotNull] this IProperty property) var mapping = ((Property)property).TypeMapping; if (mapping == null) { - throw new InvalidOperationException( - CoreStrings.ModelNotFinalized(nameof(GetTypeMapping))); + throw new InvalidOperationException(CoreStrings.ModelNotFinalized(nameof(GetTypeMapping))); } return mapping; @@ -359,7 +358,6 @@ public static PropertySaveBehavior GetAfterSaveBehavior([NotNull] this IProperty /// A new equality comparer. public static IEqualityComparer CreateKeyEqualityComparer([NotNull] this IProperty property) { - // TODO-NULLABLE: #22031 var comparer = property.GetKeyValueComparer()!; return comparer is IEqualityComparer nullableComparer diff --git a/src/EFCore/Infrastructure/Annotatable.cs b/src/EFCore/Infrastructure/Annotatable.cs index 21cf44a6981..40e80cc7eda 100644 --- a/src/EFCore/Infrastructure/Annotatable.cs +++ b/src/EFCore/Infrastructure/Annotatable.cs @@ -26,12 +26,7 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure public class Annotatable : IMutableAnnotatable { private SortedDictionary? _annotations; - - /// - /// Gets all annotations on the current object. - /// - public virtual IEnumerable GetAnnotations() - => _annotations?.Values ?? Enumerable.Empty(); + private SortedDictionary? _runtimeAnnotations; /// /// Indicates whether the current object is read-only. @@ -40,19 +35,36 @@ public virtual IEnumerable GetAnnotations() /// Runtime annotations cannot be changed when the object is not read-only. /// /// - protected virtual bool IsReadonly => false; + protected virtual bool IsReadOnly => false; /// /// Throws if the model is not read-only. /// - protected virtual void EnsureReadonly(bool shouldBeReadonly = true) + protected virtual void EnsureReadOnly() + { + if (!IsReadOnly) + { + throw new InvalidOperationException(CoreStrings.ModelMutable); + } + } + + /// + /// Throws if the model is read-only. + /// + protected virtual void EnsureMutable() { - if (!shouldBeReadonly && IsReadonly) + if (IsReadOnly) { throw new InvalidOperationException(CoreStrings.ModelReadOnly); } } + /// + /// Gets all annotations on the current object. + /// + public virtual IEnumerable GetAnnotations() + => _annotations?.Values ?? Enumerable.Empty(); + /// /// Adds an annotation to this object. Throws if an annotation with the specified name already exists. /// @@ -117,7 +129,7 @@ public virtual void SetAnnotation(string name, object? value) [NotNull] Annotation annotation, [CanBeNull] Annotation? oldAnnotation) { - EnsureReadonly(false); + EnsureMutable(); if (_annotations == null) { @@ -168,7 +180,7 @@ public virtual void SetAnnotation(string name, object? value) public virtual Annotation? RemoveAnnotation([NotNull] string name) { Check.NotNull(name, nameof(name)); - EnsureReadonly(false); + EnsureMutable(); var annotation = FindAnnotation(name); if (annotation == null) @@ -223,6 +235,136 @@ public virtual object? this[string name] protected virtual Annotation CreateAnnotation([NotNull] string name, [CanBeNull] object? value) => new(name, value); + /// + /// Gets all runtime annotations on the current object. + /// + public virtual IEnumerable GetRuntimeAnnotations() + => _runtimeAnnotations?.Values ?? Enumerable.Empty(); + + /// + /// Adds a runtime annotation to this object. Throws if an annotation with the specified name already exists. + /// + /// The key of the annotation to be added. + /// The value to be stored in the annotation. + /// The newly added annotation. + public virtual Annotation AddRuntimeAnnotation([NotNull] string name, [CanBeNull] object? value) + { + Check.NotEmpty(name, nameof(name)); + + var annotation = CreateRuntimeAnnotation(name, value); + + return AddRuntimeAnnotation(name, annotation); + } + + /// + /// Adds a runtime annotation to this object. Throws if an annotation with the specified name already exists. + /// + /// The key of the annotation to be added. + /// The annotation to be added. + /// The added annotation. + protected virtual Annotation AddRuntimeAnnotation([NotNull] string name, [NotNull] Annotation annotation) + { + if (FindRuntimeAnnotation(name) != null) + { + throw new InvalidOperationException(CoreStrings.DuplicateAnnotation(name, ToString())); + } + + SetRuntimeAnnotation(name, annotation, oldAnnotation: null); + + return annotation; + } + + /// + /// Sets the runtime annotation stored under the given key. Overwrites the existing annotation if an + /// annotation with the specified name already exists. + /// + /// The key of the annotation to be added. + /// The value to be stored in the annotation. + public virtual Annotation SetRuntimeAnnotation([NotNull] string name, [CanBeNull] object? value) + { + var oldAnnotation = FindRuntimeAnnotation(name); + if (oldAnnotation != null + && Equals(oldAnnotation.Value, value)) + { + return oldAnnotation; + } + + return SetRuntimeAnnotation(name, CreateRuntimeAnnotation(name, value), oldAnnotation); + } + + /// + /// Sets the runtime annotation stored under the given key. Overwrites the existing annotation if an + /// annotation with the specified name already exists. + /// + /// The key of the annotation to be added. + /// The annotation to be set. + /// The annotation being replaced. + /// The annotation that was set. + protected virtual Annotation SetRuntimeAnnotation( + [NotNull] string name, + [NotNull] Annotation annotation, + [CanBeNull] Annotation? oldAnnotation) + { + EnsureReadOnly(); + + if (_runtimeAnnotations == null) + { + _runtimeAnnotations = new SortedDictionary(); + } + + _runtimeAnnotations[name] = annotation; + + return annotation; + } + + /// + /// Gets the runtime annotation with the given name, returning if it does not exist. + /// + /// The key of the annotation to find. + /// + /// The existing annotation if an annotation with the specified name already exists. Otherwise, . + /// + public virtual Annotation? FindRuntimeAnnotation([NotNull] string name) + { + Check.NotEmpty(name, nameof(name)); + + return _runtimeAnnotations == null + ? null + : _runtimeAnnotations.TryGetValue(name, out var annotation) + ? annotation + : null; + } + + /// + /// Removes the given runtime annotation from this object. + /// + /// The annotation to remove. + /// The annotation that was removed. + public virtual Annotation? RemoveRuntimeAnnotation([NotNull] string name) + { + Check.NotNull(name, nameof(name)); + EnsureReadOnly(); + + var annotation = FindRuntimeAnnotation(name); + if (annotation == null) + { + return null; + } + + _runtimeAnnotations!.Remove(name); + + return annotation; + } + + /// + /// Creates a new runtime annotation. + /// + /// The key of the annotation. + /// The value to be stored in the annotation. + /// The newly created annotation. + protected virtual Annotation CreateRuntimeAnnotation([NotNull] string name, [CanBeNull] object? value) + => new Annotation(name, value); + /// [DebuggerStepThrough] IEnumerable IAnnotatable.GetAnnotations() @@ -242,5 +384,24 @@ IAnnotation IMutableAnnotatable.AddAnnotation(string name, object? value) [DebuggerStepThrough] IAnnotation? IMutableAnnotatable.RemoveAnnotation(string name) => RemoveAnnotation(name); + + /// + IEnumerable IAnnotatable.GetRuntimeAnnotations() + => GetRuntimeAnnotations(); + + /// + IAnnotation? IAnnotatable.FindRuntimeAnnotation(string name) + => FindRuntimeAnnotation(name); + + /// + IAnnotation IAnnotatable.AddRuntimeAnnotation(string name, object? value) + => AddRuntimeAnnotation(name, value); + + /// + IAnnotation? IAnnotatable.RemoveRuntimeAnnotation(string name) + => RemoveRuntimeAnnotation(name); + + IAnnotation IAnnotatable.SetRuntimeAnnotation(string name, object? value) + => SetRuntimeAnnotation(name, value); } } diff --git a/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs b/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs index dab71056999..f3a35223a63 100644 --- a/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs +++ b/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs @@ -78,6 +78,7 @@ public static readonly IDictionary CoreServices { typeof(IModelCustomizer), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IModelCacheKeyFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IModelSource), new ServiceCharacteristics(ServiceLifetime.Singleton) }, + { typeof(IModelRuntimeInitializer), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IInternalEntityEntryFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IInternalEntityEntrySubscriber), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IEntityEntryGraphIterator), new ServiceCharacteristics(ServiceLifetime.Singleton) }, @@ -220,6 +221,7 @@ public virtual EntityFrameworkServicesBuilder TryAddCoreServices() TryAdd(); TryAdd(p => ScopedLoggerFactory.Create(p, null)); TryAdd(); + TryAdd(); TryAdd(); TryAdd(); TryAdd(); @@ -298,6 +300,8 @@ public virtual EntityFrameworkServicesBuilder TryAddCoreServices() .AddDependencySingleton() .AddDependencySingleton() .AddDependencySingleton() + .AddDependencySingleton() + .AddDependencySingleton() .AddDependencyScoped() .AddDependencyScoped() .AddDependencyScoped() diff --git a/src/EFCore/Infrastructure/IAnnotatable.cs b/src/EFCore/Infrastructure/IAnnotatable.cs index adc1aaa15e8..c3d065ce11b 100644 --- a/src/EFCore/Infrastructure/IAnnotatable.cs +++ b/src/EFCore/Infrastructure/IAnnotatable.cs @@ -20,7 +20,7 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure public interface IAnnotatable { /// - /// Gets the value annotation with the given name, returning if it does not exist. + /// Gets the value of the annotation with the given name, returning if it does not exist. /// /// The name of the annotation to find. /// @@ -41,5 +41,54 @@ public interface IAnnotatable /// Gets all annotations on the current object. /// IEnumerable GetAnnotations(); + + /// + /// Gets the runtime annotation with the given name, returning if it does not exist. + /// + /// The name of the annotation to find. + /// + /// The existing runtime annotation if an annotation with the specified name already exists. Otherwise, . + /// + IAnnotation? FindRuntimeAnnotation([NotNull] string name); + + /// + /// Gets the value of the runtime annotation with the given name, returning if it does not exist. + /// + /// The name of the annotation to find. + /// + /// The value of the existing runtime annotation if an annotation with the specified name already exists. + /// Otherwise, . + /// + object? FindRuntimeAnnotationValue([NotNull] string name) => + FindRuntimeAnnotation(name)?.Value; + + /// + /// Gets all the runtime annotations on the current object. + /// + IEnumerable GetRuntimeAnnotations(); + + /// + /// Adds a runtime annotation to this object. Throws if an annotation with the specified name already exists. + /// + /// The name of the annotation to be added. + /// The value to be stored in the annotation. + /// The newly added annotation. + IAnnotation AddRuntimeAnnotation([NotNull] string name, [CanBeNull] object? value); + + /// + /// Sets the runtime annotation stored under the given key. Overwrites the existing annotation if an + /// annotation with the specified name already exists. + /// + /// The name of the annotation to be added. + /// The value to be stored in the annotation. + /// The newly added annotation. + IAnnotation SetRuntimeAnnotation([NotNull] string name, [CanBeNull] object? value); + + /// + /// Removes the given runtime annotation from this object. + /// + /// The name of the annotation to remove. + /// The annotation that was removed. + IAnnotation? RemoveRuntimeAnnotation([NotNull] string name); } } diff --git a/src/EFCore/Internal/IModelCreationDependencies.cs b/src/EFCore/Infrastructure/IModelCreationDependencies.cs similarity index 52% rename from src/EFCore/Internal/IModelCreationDependencies.cs rename to src/EFCore/Infrastructure/IModelCreationDependencies.cs index 325c5a94f03..c3f090a58dd 100644 --- a/src/EFCore/Internal/IModelCreationDependencies.cs +++ b/src/EFCore/Infrastructure/IModelCreationDependencies.cs @@ -1,11 +1,13 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.Extensions.DependencyInjection; -namespace Microsoft.EntityFrameworkCore.Internal +#nullable enable + +namespace Microsoft.EntityFrameworkCore.Infrastructure { /// /// @@ -24,27 +26,28 @@ namespace Microsoft.EntityFrameworkCore.Internal public interface IModelCreationDependencies { /// - /// 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. + /// The model source. /// public IModelSource ModelSource { get; } /// - /// 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. + /// The convention set to use when creating the model. /// public IConventionSetBuilder ConventionSetBuilder { get; } /// - /// 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. + /// The dependencies object for the model. /// public ModelDependencies ModelDependencies { get; } + + /// + /// The model runtime initializer that will be used after the model building is finished. + /// + public IModelRuntimeInitializer ModelRuntimeInitializer { get; } + + /// + /// The validation logger. + /// + public IDiagnosticsLogger ValidationLogger { get; } } } diff --git a/src/EFCore/Infrastructure/IModelRuntimeInitializer.cs b/src/EFCore/Infrastructure/IModelRuntimeInitializer.cs new file mode 100644 index 00000000000..497991bf3e2 --- /dev/null +++ b/src/EFCore/Infrastructure/IModelRuntimeInitializer.cs @@ -0,0 +1,39 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.Extensions.DependencyInjection; + +#nullable enable + +namespace Microsoft.EntityFrameworkCore.Infrastructure +{ + /// + /// + /// Initializes a with the runtime dependencies. + /// This is typically implemented by database providers to ensure that any + /// runtime dependencies specific to their database are used. + /// + /// + /// This interface is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The service lifetime is . This means a single instance + /// is used by many instances. The implementation must be thread-safe. + /// This service cannot depend on services registered as . + /// + /// + public interface IModelRuntimeInitializer + { + /// + /// Validates and initializes the given model with runtime dependencies. + /// + /// The model to initialize. + /// The validation logger. + /// The initialized model. + IModel Initialize([NotNull] IModel model, [NotNull] IDiagnosticsLogger? validationLogger); + } +} diff --git a/src/EFCore/Infrastructure/IModelSource.cs b/src/EFCore/Infrastructure/IModelSource.cs index f3bb3f2003f..fd582f69408 100644 --- a/src/EFCore/Infrastructure/IModelSource.cs +++ b/src/EFCore/Infrastructure/IModelSource.cs @@ -7,6 +7,8 @@ using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.Extensions.DependencyInjection; +#nullable enable + namespace Microsoft.EntityFrameworkCore.Infrastructure { /// @@ -32,7 +34,7 @@ public interface IModelSource /// The context the model is being produced for. /// The convention set to use when creating the model. /// The model to be used. - [Obsolete("Use the overload with ModelDependencies")] + [Obsolete("Use the overload with IModelCreationDependencies")] IModel GetModel( [NotNull] DbContext context, [NotNull] IConventionSetBuilder conventionSetBuilder); @@ -44,9 +46,20 @@ IModel GetModel( /// The convention set to use when creating the model. /// The dependencies object for the model. /// The model to be used. + [Obsolete("Use the overload with IModelCreationDependencies")] IModel GetModel( [NotNull] DbContext context, [NotNull] IConventionSetBuilder conventionSetBuilder, [NotNull] ModelDependencies modelDependencies); + + /// + /// Gets the model to be used. + /// + /// The context the model is being produced for. + /// The dependencies object used during the creation of the model. + /// The model to be used. + IModel GetModel( + [NotNull] DbContext context, + [NotNull] IModelCreationDependencies modelCreationDependencies); } } diff --git a/src/EFCore/Infrastructure/ModelCustomizerDependencies.cs b/src/EFCore/Infrastructure/ModelCustomizerDependencies.cs index b3a3a90d2a6..97d08940a6d 100644 --- a/src/EFCore/Infrastructure/ModelCustomizerDependencies.cs +++ b/src/EFCore/Infrastructure/ModelCustomizerDependencies.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.Extensions.DependencyInjection; @@ -56,13 +57,16 @@ public ModelCustomizerDependencies([NotNull] IDbSetFinder setFinder) { Check.NotNull(setFinder, nameof(setFinder)); +#pragma warning disable CS0618 // Type or member is obsolete SetFinder = setFinder; +#pragma warning restore CS0618 // Type or member is obsolete } /// /// Gets the that will locate the properties /// on the derived context. /// + [Obsolete("This is part of ProviderConventionSetBuilderDependencies now")] public IDbSetFinder SetFinder { get; [param: NotNull] init; } } } diff --git a/src/EFCore/Infrastructure/ModelRuntimeInitializer.cs b/src/EFCore/Infrastructure/ModelRuntimeInitializer.cs new file mode 100644 index 00000000000..cf332d53840 --- /dev/null +++ b/src/EFCore/Infrastructure/ModelRuntimeInitializer.cs @@ -0,0 +1,80 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Utilities; +using Microsoft.Extensions.DependencyInjection; + +#nullable enable + +namespace Microsoft.EntityFrameworkCore.Infrastructure +{ + /// + /// + /// Initializes a with the runtime dependencies. + /// + /// + /// This type is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The service lifetime is . This means a single instance + /// is used by many instances. The implementation must be thread-safe. + /// This service cannot depend on services registered as . + /// + /// + public class ModelRuntimeInitializer : IModelRuntimeInitializer + { + /// + /// Creates a new instance. + /// + /// The dependencies to use. + public ModelRuntimeInitializer([NotNull] ModelRuntimeInitializerDependencies dependencies) + { + Check.NotNull(dependencies, nameof(dependencies)); + + Dependencies = dependencies; + } + + /// + /// The dependencies. + /// + protected virtual ModelRuntimeInitializerDependencies Dependencies { get; } + + /// + /// Validates and initializes the given model with runtime dependencies. + /// + /// The model to initialize. + /// The validation logger. + /// The initialized model. + public virtual IModel Initialize(IModel model, IDiagnosticsLogger? validationLogger) + { + if (model.SetModelDependencies(Dependencies.ModelDependencies)) + { + InitializeModel(model, preValidation: true); + if (validationLogger != null + && model is IMutableModel) + { + Dependencies.ModelValidator.Validate(model, validationLogger); + } + InitializeModel(model, preValidation: false); + } + + return model; + } + + /// + /// Initializes the given model with runtime dependencies. + /// + /// The model to initialize. + /// + /// indicates that only pre-validation initialization should be performed; + /// indicates that only post-validation initialization should be performed; + /// + protected virtual void InitializeModel([NotNull] IModel model, bool preValidation) + { + } + } +} diff --git a/src/EFCore/Infrastructure/ModelRuntimeInitializerDependencies.cs b/src/EFCore/Infrastructure/ModelRuntimeInitializerDependencies.cs new file mode 100644 index 00000000000..60bea6f40a2 --- /dev/null +++ b/src/EFCore/Infrastructure/ModelRuntimeInitializerDependencies.cs @@ -0,0 +1,78 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Utilities; +using Microsoft.Extensions.DependencyInjection; + +#nullable enable + +namespace Microsoft.EntityFrameworkCore.Infrastructure +{ + /// + /// + /// Service dependencies parameter class for + /// + /// + /// This type is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// Do not construct instances of this class directly from either provider or application code as the + /// constructor signature may change as new dependencies are added. Instead, use this type in + /// your constructor so that an instance will be created and injected automatically by the + /// dependency injection container. To create an instance with some dependent services replaced, + /// first resolve the object from the dependency injection container, then replace selected + /// services using the 'With...' methods. Do not call the constructor at any point in this process. + /// + /// + /// The service lifetime is . + /// This means a single instance of each service is used by many instances. + /// The implementation must be thread-safe. + /// This service cannot depend on services registered as . + /// + /// + public sealed record ModelRuntimeInitializerDependencies + { + /// + /// + /// Creates the service dependencies parameter object for a . + /// + /// + /// 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. + /// + /// + /// Do not call this constructor directly from either provider or application code as it may change + /// as new dependencies are added. Instead, use this type in your constructor so that an instance + /// will be created and injected automatically by the dependency injection container. To create + /// an instance with some dependent services replaced, first resolve the object from the dependency + /// injection container, then replace selected services using the 'With...' methods. Do not call + /// the constructor at any point in this process. + /// + /// + [EntityFrameworkInternal] + public ModelRuntimeInitializerDependencies( + [NotNull] SingletonModelDependencies singletonModelDependencies, + [NotNull] IModelValidator modelValidator) + { + Check.NotNull(singletonModelDependencies, nameof(singletonModelDependencies)); + Check.NotNull(modelValidator, nameof(modelValidator)); + + ModelDependencies = singletonModelDependencies; + ModelValidator = modelValidator; + } + + /// + /// The model runtime dependencies. + /// + public SingletonModelDependencies ModelDependencies { get; [param: NotNull] init; } + + /// + /// The model validator. + /// + public IModelValidator ModelValidator { get; [param: NotNull] init; } + } +} diff --git a/src/EFCore/Infrastructure/ModelSource.cs b/src/EFCore/Infrastructure/ModelSource.cs index 25aaf9199af..66ff8a0c723 100644 --- a/src/EFCore/Infrastructure/ModelSource.cs +++ b/src/EFCore/Infrastructure/ModelSource.cs @@ -53,7 +53,7 @@ public ModelSource([NotNull] ModelSourceDependencies dependencies) /// The context the model is being produced for. /// The convention set to use when creating the model. /// The model to be used. - [Obsolete("Use the overload with ModelDependencies")] + [Obsolete("Use the overload with IModelCreationDependencies")] public virtual IModel GetModel( DbContext context, IConventionSetBuilder conventionSetBuilder) @@ -83,6 +83,7 @@ public virtual IModel GetModel( /// The convention set to use when creating the model. /// The dependencies object for the model. /// The model to be used. + [Obsolete("Use the overload with IModelCreationDependencies")] public virtual IModel GetModel( DbContext context, IConventionSetBuilder conventionSetBuilder, @@ -106,13 +107,44 @@ public virtual IModel GetModel( return model; } + /// + /// Gets the model to be used. + /// + /// The context the model is being produced for. + /// The dependencies object used during the creation of the model. + /// The model to be used. + public virtual IModel GetModel( + DbContext context, + IModelCreationDependencies modelCreationDependencies) + { + var cache = Dependencies.MemoryCache; + var cacheKey = Dependencies.ModelCacheKeyFactory.Create(context); + if (!cache.TryGetValue(cacheKey, out IModel model)) + { + // Make sure OnModelCreating really only gets called once, since it may not be thread safe. + lock (_syncObject) + { + if (!cache.TryGetValue(cacheKey, out model)) + { + model = CreateModel(context, modelCreationDependencies.ConventionSetBuilder, modelCreationDependencies.ModelDependencies); + + modelCreationDependencies.ModelRuntimeInitializer.Initialize(model, modelCreationDependencies.ValidationLogger); + + model = cache.Set(cacheKey, model, new MemoryCacheEntryOptions { Size = 100, Priority = CacheItemPriority.High }); + } + } + } + + return model; + } + /// /// Creates the model. This method is called when the model was not found in the cache. /// /// The context the model is being produced for. /// The convention set to use when creating the model. /// The model to be used. - [Obsolete("Use the overload with ModelDependencies")] + [Obsolete("Use the overload with IModelCreationDependencies")] protected virtual IModel CreateModel( [NotNull] DbContext context, [NotNull] IConventionSetBuilder conventionSetBuilder) diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index 3bf6c273ffb..405f614e1fc 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -51,8 +51,6 @@ public ModelValidator([NotNull] ModelValidatorDependencies dependencies) /// public virtual void Validate(IModel model, IDiagnosticsLogger logger) { - ((Model)model).IsValidated = true; - #pragma warning disable CS0618 // Type or member is obsolete ValidateNoShadowEntities(model, logger); #pragma warning restore CS0618 // Type or member is obsolete diff --git a/src/EFCore/Infrastructure/ModelValidatorDependencies.cs b/src/EFCore/Infrastructure/ModelValidatorDependencies.cs index c9d36bdf085..4ee43f23ab9 100644 --- a/src/EFCore/Infrastructure/ModelValidatorDependencies.cs +++ b/src/EFCore/Infrastructure/ModelValidatorDependencies.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage; @@ -65,13 +66,16 @@ public ModelValidatorDependencies( Check.NotNull(typeMappingSource, nameof(typeMappingSource)); Check.NotNull(memberClassifier, nameof(memberClassifier)); +#pragma warning disable CS0618 // Type or member is obsolete TypeMappingSource = typeMappingSource; +#pragma warning restore CS0618 // Type or member is obsolete MemberClassifier = memberClassifier; } /// /// The type mapper. /// + [Obsolete("The model now contains this dependency")] public ITypeMappingSource TypeMappingSource { get; [param: NotNull] init; } /// diff --git a/src/EFCore/Infrastructure/SingletonModelDependencies.cs b/src/EFCore/Infrastructure/SingletonModelDependencies.cs new file mode 100644 index 00000000000..aaafa61b2e3 --- /dev/null +++ b/src/EFCore/Infrastructure/SingletonModelDependencies.cs @@ -0,0 +1,70 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Utilities; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.EntityFrameworkCore.Infrastructure +{ + /// + /// + /// Service dependencies parameter class for + /// + /// + /// This type is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// Do not construct instances of this class directly from either provider or application code as the + /// constructor signature may change as new dependencies are added. Instead, use this type in + /// your constructor so that an instance will be created and injected automatically by the + /// dependency injection container. To create an instance with some dependent services replaced, + /// first resolve the object from the dependency injection container, then replace selected + /// services using the 'With...' methods. Do not call the constructor at any point in this process. + /// + /// + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. + /// + /// + public sealed record SingletonModelDependencies + { + /// + /// + /// Creates the service dependencies parameter object for a . + /// + /// + /// Do not call this constructor directly from either provider or application code as it may change + /// as new dependencies are added. Instead, use this type in your constructor so that an instance + /// will be created and injected automatically by the dependency injection container. To create + /// an instance with some dependent services replaced, first resolve the object from the dependency + /// injection container, then replace selected services using the 'With...' methods. Do not call + /// the constructor at any point in this process. + /// + /// + /// 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. + /// + /// + [EntityFrameworkInternal] + public SingletonModelDependencies( + [NotNull] ITypeMappingSource typeMappingSource) + { + Check.NotNull(typeMappingSource, nameof(typeMappingSource)); + + TypeMappingSource = typeMappingSource; + } + + /// + /// The type mapper. + /// + public ITypeMappingSource TypeMappingSource { get; [param: NotNull] init; } + } +} diff --git a/src/EFCore/Internal/DbContextServices.cs b/src/EFCore/Internal/DbContextServices.cs index 4e73463609a..225438f487e 100644 --- a/src/EFCore/Internal/DbContextServices.cs +++ b/src/EFCore/Internal/DbContextServices.cs @@ -32,7 +32,7 @@ public class DbContextServices : IDbContextServices private IServiceProvider _scopedProvider; private IDbContextOptions _contextOptions; private ICurrentDbContext _currentContext; - private IModel _modelFromSource; + private IModel _model; private bool _inOnModelCreating; /// @@ -70,22 +70,27 @@ public virtual IDbContextServices Initialize( private static string BuildDatabaseNamesString(IEnumerable available) => string.Join(", ", available.Select(e => "'" + e.Name + "'")); - private IModel CreateModel() + private IModel CreateModel(IModel modelFromOptions) { if (_inOnModelCreating) { throw new InvalidOperationException(CoreStrings.RecursiveOnModelCreating); } + if (modelFromOptions != null + && modelFromOptions.ModelDependencies != null) + { + return modelFromOptions; + } + try { _inOnModelCreating = true; var dependencies = _scopedProvider.GetService(); - return dependencies.ModelSource.GetModel( - _currentContext.Context, - dependencies.ConventionSetBuilder, - dependencies.ModelDependencies); + return modelFromOptions == null + ? dependencies.ModelSource.GetModel(_currentContext.Context, dependencies) + : dependencies.ModelRuntimeInitializer.Initialize(modelFromOptions, dependencies.ValidationLogger); } finally { @@ -109,8 +114,7 @@ public virtual ICurrentDbContext CurrentContext /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IModel Model - => CoreOptions?.Model - ?? (_modelFromSource ??= CreateModel()); + => _model ??= CreateModel(CoreOptions?.Model); private CoreOptionsExtension CoreOptions => _contextOptions?.FindExtension(); diff --git a/src/EFCore/Internal/ModelCreationDependencies.cs b/src/EFCore/Internal/ModelCreationDependencies.cs index 1b98d5880dd..027920cc3da 100644 --- a/src/EFCore/Internal/ModelCreationDependencies.cs +++ b/src/EFCore/Internal/ModelCreationDependencies.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Utilities; @@ -34,15 +35,21 @@ public sealed record ModelCreationDependencies : IModelCreationDependencies public ModelCreationDependencies( [NotNull] IModelSource modelSource, [NotNull] IConventionSetBuilder conventionSetBuilder, - [NotNull] ModelDependencies modelDependencies) + [NotNull] ModelDependencies modelDependencies, + [NotNull] IModelRuntimeInitializer modelRuntimeInitializer, + [NotNull] IDiagnosticsLogger validationLogger) { Check.NotNull(modelSource, nameof(modelSource)); Check.NotNull(conventionSetBuilder, nameof(conventionSetBuilder)); Check.NotNull(modelDependencies, nameof(modelDependencies)); + Check.NotNull(modelRuntimeInitializer, nameof(modelRuntimeInitializer)); + Check.NotNull(validationLogger, nameof(validationLogger)); ModelSource = modelSource; ConventionSetBuilder = conventionSetBuilder; ModelDependencies = modelDependencies; + ModelRuntimeInitializer = modelRuntimeInitializer; + ValidationLogger = validationLogger; } /// @@ -68,5 +75,21 @@ public ModelCreationDependencies( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public ModelDependencies ModelDependencies { get; [param: NotNull] init; } + + /// + /// 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 IModelRuntimeInitializer ModelRuntimeInitializer { get; [param: NotNull] init; } + + /// + /// 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 IDiagnosticsLogger ValidationLogger { get; [param: NotNull] init; } } } diff --git a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs index 81fd7c81bd9..ad09b3e97b0 100644 --- a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs +++ b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs @@ -232,7 +232,6 @@ public virtual ConventionSet CreateConventionSet() conventionSet.ModelFinalizingConventions.Add(foreignKeyAttributeConvention); conventionSet.ModelFinalizingConventions.Add(new ChangeTrackingStrategyConvention(Dependencies)); conventionSet.ModelFinalizingConventions.Add(new ConstructorBindingConvention(Dependencies)); - conventionSet.ModelFinalizingConventions.Add(new TypeMappingConvention(Dependencies)); conventionSet.ModelFinalizingConventions.Add(foreignKeyIndexConvention); conventionSet.ModelFinalizingConventions.Add(foreignKeyPropertyDiscoveryConvention); conventionSet.ModelFinalizingConventions.Add(servicePropertyDiscoveryConvention); @@ -242,8 +241,6 @@ public virtual ConventionSet CreateConventionSet() conventionSet.ModelFinalizingConventions.Add(inversePropertyAttributeConvention); conventionSet.ModelFinalizingConventions.Add(backingFieldConvention); - conventionSet.ModelFinalizedConventions.Add(new ValidatingConvention(Dependencies)); - return conventionSet; } diff --git a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilderDependencies.cs b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilderDependencies.cs index efa0644f518..8cd0fe3ebb1 100644 --- a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilderDependencies.cs +++ b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilderDependencies.cs @@ -98,10 +98,12 @@ public ProviderConventionSetBuilderDependencies( MemberClassifier = memberClassifier; ConstructorBindingFactory = constructorBindingFactory; Logger = logger; - ValidationLogger = validationLogger; SetFinder = setFinder; _currentContext = currentContext; + ValidationLogger = validationLogger; +#pragma warning disable CS0618 // Type or member is obsolete ModelValidator = validator; +#pragma warning restore CS0618 // Type or member is obsolete } /// @@ -149,6 +151,7 @@ public Type ContextType /// /// The model validator. /// + [Obsolete("The validation is no longer performed by a convention")] public IModelValidator ModelValidator { get; [param: NotNull] init; } /// @@ -157,8 +160,10 @@ public Type ContextType /// A replacement for the current dependency of this type. /// A new parameter object with the given service replaced. public ProviderConventionSetBuilderDependencies With([NotNull] ICurrentDbContext currentContext) +#pragma warning disable CS0618 // Type or member is obsolete => new( TypeMappingSource, ConstructorBindingFactory, ParameterBindingFactories, MemberClassifier, Logger, ValidationLogger, SetFinder, currentContext, ModelValidator); +#pragma warning restore CS0618 // Type or member is obsolete } } diff --git a/src/EFCore/Metadata/Conventions/TypeMappingConvention.cs b/src/EFCore/Metadata/Conventions/TypeMappingConvention.cs index b5a6c990c40..27bfda97277 100644 --- a/src/EFCore/Metadata/Conventions/TypeMappingConvention.cs +++ b/src/EFCore/Metadata/Conventions/TypeMappingConvention.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -13,6 +14,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions /// /// A convention that creates and assigns store type mapping to entity properties. /// + [Obsolete("Use IModelRuntimeInitializer.Initialize instead.")] public class TypeMappingConvention : IModelFinalizingConvention { /// diff --git a/src/EFCore/Metadata/Conventions/ValidatingConvention.cs b/src/EFCore/Metadata/Conventions/ValidatingConvention.cs index 2ab764b0735..e0677cbe5bc 100644 --- a/src/EFCore/Metadata/Conventions/ValidatingConvention.cs +++ b/src/EFCore/Metadata/Conventions/ValidatingConvention.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; @@ -11,6 +12,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions /// /// A convention that checks whether the model is valid. /// + [Obsolete("The validation is no longer performed by a convention")] public class ValidatingConvention : IModelFinalizedConvention { /// diff --git a/src/EFCore/Metadata/IModel.cs b/src/EFCore/Metadata/IModel.cs index c1fef13bd23..16a278ac25e 100644 --- a/src/EFCore/Metadata/IModel.cs +++ b/src/EFCore/Metadata/IModel.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.Extensions.DependencyInjection; #nullable enable @@ -53,5 +54,28 @@ public interface IModel : IAnnotatable [NotNull] string name, [NotNull] string definingNavigationName, [NotNull] IEntityType definingEntityType); + + /// + /// The runtime service dependencies. + /// + SingletonModelDependencies? ModelDependencies + => (SingletonModelDependencies?)FindRuntimeAnnotationValue(CoreAnnotationNames.ModelDependencies); + + /// + /// Set the runtime service dependencies. + /// + /// The runtime service dependencies. + /// if the runtime service dependencies were set; otherwise. + bool SetModelDependencies([NotNull] SingletonModelDependencies modelDependencies) + { + if (FindRuntimeAnnotation(CoreAnnotationNames.ModelDependencies) != null) + { + return false; + } + + SetRuntimeAnnotation(CoreAnnotationNames.ModelDependencies, modelDependencies); + + return true; + } } } diff --git a/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs b/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs index d6fd6a9a4dd..df492680914 100644 --- a/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs +++ b/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs @@ -211,6 +211,14 @@ public static class CoreAnnotationNames /// public const string ProviderClrType = "ProviderClrType"; + /// + /// 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 const string ModelDependencies = "ModelDependencies"; + /// /// 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 @@ -303,6 +311,7 @@ public static class CoreAnnotationNames #pragma warning restore CS0612 // Type or member is obsolete EagerLoaded, ProviderClrType, + ModelDependencies, InverseNavigations, DerivedTypes, NavigationCandidates, diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index 86726e0ec08..bce31c04e93 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -77,6 +77,8 @@ private readonly SortedDictionary _serviceProperties private Func? _shadowValuesFactory; private Func? _emptyShadowValuesFactory; private Func? _instanceFactory; + private IProperty[]? _propagatingProperties; + private IProperty[]? _generatingProperties; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -199,7 +201,7 @@ public virtual bool IsKeyless /// public virtual bool? SetIsKeyless(bool? keyless, ConfigurationSource configurationSource) { - EnsureReadonly(false); + EnsureMutable(); if (_isKeyless == keyless) { @@ -262,7 +264,7 @@ public virtual void UpdateIsKeylessConfigurationSource(ConfigurationSource confi /// public virtual EntityType? SetBaseType([CanBeNull] EntityType? newBaseType, ConfigurationSource configurationSource) { - EnsureReadonly(false); + EnsureMutable(); Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); if (_baseType == newBaseType) @@ -548,7 +550,7 @@ public virtual IEnumerable FindMembersInHierarchy([NotNull] string [CanBeNull] IReadOnlyList? properties, ConfigurationSource configurationSource) { - EnsureReadonly(false); + EnsureMutable(); Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); if (_baseType != null) @@ -710,7 +712,7 @@ private void UpdatePrimaryKeyConfigurationSource(ConfigurationSource configurati { Check.NotEmpty(properties, nameof(properties)); Check.HasNoNulls(properties, nameof(properties)); - EnsureReadonly(false); + EnsureMutable(); if (_baseType != null) { @@ -848,7 +850,7 @@ public virtual IEnumerable GetDeclaredKeys() { Check.NotNull(key, nameof(key)); Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); - EnsureReadonly(false); + EnsureMutable(); if (key.DeclaringEntityType != this) { @@ -942,7 +944,7 @@ public virtual IEnumerable GetKeys() Check.HasNoNulls(properties, nameof(properties)); Check.NotNull(principalKey, nameof(principalKey)); Check.NotNull(principalEntityType, nameof(principalEntityType)); - EnsureReadonly(false); + EnsureMutable(); var foreignKey = new ForeignKey( properties, principalKey, this, principalEntityType, configurationSource); @@ -1296,7 +1298,7 @@ public virtual IEnumerable FindForeignKeysInHierarchy( { Check.NotNull(foreignKey, nameof(foreignKey)); Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); - EnsureReadonly(false); + EnsureMutable(); if (foreignKey.DeclaringEntityType != this) { @@ -1432,7 +1434,7 @@ public virtual Navigation AddNavigation( private Navigation AddNavigation(MemberIdentity navigationMember, ForeignKey foreignKey, bool pointsToPrincipal) { - EnsureReadonly(false); + EnsureMutable(); var name = navigationMember.Name!; var duplicateNavigation = FindNavigationsInHierarchy(name).FirstOrDefault(); @@ -1585,7 +1587,7 @@ public virtual IEnumerable FindNavigationsInHierarchy([NotNull] stri public virtual Navigation? RemoveNavigation([NotNull] string name) { Check.NotEmpty(name, nameof(name)); - EnsureReadonly(false); + EnsureMutable(); var navigation = FindDeclaredNavigation(name); if (navigation == null) @@ -1625,7 +1627,7 @@ public virtual IEnumerable GetNavigations() { Check.NotEmpty(name, nameof(name)); Check.NotNull(targetEntityType, nameof(targetEntityType)); - EnsureReadonly(false); + EnsureMutable(); var duplicateProperty = FindMembersInHierarchy(name).FirstOrDefault(); if (duplicateProperty != null) @@ -1826,7 +1828,7 @@ public virtual IEnumerable FindSkipNavigationsInHierarchy([NotNu { Check.NotNull(navigation, nameof(navigation)); Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); - EnsureReadonly(false); + EnsureMutable(); if (navigation.DeclaringEntityType != this) { @@ -1948,7 +1950,7 @@ public virtual IEnumerable GetDerivedReferencingSkipNavigations( { Check.NotEmpty(properties, nameof(properties)); Check.HasNoNulls(properties, nameof(properties)); - EnsureReadonly(false); + EnsureMutable(); CheckIndexProperties(properties); @@ -1981,7 +1983,7 @@ public virtual IEnumerable GetDerivedReferencingSkipNavigations( Check.NotEmpty(properties, nameof(properties)); Check.HasNoNulls(properties, nameof(properties)); Check.NotEmpty(name, nameof(name)); - EnsureReadonly(false); + EnsureMutable(); CheckIndexProperties(properties); @@ -2208,7 +2210,7 @@ public virtual IEnumerable FindIndexesInHierarchy([NotNull] string name) { Check.NotNull(index, nameof(index)); Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); - EnsureReadonly(false); + EnsureMutable(); if (index.Name == null) { @@ -2335,7 +2337,7 @@ public virtual IEnumerable GetIndexes() Check.NotNull(name, nameof(name)); Check.NotNull(propertyType, nameof(propertyType)); Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); - EnsureReadonly(false); + EnsureMutable(); var conflictingMember = FindMembersInHierarchy(name).FirstOrDefault(); if (conflictingMember != null) @@ -2509,7 +2511,7 @@ public virtual IEnumerable FindPropertiesInHierarchy([NotNull] string { Check.NotNull(property, nameof(property)); Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); - EnsureReadonly(false); + EnsureMutable(); if (property.DeclaringEntityType != this) { @@ -2576,7 +2578,11 @@ public virtual IEnumerable GetProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual PropertyCounts Counts - => NonCapturingLazyInitializer.EnsureInitialized(ref _counts, this, static entityType => entityType.CalculateCounts()); + => NonCapturingLazyInitializer.EnsureInitialized(ref _counts, this, static entityType => + { + entityType.EnsureReadOnly(); + return entityType.CalculateCounts(); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2587,7 +2593,11 @@ public virtual PropertyCounts Counts public virtual Func RelationshipSnapshotFactory => NonCapturingLazyInitializer.EnsureInitialized( ref _relationshipSnapshotFactory, this, - static entityType => new RelationshipSnapshotFactoryFactory().Create(entityType)); + static entityType => + { + entityType.EnsureReadOnly(); + return new RelationshipSnapshotFactoryFactory().Create(entityType); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2598,7 +2608,11 @@ public virtual Func RelationshipSnapshotFactory public virtual Func OriginalValuesFactory => NonCapturingLazyInitializer.EnsureInitialized( ref _originalValuesFactory, this, - static entityType => new OriginalValuesFactoryFactory().Create(entityType)); + static entityType => + { + entityType.EnsureReadOnly(); + return new OriginalValuesFactoryFactory().Create(entityType); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2609,7 +2623,11 @@ public virtual Func OriginalValuesFactory public virtual Func StoreGeneratedValuesFactory => NonCapturingLazyInitializer.EnsureInitialized( ref _storeGeneratedValuesFactory, this, - static entityType => new SidecarValuesFactoryFactory().Create(entityType)); + static entityType => + { + entityType.EnsureReadOnly(); + return new SidecarValuesFactoryFactory().Create(entityType); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2620,7 +2638,11 @@ public virtual Func StoreGeneratedValuesFactory public virtual Func TemporaryValuesFactory => NonCapturingLazyInitializer.EnsureInitialized( ref _temporaryValuesFactory, this, - static entityType => new TemporaryValuesFactoryFactory().Create(entityType)); + static entityType => + { + entityType.EnsureReadOnly(); + return new TemporaryValuesFactoryFactory().Create(entityType); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2631,7 +2653,11 @@ public virtual Func TemporaryValuesFactory public virtual Func ShadowValuesFactory => NonCapturingLazyInitializer.EnsureInitialized( ref _shadowValuesFactory, this, - static entityType => new ShadowValuesFactoryFactory().Create(entityType)); + static entityType => + { + entityType.EnsureReadOnly(); + return new ShadowValuesFactoryFactory().Create(entityType); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2642,7 +2668,11 @@ public virtual Func ShadowValuesFactory public virtual Func EmptyShadowValuesFactory => NonCapturingLazyInitializer.EnsureInitialized( ref _emptyShadowValuesFactory, this, - static entityType => new EmptyShadowValuesFactoryFactory().CreateEmpty(entityType)); + static entityType => + { + entityType.EnsureReadOnly(); + return new EmptyShadowValuesFactoryFactory().CreateEmpty(entityType); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2655,6 +2685,8 @@ public virtual Func InstanceFactory ref _instanceFactory, this, static entityType => { + entityType.EnsureReadOnly(); + var binding = (InstantiationBinding?)entityType[CoreAnnotationNames.ServiceOnlyConstructorBinding]; if (binding == null) { @@ -2663,13 +2695,45 @@ public virtual Func InstanceFactory var contextParam = Expression.Parameter(typeof(MaterializationContext), "mc"); - entityType._instanceFactory = Expression.Lambda>( + return Expression.Lambda>( binding.CreateConstructorExpression( new ParameterBindingInfo(entityType, contextParam)), contextParam) .Compile(); }); + /// + /// 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 IReadOnlyList PropagatingProperties + => NonCapturingLazyInitializer.EnsureInitialized( + ref _propagatingProperties, this, + static entityType => + { + entityType.EnsureReadOnly(); + + return entityType.GetProperties().Where(p => p.IsForeignKey()).ToArray(); + }); + + /// + /// 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 IReadOnlyList GeneratingProperties + => NonCapturingLazyInitializer.EnsureInitialized( + ref _generatingProperties, this, + static entityType => + { + entityType.EnsureReadOnly(); + + return entityType.GetProperties().Where(p => p.RequiresValueGenerator()).ToArray(); + }); + #endregion #region Service properties @@ -2686,7 +2750,7 @@ public virtual ServiceProperty AddServiceProperty( ConfigurationSource configurationSource) { Check.NotNull(memberInfo, nameof(memberInfo)); - EnsureReadonly(false); + EnsureMutable(); var name = memberInfo.GetSimpleMemberName(); var duplicateMember = FindMembersInHierarchy(name).FirstOrDefault(); @@ -2818,7 +2882,7 @@ public virtual ServiceProperty RemoveServiceProperty([NotNull] ServiceProperty p { Check.NotNull(property, nameof(property)); Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); - EnsureReadonly(false); + EnsureMutable(); if (property.DeclaringEntityType != this) { @@ -3005,7 +3069,7 @@ public virtual IEnumerable GetDeclaredServiceProperties() /// public virtual void AddData([NotNull] IEnumerable data) { - EnsureReadonly(false); + EnsureMutable(); _data ??= new List(); @@ -3047,7 +3111,7 @@ public virtual ChangeTrackingStrategy GetChangeTrackingStrategy() ChangeTrackingStrategy? changeTrackingStrategy, ConfigurationSource configurationSource) { - EnsureReadonly(false); + EnsureMutable(); if (changeTrackingStrategy != null) { diff --git a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs index 4d71f4fd145..49ad3ea5bb3 100644 --- a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs +++ b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs @@ -191,8 +191,6 @@ public static PropertyCounts GetCounts([NotNull] this IEntityType entityType) /// public static PropertyCounts CalculateCounts([NotNull] this EntityType entityType) { - Check.DebugAssert(entityType.Model.IsValidated, "Should not be called on a non-validated model"); - var index = 0; var navigationIndex = 0; var originalValueIndex = 0; @@ -277,6 +275,24 @@ public static Func GetInstanceFactory([NotNull] public static Func GetEmptyShadowValuesFactory([NotNull] this IEntityType entityType) => entityType.AsEntityType().EmptyShadowValuesFactory; + /// + /// 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 static IReadOnlyList GetPropagatingProperties([NotNull] this IEntityType entityType) + => entityType.AsEntityType().PropagatingProperties; + + /// + /// 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 static IReadOnlyList GetGeneratingProperties([NotNull] this IEntityType entityType) + => entityType.AsEntityType().GeneratingProperties; + /// /// 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/Metadata/Internal/ForeignKey.cs b/src/EFCore/Metadata/Internal/ForeignKey.cs index adc9da49e8f..54d9f35b66f 100644 --- a/src/EFCore/Metadata/Internal/ForeignKey.cs +++ b/src/EFCore/Metadata/Internal/ForeignKey.cs @@ -44,6 +44,8 @@ public class ForeignKey : ConventionAnnotatable, IMutableForeignKey, IConvention private ConfigurationSource? _isOwnershipConfigurationSource; private ConfigurationSource? _dependentToPrincipalConfigurationSource; private ConfigurationSource? _principalToDependentConfigurationSource; + private object? _dependentKeyValueFactory; + private Func? _dependentsMapFactory; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -150,7 +152,7 @@ public virtual void SetRemovedFromModel() /// 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. /// - protected override bool IsReadonly => DeclaringEntityType.Model.IsModelReadonly; + protected override bool IsReadOnly => DeclaringEntityType.Model.IsModelReadOnly; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -217,7 +219,7 @@ public virtual IReadOnlyList SetProperties( [NotNull] Key principalKey, ConfigurationSource? configurationSource) { - EnsureReadonly(false); + EnsureMutable(); Validate(properties, principalKey, DeclaringEntityType, PrincipalEntityType); @@ -425,7 +427,7 @@ public virtual void UpdatePrincipalToDependentConfigurationSource(ConfigurationS ConfigurationSource configurationSource, bool pointsToPrincipal) { - EnsureReadonly(false); + EnsureMutable(); var name = propertyIdentity?.Name; if (pointsToPrincipal @@ -547,7 +549,7 @@ public virtual bool IsUnique /// public virtual bool? SetIsUnique(bool? unique, ConfigurationSource configurationSource) { - EnsureReadonly(false); + EnsureMutable(); var oldUnique = IsUnique; _isUnique = unique; @@ -622,7 +624,7 @@ public virtual bool IsRequired /// public virtual bool? SetIsRequired(bool? required, ConfigurationSource configurationSource) { - EnsureReadonly(false); + EnsureMutable(); var oldRequired = IsRequired; _isRequired = required ?? DefaultIsRequired; @@ -678,7 +680,7 @@ public virtual bool IsRequiredDependent /// public virtual bool? SetIsRequiredDependent(bool? required, ConfigurationSource configurationSource) { - EnsureReadonly(false); + EnsureMutable(); if (!IsUnique && required == true) @@ -741,7 +743,7 @@ public virtual DeleteBehavior DeleteBehavior /// public virtual DeleteBehavior? SetDeleteBehavior(DeleteBehavior? deleteBehavior, ConfigurationSource configurationSource) { - EnsureReadonly(false); + EnsureMutable(); _deleteBehavior = deleteBehavior; @@ -799,7 +801,7 @@ public virtual bool IsOwnership /// public virtual bool? SetIsOwnership(bool? ownership, ConfigurationSource configurationSource) { - EnsureReadonly(false); + EnsureMutable(); var oldIsOwnership = IsOwnership; _isOwnership = ownership; @@ -874,7 +876,26 @@ public virtual EntityType ResolveOtherEntityType([NotNull] EntityType entityType /// 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 object? DependentKeyValueFactory { get; [param: NotNull] set; } + public virtual object DependentKeyValueFactory + { + get + { + if (_dependentKeyValueFactory == null) + { + EnsureReadOnly(); + } + + return _dependentKeyValueFactory!; + } + + [param: NotNull] + set + { + EnsureReadOnly(); + + _dependentKeyValueFactory = value; + } + } // Note: This is set and used only by IdentityMapFactoryFactory, which ensures thread-safety /// @@ -883,7 +904,26 @@ public virtual EntityType ResolveOtherEntityType([NotNull] EntityType entityType /// 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 Func? DependentsMapFactory { get; [param: NotNull] set; } + public virtual Func DependentsMapFactory + { + get + { + if (_dependentsMapFactory == null) + { + EnsureReadOnly(); + } + + return _dependentsMapFactory!; + } + + [param: NotNull] + set + { + EnsureReadOnly(); + + _dependentsMapFactory = value; + } + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -893,7 +933,8 @@ public virtual EntityType ResolveOtherEntityType([NotNull] EntityType entityType /// IReadOnlyList IForeignKey.Properties { - [DebuggerStepThrough] get => Properties; + [DebuggerStepThrough] + get => Properties; } /// @@ -904,7 +945,8 @@ IReadOnlyList IForeignKey.Properties /// IKey IForeignKey.PrincipalKey { - [DebuggerStepThrough] get => PrincipalKey; + [DebuggerStepThrough] + get => PrincipalKey; } /// @@ -915,7 +957,8 @@ IKey IForeignKey.PrincipalKey /// IEntityType IForeignKey.DeclaringEntityType { - [DebuggerStepThrough] get => DeclaringEntityType; + [DebuggerStepThrough] + get => DeclaringEntityType; } /// @@ -926,7 +969,8 @@ IEntityType IForeignKey.DeclaringEntityType /// IEntityType IForeignKey.PrincipalEntityType { - [DebuggerStepThrough] get => PrincipalEntityType; + [DebuggerStepThrough] + get => PrincipalEntityType; } /// @@ -937,7 +981,8 @@ IEntityType IForeignKey.PrincipalEntityType /// INavigation? IForeignKey.DependentToPrincipal { - [DebuggerStepThrough] get => DependentToPrincipal; + [DebuggerStepThrough] + get => DependentToPrincipal; } /// @@ -948,7 +993,8 @@ IEntityType IForeignKey.PrincipalEntityType /// INavigation? IForeignKey.PrincipalToDependent { - [DebuggerStepThrough] get => PrincipalToDependent; + [DebuggerStepThrough] + get => PrincipalToDependent; } /// @@ -959,7 +1005,8 @@ IEntityType IForeignKey.PrincipalEntityType /// IReadOnlyList IMutableForeignKey.Properties { - [DebuggerStepThrough] get => Properties; + [DebuggerStepThrough] + get => Properties; } /// @@ -970,7 +1017,8 @@ IReadOnlyList IMutableForeignKey.Properties /// IMutableKey IMutableForeignKey.PrincipalKey { - [DebuggerStepThrough] get => PrincipalKey; + [DebuggerStepThrough] + get => PrincipalKey; } /// @@ -981,7 +1029,8 @@ IMutableKey IMutableForeignKey.PrincipalKey /// IMutableEntityType IMutableForeignKey.DeclaringEntityType { - [DebuggerStepThrough] get => DeclaringEntityType; + [DebuggerStepThrough] + get => DeclaringEntityType; } /// @@ -992,7 +1041,8 @@ IMutableEntityType IMutableForeignKey.DeclaringEntityType /// IMutableEntityType IMutableForeignKey.PrincipalEntityType { - [DebuggerStepThrough] get => PrincipalEntityType; + [DebuggerStepThrough] + get => PrincipalEntityType; } /// @@ -1003,7 +1053,8 @@ IMutableEntityType IMutableForeignKey.PrincipalEntityType /// IMutableNavigation? IMutableForeignKey.DependentToPrincipal { - [DebuggerStepThrough] get => DependentToPrincipal; + [DebuggerStepThrough] + get => DependentToPrincipal; } /// @@ -1014,7 +1065,8 @@ IMutableEntityType IMutableForeignKey.PrincipalEntityType /// IMutableNavigation? IMutableForeignKey.PrincipalToDependent { - [DebuggerStepThrough] get => PrincipalToDependent; + [DebuggerStepThrough] + get => PrincipalToDependent; } /// @@ -1077,7 +1129,8 @@ void IMutableForeignKey.SetProperties(IReadOnlyList properties /// IConventionEntityType IConventionForeignKey.DeclaringEntityType { - [DebuggerStepThrough] get => DeclaringEntityType; + [DebuggerStepThrough] + get => DeclaringEntityType; } /// @@ -1088,7 +1141,8 @@ IConventionEntityType IConventionForeignKey.DeclaringEntityType /// IConventionEntityType IConventionForeignKey.PrincipalEntityType { - [DebuggerStepThrough] get => PrincipalEntityType; + [DebuggerStepThrough] + get => PrincipalEntityType; } /// @@ -1099,7 +1153,8 @@ IConventionEntityType IConventionForeignKey.PrincipalEntityType /// IConventionKey IConventionForeignKey.PrincipalKey { - [DebuggerStepThrough] get => PrincipalKey; + [DebuggerStepThrough] + get => PrincipalKey; } /// @@ -1110,7 +1165,8 @@ IConventionKey IConventionForeignKey.PrincipalKey /// IReadOnlyList IConventionForeignKey.Properties { - [DebuggerStepThrough] get => Properties; + [DebuggerStepThrough] + get => Properties; } /// @@ -1121,7 +1177,8 @@ IReadOnlyList IConventionForeignKey.Properties /// IConventionNavigation? IConventionForeignKey.DependentToPrincipal { - [DebuggerStepThrough] get => DependentToPrincipal; + [DebuggerStepThrough] + get => DependentToPrincipal; } /// @@ -1132,7 +1189,8 @@ IReadOnlyList IConventionForeignKey.Properties /// IConventionNavigation? IConventionForeignKey.PrincipalToDependent { - [DebuggerStepThrough] get => PrincipalToDependent; + [DebuggerStepThrough] + get => PrincipalToDependent; } /// @@ -1143,7 +1201,8 @@ IReadOnlyList IConventionForeignKey.Properties /// IConventionForeignKeyBuilder IConventionForeignKey.Builder { - [DebuggerStepThrough] get => Builder; + [DebuggerStepThrough] + get => Builder; } /// @@ -1154,7 +1213,8 @@ IConventionForeignKeyBuilder IConventionForeignKey.Builder /// IConventionAnnotatableBuilder IConventionAnnotatable.Builder { - [DebuggerStepThrough] get => Builder; + [DebuggerStepThrough] + get => Builder; } /// diff --git a/src/EFCore/Metadata/Internal/ForeignKeyExtensions.cs b/src/EFCore/Metadata/Internal/ForeignKeyExtensions.cs index b5441062686..0cce91d9e28 100644 --- a/src/EFCore/Metadata/Internal/ForeignKeyExtensions.cs +++ b/src/EFCore/Metadata/Internal/ForeignKeyExtensions.cs @@ -245,7 +245,7 @@ public static IEntityType ResolveEntityTypeInHierarchy([NotNull] this IForeignKe /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public static IDependentsMap CreateDependentsMapFactory([NotNull] this IForeignKey foreignKey) - => foreignKey.AsForeignKey().DependentsMapFactory!(); // TODO-NULLABLE: #22031 + => foreignKey.AsForeignKey().DependentsMapFactory(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/Index.cs b/src/EFCore/Metadata/Internal/Index.cs index 46bc0f02924..24634905937 100644 --- a/src/EFCore/Metadata/Internal/Index.cs +++ b/src/EFCore/Metadata/Internal/Index.cs @@ -133,7 +133,7 @@ public virtual void SetRemovedFromModel() /// 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. /// - protected override bool IsReadonly => DeclaringEntityType.Model.IsModelReadonly; + protected override bool IsReadOnly => DeclaringEntityType.Model.IsModelReadOnly; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -173,7 +173,7 @@ public virtual bool IsUnique /// public virtual bool? SetIsUnique(bool? unique, ConfigurationSource configurationSource) { - EnsureReadonly(false); + EnsureMutable(); var oldIsUnique = IsUnique; var isChanging = oldIsUnique != unique; @@ -229,7 +229,11 @@ private void UpdateIsUniqueConfigurationSource(ConfigurationSource configuration /// public virtual IDependentKeyValueFactory GetNullableValueFactory() => (IDependentKeyValueFactory)NonCapturingLazyInitializer.EnsureInitialized( - ref _nullableValueFactory, this, static i => new CompositeValueFactory(i.Properties)); + ref _nullableValueFactory, this, static index => + { + index.EnsureReadOnly(); + return new CompositeValueFactory(index.Properties); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs index 1b449af5bf0..024c8212f00 100644 --- a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs @@ -940,7 +940,7 @@ private static bool IsCompatible( && Metadata.GetPrincipalEndConfigurationSource() == null && configurationSource == ConfigurationSource.Explicit) { - Metadata.DeclaringEntityType.Model.ModelDependencies?.Logger.AmbiguousEndRequiredWarning(Metadata); + Metadata.DeclaringEntityType.Model.ScopedModelDependencies?.Logger.AmbiguousEndRequiredWarning(Metadata); } Metadata.SetIsRequired(required, configurationSource); diff --git a/src/EFCore/Metadata/Internal/Key.cs b/src/EFCore/Metadata/Internal/Key.cs index 21e0828204b..ef434bbd69c 100644 --- a/src/EFCore/Metadata/Internal/Key.cs +++ b/src/EFCore/Metadata/Internal/Key.cs @@ -105,7 +105,7 @@ public virtual void SetRemovedFromModel() /// 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. /// - protected override bool IsReadonly => DeclaringEntityType.Model.IsModelReadonly; + protected override bool IsReadOnly => DeclaringEntityType.Model.IsModelReadOnly; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -162,7 +162,11 @@ public virtual IEnumerable GetReferencingForeignKeys() /// public virtual Func IdentityMapFactory => NonCapturingLazyInitializer.EnsureInitialized( - ref _identityMapFactory, this, static k => new IdentityMapFactoryFactory().Create(k)); + ref _identityMapFactory, this, static key => + { + key.EnsureReadOnly(); + return new IdentityMapFactoryFactory().Create(key); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -172,7 +176,11 @@ public virtual Func IdentityMapFactory /// public virtual IPrincipalKeyValueFactory GetPrincipalKeyValueFactory() => (IPrincipalKeyValueFactory)NonCapturingLazyInitializer.EnsureInitialized( - ref _principalKeyValueFactory, this, static k => new KeyValueFactoryFactory().Create(k)); + ref _principalKeyValueFactory, this, static key => + { + key.EnsureReadOnly(); + return new KeyValueFactoryFactory().Create(key); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/Model.cs b/src/EFCore/Metadata/Internal/Model.cs index dd9e32c14b5..a17e9eaef7c 100644 --- a/src/EFCore/Metadata/Internal/Model.cs +++ b/src/EFCore/Metadata/Internal/Model.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using System.Reflection; +using System.Threading; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -43,6 +44,7 @@ public class Model : ConventionAnnotatable, IMutableModel, IConventionModel /// public static readonly Type DefaultPropertyBagType = typeof(Dictionary); + private SingletonModelDependencies? _modelDependencies; private readonly SortedDictionary _entityTypes = new(StringComparer.Ordinal); private readonly ConcurrentDictionary _indexerPropertyInfoMap = new(); private readonly ConcurrentDictionary _clrTypeNameMap = new(); @@ -75,7 +77,10 @@ public Model() /// public Model([NotNull] ConventionSet conventions, [CanBeNull] ModelDependencies? modelDependencies = null) { - ModelDependencies = modelDependencies; + if (modelDependencies != null) + { + ScopedModelDependencies = modelDependencies; + } var dispatcher = new ConventionDispatcher(conventions); var builder = new InternalModelBuilder(this); _conventionDispatcher = dispatcher; @@ -100,18 +105,18 @@ public virtual ConventionDispatcher ConventionDispatcher /// 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 ModelDependencies? ModelDependencies { get; private set; } + [CA.DisallowNull] + public virtual ModelDependencies? ScopedModelDependencies { get; [param: NotNull] set; } /// /// Indicates whether the model is read-only. /// - protected override bool IsReadonly - => IsModelReadonly; + protected override bool IsReadOnly => IsModelReadOnly; /// /// Indicates whether the model is read-only. /// - public virtual bool IsModelReadonly => _conventionDispatcher == null; + public virtual bool IsModelReadOnly => _conventionDispatcher == null; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -198,7 +203,7 @@ public virtual IEnumerable GetEntityTypes() private EntityType? AddEntityType(EntityType entityType) { - EnsureReadonly(false); + EnsureMutable(); var entityTypeName = entityType.Name; if (_entityTypes.ContainsKey(entityTypeName)) @@ -322,7 +327,7 @@ private static void AssertCanRemove(EntityType entityType) return null; } - EnsureReadonly(false); + EnsureMutable(); AssertCanRemove(entityType); if (_sharedTypes.TryGetValue(entityType.ClrType, out var existingTypes)) @@ -580,7 +585,7 @@ private static int GetDerivedLevel(Type? derivedType, Dictionary deri [CanBeNull] Type? type, ConfigurationSource configurationSource) { - EnsureReadonly(false); + EnsureMutable(); if (_ignoredTypeNames.TryGetValue(name, out var existingIgnoredConfigurationSource)) { @@ -675,7 +680,7 @@ public virtual bool IsShared([NotNull] Type type) public virtual string? RemoveIgnored(string name) { Check.NotNull(name, nameof(name)); - EnsureReadonly(false); + EnsureMutable(); return _ignoredTypeNames.Remove(name) ? name : null; } @@ -725,7 +730,7 @@ public virtual bool IsOwned([NotNull] Type type) /// public virtual void AddOwned([NotNull] Type type, ConfigurationSource configurationSource) { - EnsureReadonly(false); + EnsureMutable(); var name = GetDisplayName(type); if (!(this[CoreAnnotationNames.OwnedTypes] is Dictionary ownedTypes)) { @@ -750,7 +755,7 @@ public virtual void AddOwned([NotNull] Type type, ConfigurationSource configurat /// public virtual string? RemoveOwned([NotNull] Type type) { - EnsureReadonly(false); + EnsureMutable(); if (!(this[CoreAnnotationNames.OwnedTypes] is Dictionary ownedTypes)) { @@ -769,7 +774,7 @@ public virtual void AddOwned([NotNull] Type type, ConfigurationSource configurat /// public virtual void AddShared([NotNull] Type type, ConfigurationSource configurationSource) { - EnsureReadonly(false); + EnsureMutable(); if (_entityTypes.Any(et => !et.Value.HasSharedClrType && et.Value.ClrType == type)) { @@ -821,7 +826,7 @@ public virtual ChangeTrackingStrategy GetChangeTrackingStrategy() ChangeTrackingStrategy? changeTrackingStrategy, ConfigurationSource configurationSource) { - EnsureReadonly(false); + EnsureMutable(); _changeTrackingStrategy = changeTrackingStrategy; @@ -862,7 +867,7 @@ public virtual ChangeTrackingStrategy GetChangeTrackingStrategy() /// public virtual IConventionBatch DelayConventions() { - EnsureReadonly(false); + EnsureMutable(); return ConventionDispatcher.DelayConventions(); } @@ -874,7 +879,7 @@ public virtual IConventionBatch DelayConventions() /// public virtual T Track([NotNull] Func func, [CanBeNull] [CA.DisallowNull] ref IConventionForeignKey? foreignKey) { - EnsureReadonly(false); + EnsureMutable(); return ConventionDispatcher.Track(func, ref foreignKey); } @@ -886,7 +891,7 @@ public virtual T Track([NotNull] Func func, [CanBeNull] [CA.DisallowNull] /// public virtual IModel FinalizeModel() { - EnsureReadonly(false); + EnsureMutable(); ConventionDispatcher.AssertNoScope(); IModel? finalizedModel = ConventionDispatcher.OnModelFinalizing(Builder)?.Metadata; @@ -908,8 +913,7 @@ private IModel MakeReadonly() { // ConventionDispatcher should never be accessed once the model is made read-only. _conventionDispatcher = null; - ModelDependencies = null; - IsValidated = true; + ScopedModelDependencies = null!; return this; } @@ -942,7 +946,7 @@ public virtual bool SkipDetectChanges /// public virtual bool? SetSkipDetectChanges(bool? skipDetectChanges) { - EnsureReadonly(false); + EnsureMutable(); _skipDetectChanges = skipDetectChanges; @@ -956,7 +960,7 @@ public virtual bool SkipDetectChanges /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual object? RelationalModel - => this["Relational:RelationalModel"]; + => FindRuntimeAnnotation("Relational:RelationalModel"); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -969,6 +973,30 @@ public virtual DebugView DebugView () => this.ToDebugString(MetadataDebugStringOptions.ShortDefault), () => this.ToDebugString(MetadataDebugStringOptions.LongDefault)); + /// + /// The runtime service dependencies. + /// + SingletonModelDependencies? IModel.ModelDependencies + { + get + { + if (_modelDependencies == null) + { + EnsureReadOnly(); + } + + return _modelDependencies; + } + } + + /// + /// Set the runtime service dependencies. + /// + /// The runtime service dependencies. + /// if the runtime service dependencies were set; otherwise. + bool IModel.SetModelDependencies(SingletonModelDependencies modelDependencies) + => Interlocked.CompareExchange(ref _modelDependencies, modelDependencies, null) == null; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/Navigation.cs b/src/EFCore/Metadata/Internal/Navigation.cs index 8f34817887a..ed7a65cb05d 100644 --- a/src/EFCore/Metadata/Internal/Navigation.cs +++ b/src/EFCore/Metadata/Internal/Navigation.cs @@ -300,7 +300,11 @@ public virtual IClrCollectionAccessor? CollectionAccessor ref _collectionAccessor, ref _collectionAccessorInitialized, this, - static n => new ClrCollectionAccessorFactory().Create(n)); + static navigation => + { + navigation.EnsureReadOnly(); + return new ClrCollectionAccessorFactory().Create(navigation); + }); /// /// Runs the conventions when an annotation was set or removed. diff --git a/src/EFCore/Metadata/Internal/Property.cs b/src/EFCore/Metadata/Internal/Property.cs index 73dd36caa98..da37625e29d 100644 --- a/src/EFCore/Metadata/Internal/Property.cs +++ b/src/EFCore/Metadata/Internal/Property.cs @@ -162,7 +162,7 @@ public virtual bool IsNullable /// public virtual bool? SetIsNullable(bool? nullable, ConfigurationSource configurationSource) { - EnsureReadonly(false); + EnsureMutable(); var isChanging = (nullable ?? DefaultIsNullable) != IsNullable; if (nullable == null) @@ -244,7 +244,7 @@ public virtual ValueGenerated ValueGenerated /// public virtual ValueGenerated? SetValueGenerated(ValueGenerated? valueGenerated, ConfigurationSource configurationSource) { - EnsureReadonly(false); + EnsureMutable(); _valueGenerated = valueGenerated; @@ -287,7 +287,7 @@ public virtual bool IsConcurrencyToken /// public virtual bool? SetIsConcurrencyToken(bool? concurrencyToken, ConfigurationSource configurationSource) { - EnsureReadonly(false); + EnsureMutable(); if (IsConcurrencyToken != concurrencyToken) { @@ -506,7 +506,17 @@ private static bool DefaultIsConcurrencyToken [CA.DisallowNull] public virtual CoreTypeMapping? TypeMapping { - get => _typeMapping; + get + { + if (_typeMapping == null + && IsReadOnly) + { + _typeMapping = ((IModel)DeclaringEntityType.Model).ModelDependencies?.TypeMappingSource.FindMapping(this); + } + + return _typeMapping; + } + [param: NotNull] set => SetTypeMapping(value, ConfigurationSource.Explicit); } diff --git a/src/EFCore/Metadata/Internal/PropertyBase.cs b/src/EFCore/Metadata/Internal/PropertyBase.cs index 040c5f4a68d..a16c83af913 100644 --- a/src/EFCore/Metadata/Internal/PropertyBase.cs +++ b/src/EFCore/Metadata/Internal/PropertyBase.cs @@ -70,7 +70,7 @@ protected PropertyBase( /// /// Indicates whether the model is read-only. /// - protected override bool IsReadonly => DeclaringType.Model.IsModelReadonly; + protected override bool IsReadOnly => DeclaringType.Model.IsModelReadOnly; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -183,7 +183,7 @@ public virtual void SetConfigurationSource(ConfigurationSource configurationSour /// public virtual FieldInfo? SetFieldInfo([CanBeNull] FieldInfo? fieldInfo, ConfigurationSource configurationSource) { - EnsureReadonly(false); + EnsureMutable(); if (Equals(FieldInfo, fieldInfo)) { @@ -303,6 +303,8 @@ public virtual PropertyIndexes PropertyIndexes ref _indexes, this, static property => { + property.EnsureReadOnly(); + var _ = (property.DeclaringType as EntityType)?.Counts; }); @@ -311,6 +313,7 @@ public virtual PropertyIndexes PropertyIndexes { if (value == null) { + EnsureMutable(); // This path should only kick in when the model is still mutable and therefore access does not need // to be thread-safe. _indexes = null; @@ -359,7 +362,11 @@ private void UpdateFieldInfoConfigurationSource(ConfigurationSource configuratio /// public virtual IClrPropertyGetter Getter => NonCapturingLazyInitializer.EnsureInitialized( - ref _getter, this, static p => new ClrPropertyGetterFactory().Create(p)); + ref _getter, this, static property => + { + property.EnsureReadOnly(); + return new ClrPropertyGetterFactory().Create(property); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -369,7 +376,11 @@ public virtual IClrPropertyGetter Getter /// public virtual IClrPropertySetter Setter => NonCapturingLazyInitializer.EnsureInitialized( - ref _setter, this, static p => new ClrPropertySetterFactory().Create(p)); + ref _setter, this, static property => + { + property.EnsureReadOnly(); + return new ClrPropertySetterFactory().Create(property); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -379,7 +390,11 @@ public virtual IClrPropertySetter Setter /// public virtual IClrPropertySetter MaterializationSetter => NonCapturingLazyInitializer.EnsureInitialized( - ref _materializationSetter, this, static p => new ClrPropertyMaterializationSetterFactory().Create(p)); + ref _materializationSetter, this, static property => + { + property.EnsureReadOnly(); + return new ClrPropertyMaterializationSetterFactory().Create(property); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -388,7 +403,11 @@ public virtual IClrPropertySetter MaterializationSetter /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual PropertyAccessors Accessors - => NonCapturingLazyInitializer.EnsureInitialized(ref _accessors, this, static p => new PropertyAccessorsFactory().Create(p)); + => NonCapturingLazyInitializer.EnsureInitialized(ref _accessors, this, static property => + { + property.EnsureReadOnly(); + return new PropertyAccessorsFactory().Create(property); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -398,7 +417,11 @@ public virtual PropertyAccessors Accessors /// public virtual IComparer CurrentValueComparer => NonCapturingLazyInitializer.EnsureInitialized( - ref _currentValueComparer, this, static p => new CurrentValueComparerFactory().Create(p)); + ref _currentValueComparer, this, static property => + { + property.EnsureReadOnly(); + return new CurrentValueComparerFactory().Create(property); + }); private static readonly MethodInfo _containsKeyMethod = typeof(IDictionary).GetRequiredMethod(nameof(IDictionary.ContainsKey), new[] { typeof(string) }); diff --git a/src/EFCore/Metadata/Internal/ServiceProperty.cs b/src/EFCore/Metadata/Internal/ServiceProperty.cs index 7bee0784588..2f95c13a4ed 100644 --- a/src/EFCore/Metadata/Internal/ServiceProperty.cs +++ b/src/EFCore/Metadata/Internal/ServiceProperty.cs @@ -128,7 +128,7 @@ public virtual ServiceParameterBinding? ParameterBinding [CanBeNull] ServiceParameterBinding? parameterBinding, ConfigurationSource configurationSource) { - EnsureReadonly(false); + EnsureMutable(); _parameterBinding = parameterBinding; diff --git a/src/EFCore/Metadata/Internal/SkipNavigation.cs b/src/EFCore/Metadata/Internal/SkipNavigation.cs index 3e0174063fd..d30e17b325f 100644 --- a/src/EFCore/Metadata/Internal/SkipNavigation.cs +++ b/src/EFCore/Metadata/Internal/SkipNavigation.cs @@ -187,7 +187,7 @@ public virtual EntityType? JoinEntityType /// public virtual ForeignKey? SetForeignKey([CanBeNull] ForeignKey? foreignKey, ConfigurationSource configurationSource) { - EnsureReadonly(false); + EnsureMutable(); var oldForeignKey = ForeignKey; var isChanging = foreignKey != ForeignKey; @@ -264,7 +264,7 @@ public virtual void UpdateForeignKeyConfigurationSource(ConfigurationSource conf /// public virtual SkipNavigation? SetInverse([CanBeNull] SkipNavigation? inverse, ConfigurationSource configurationSource) { - EnsureReadonly(false); + EnsureMutable(); var oldInverse = Inverse; var isChanging = inverse != Inverse; @@ -356,7 +356,11 @@ public virtual IClrCollectionAccessor? CollectionAccessor ref _collectionAccessor, ref _collectionAccessorInitialized, this, - static n => new ClrCollectionAccessorFactory().Create(n)); + static navigation => + { + navigation.EnsureReadOnly(); + return new ClrCollectionAccessorFactory().Create(navigation); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -366,7 +370,11 @@ public virtual IClrCollectionAccessor? CollectionAccessor /// public virtual ICollectionLoader ManyToManyLoader => NonCapturingLazyInitializer.EnsureInitialized( - ref _manyToManyLoader, this, static n => new ManyToManyLoaderFactory().Create(n)); + ref _manyToManyLoader, this, static navigation => + { + navigation.EnsureReadOnly(); + return new ManyToManyLoaderFactory().Create(navigation); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/TypeBase.cs b/src/EFCore/Metadata/Internal/TypeBase.cs index e3ac7854798..fb7f731e3ad 100644 --- a/src/EFCore/Metadata/Internal/TypeBase.cs +++ b/src/EFCore/Metadata/Internal/TypeBase.cs @@ -94,7 +94,7 @@ protected TypeBase([NotNull] string name, [NotNull] Type type, [NotNull] Model m /// 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. /// - protected override bool IsReadonly => Model.IsModelReadonly; + protected override bool IsReadOnly => Model.IsModelReadOnly; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -249,7 +249,7 @@ public virtual IReadOnlyDictionary GetRuntimeFields() public virtual string? AddIgnored([NotNull] string name, ConfigurationSource configurationSource) { Check.NotNull(name, nameof(name)); - EnsureReadonly(false); + EnsureMutable(); if (_ignoredMembers.TryGetValue(name, out var existingIgnoredConfigurationSource)) { @@ -317,7 +317,7 @@ public virtual bool IsIgnored(string name) public virtual string? RemoveIgnored(string name) { Check.NotNull(name, nameof(name)); - EnsureReadonly(false); + EnsureMutable(); return _ignoredMembers.Remove(name) ? name : null; } diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 60fa776c5ed..53c8a727004 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -1491,7 +1491,13 @@ public static string MissingInverseManyToManyNavigation([CanBeNull] object? prin principalEntityType, declaringEntityType); /// - /// The model must be finalized before '{method}' can be used. Ensure that either 'OnModelCreating' has completed or, if using a stand-alone 'ModelBuilder', that 'FinalizeModel' has been called. + /// Runtime metadata changes are not allowed when the model hasn't been marked as read-only. + /// + public static string ModelMutable + => GetString("ModelMutable"); + + /// + /// The model must be finalized and its runtime dependencies must be initialized before '{method}' can be used. Ensure that either 'OnModelCreating' has completed or, if using a stand-alone 'ModelBuilder', that 'IModelRuntimeInitializer.Initialize(model.FinalizeModel())' was called. /// public static string ModelNotFinalized([CanBeNull] object? method) => string.Format( @@ -1499,7 +1505,7 @@ public static string ModelNotFinalized([CanBeNull] object? method) method); /// - /// Metadata changes are not allowed as the model has been marked as read-only. + /// Metadata changes are not allowed when the model has been marked as read-only. /// public static string ModelReadOnly => GetString("ModelReadOnly"); diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 8a7fb7edba3..1a98936250e 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -980,11 +980,14 @@ Unable to set up a many-to-many relationship between the entity types '{principalEntityType}' and '{declaringEntityType}' because one of the navigations was not specified. Provide a navigation in the 'HasMany' call in 'OnModelCreating'. + + Runtime metadata changes are not allowed when the model hasn't been marked as read-only. + - The model must be finalized before '{method}' can be used. Ensure that either 'OnModelCreating' has completed or, if using a stand-alone 'ModelBuilder', that 'FinalizeModel' has been called. + The model must be finalized and its runtime dependencies must be initialized before '{method}' can be used. Ensure that either 'OnModelCreating' has completed or, if using a stand-alone 'ModelBuilder', that 'IModelRuntimeInitializer.Initialize(model.FinalizeModel())' was called. - Metadata changes are not allowed as the model has been marked as read-only. + Metadata changes are not allowed when the model has been marked as read-only. The filters '{filter1}' and '{filter2}' have both been configured on the same included navigation. Only one unique filter per navigation is allowed. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393. diff --git a/src/EFCore/Storage/TypeMappingSource.cs b/src/EFCore/Storage/TypeMappingSource.cs index 701334bcf56..66a534b5cdd 100644 --- a/src/EFCore/Storage/TypeMappingSource.cs +++ b/src/EFCore/Storage/TypeMappingSource.cs @@ -149,12 +149,6 @@ protected TypeMappingSource([NotNull] TypeMappingSourceDependencies dependencies /// The type mapping, or if none was found. public override CoreTypeMapping? FindMapping(IProperty property) { - var mapping = property.FindTypeMapping(); - if (mapping != null) - { - return mapping; - } - var principals = property.FindPrincipals(); return FindMappingWithConversion(new TypeMappingInfo(principals), principals); } diff --git a/src/EFCore/ValueGeneration/HiLoValueGeneratorState.cs b/src/EFCore/ValueGeneration/HiLoValueGeneratorState.cs index 29e5f0cbdfb..d182c8cb9c0 100644 --- a/src/EFCore/ValueGeneration/HiLoValueGeneratorState.cs +++ b/src/EFCore/ValueGeneration/HiLoValueGeneratorState.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.ValueGeneration diff --git a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs index dc1afb931de..9ace098eac6 100644 --- a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs +++ b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs @@ -307,11 +307,8 @@ public bool IsConceptualNull(IProperty property) => throw new NotImplementedException(); } - public class FakeEntityType : IEntityType + public class FakeEntityType : Annotatable, IEntityType { - public object this[string name] - => null; - public IEntityType BaseType => throw new NotImplementedException(); @@ -336,9 +333,6 @@ public bool HasSharedClrType public bool IsPropertyBag => throw new NotImplementedException(); - public IAnnotation FindAnnotation(string name) - => throw new NotImplementedException(); - public IForeignKey FindForeignKey(IReadOnlyList properties, IKey principalKey, IEntityType principalEntityType) => throw new NotImplementedException(); @@ -363,9 +357,6 @@ public IServiceProperty FindServiceProperty(string name) public ISkipNavigation FindSkipNavigation(string name) => throw new NotImplementedException(); - public IEnumerable GetAnnotations() - => throw new NotImplementedException(); - public IEnumerable GetForeignKeys() => throw new NotImplementedException(); diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index 44a8cd802a0..46838bd9e7f 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -192,7 +192,7 @@ public void Test_new_annotations_handled_for_properties() }; var columnMapping = - $@"{_nl}.{nameof(RelationalPropertyBuilderExtensions.HasColumnType)}(""default_int_mapping"")"; + $@"{_nl}.{nameof(RelationalPropertyBuilderExtensions.HasColumnType)}(""int"")"; // Add a line here if the code generator is supposed to handle this annotation // Note that other tests should be added to check code is generated correctly @@ -203,11 +203,11 @@ public void Test_new_annotations_handled_for_properties() { CoreAnnotationNames.Unicode, (false, $@"{_nl}.{nameof(PropertyBuilder.IsUnicode)}(false){columnMapping}") }, { CoreAnnotationNames.ValueConverter, (new ValueConverter(v => v, v => (int)v), - $@"{_nl}.{nameof(RelationalPropertyBuilderExtensions.HasColumnType)}(""default_long_mapping"")") + $@"{_nl}.{nameof(RelationalPropertyBuilderExtensions.HasColumnType)}(""bigint"")") }, { CoreAnnotationNames.ProviderClrType, - (typeof(long), $@"{_nl}.{nameof(RelationalPropertyBuilderExtensions.HasColumnType)}(""default_long_mapping"")") + (typeof(long), $@"{_nl}.{nameof(RelationalPropertyBuilderExtensions.HasColumnType)}(""bigint"")") }, { RelationalAnnotationNames.ColumnName, @@ -622,7 +622,7 @@ public void Snapshots_compile() { var generator = CreateMigrationsCodeGenerator(); - var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(skipValidation: true); + var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); modelBuilder.Model.RemoveAnnotation(CoreAnnotationNames.ProductVersion); modelBuilder.Entity( x => @@ -679,10 +679,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity(""Cheese"", b => { b.Property(""Ham"") - .HasColumnType(""just_string(10)""); + .HasColumnType(""nvarchar(10)""); b.Property(""Pickle"") - .HasColumnType(""just_string(10)""); + .HasColumnType(""nvarchar(10)""); b.HasKey(""Ham""); @@ -693,10 +693,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""default_int_mapping""); + .HasColumnType(""int""); b.Property(""PropertyWithValueGenerator"") - .HasColumnType(""default_guid_mapping""); + .HasColumnType(""uniqueidentifier""); b.HasKey(""Id""); diff --git a/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs b/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs index 1e7c9f0c81a..ecd30b0b675 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs @@ -11,7 +11,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Migrations.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal; @@ -85,8 +84,8 @@ var migrationAssembly var historyRepository = new MockHistoryRepository(); var services = RelationalTestHelpers.Instance.CreateContextServices(); - var model = new Model(); - model[RelationalAnnotationNames.RelationalModel] = new RelationalModel(model); + var model = new Model().FinalizeModel(); + model.AddRuntimeAnnotation(RelationalAnnotationNames.RelationalModel, new RelationalModel(model)); return new MigrationsScaffolder( new MigrationsScaffolderDependencies( @@ -122,7 +121,7 @@ var migrationAssembly historyRepository, reporter, new MockProvider(), - new SnapshotModelProcessor(reporter, services.GetRequiredService()), + new SnapshotModelProcessor(reporter, services.GetRequiredService()), new Migrator( migrationAssembly, historyRepository, @@ -133,7 +132,7 @@ var migrationAssembly services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), - services.GetRequiredService(), + services.GetRequiredService(), services.GetRequiredService>(), services.GetRequiredService>(), services.GetRequiredService()))); diff --git a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs index 2f93686531d..eec5f40a290 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reflection; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; @@ -54,7 +55,7 @@ public void Updates_provider_annotations_on_model() var reporter = new TestOperationReporter(); - new SnapshotModelProcessor(reporter, NullConventionSetBuilder.Instance).Process(model); + new SnapshotModelProcessor(reporter, DummyModelRuntimeInitializer.Instance).Process(model); AssertAnnotations(model); AssertAnnotations(entityType); @@ -80,7 +81,7 @@ public void Warns_for_conflicting_annotations() var reporter = new TestOperationReporter(); - new SnapshotModelProcessor(reporter, NullConventionSetBuilder.Instance).Process(model); + new SnapshotModelProcessor(reporter, DummyModelRuntimeInitializer.Instance).Process(model); Assert.Equal("warn: " + DesignStrings.MultipleAnnotationConflict("DefaultSchema"), reporter.Messages.Single()); Assert.Equal(2, model.GetAnnotations().Count()); @@ -101,7 +102,7 @@ public void Warns_for_conflicting_annotations_one_relational() var reporter = new TestOperationReporter(); - new SnapshotModelProcessor(reporter, NullConventionSetBuilder.Instance).Process(model); + new SnapshotModelProcessor(reporter, DummyModelRuntimeInitializer.Instance).Process(model); Assert.Equal("warn: " + DesignStrings.MultipleAnnotationConflict("DefaultSchema"), reporter.Messages.Single()); Assert.Equal(2, model.GetAnnotations().Count()); @@ -122,7 +123,7 @@ public void Does_not_warn_for_duplicate_non_conflicting_annotations() var reporter = new TestOperationReporter(); - new SnapshotModelProcessor(reporter, NullConventionSetBuilder.Instance).Process(model); + new SnapshotModelProcessor(reporter, DummyModelRuntimeInitializer.Instance).Process(model); Assert.Empty(reporter.Messages); @@ -141,7 +142,7 @@ public void Does_not_process_non_v1_models() var reporter = new TestOperationReporter(); - new SnapshotModelProcessor(reporter, NullConventionSetBuilder.Instance).Process(model); + new SnapshotModelProcessor(reporter, DummyModelRuntimeInitializer.Instance).Process(model); Assert.Empty(reporter.Messages); @@ -167,7 +168,7 @@ public void Sets_owned_type_keys() }); var reporter = new TestOperationReporter(); - new SnapshotModelProcessor(reporter, NullConventionSetBuilder.Instance).Process(model); + new SnapshotModelProcessor(reporter, DummyModelRuntimeInitializer.Instance).Process(model); Assert.Empty(reporter.Messages); Assert.Equal( @@ -188,8 +189,8 @@ public void Can_diff_against_older_ownership_model(Type snapshotType) var differ = context.GetService(); var snapshot = (ModelSnapshot)Activator.CreateInstance(snapshotType); var reporter = new TestOperationReporter(); - var setBuilder = SqlServerTestHelpers.Instance.CreateContextServices().GetRequiredService(); - var processor = new SnapshotModelProcessor(reporter, setBuilder); + var modelRuntimeInitializer = SqlServerTestHelpers.Instance.CreateContextServices().GetRequiredService(); + var processor = new SnapshotModelProcessor(reporter, modelRuntimeInitializer); var model = processor.Process(snapshot.Model); var differences = differ.GetDifferences(model.GetRelationalModel(), context.Model.GetRelationalModel()); @@ -207,7 +208,7 @@ public void Can_diff_against_older_sequence_model(Type snapshotType) var differ = context.GetService(); var snapshot = (ModelSnapshot)Activator.CreateInstance(snapshotType); var reporter = new TestOperationReporter(); - var setBuilder = SqlServerTestHelpers.Instance.CreateContextServices().GetRequiredService(); + var setBuilder = SqlServerTestHelpers.Instance.CreateContextServices().GetRequiredService(); var processor = new SnapshotModelProcessor(reporter, setBuilder); var model = processor.Process(snapshot.Model); @@ -265,18 +266,16 @@ private static IEnumerable GetAnnotationNames() .Where(p => p.Name != nameof(RelationalAnnotationNames.Prefix)) .Select(p => (string)p.GetValue(null)); - private class NullConventionSetBuilder : IConventionSetBuilder + private class DummyModelRuntimeInitializer : IModelRuntimeInitializer { - private NullConventionSetBuilder() + private DummyModelRuntimeInitializer() { } - public ConventionSet CreateConventionSet() - { - return new(); - } + public IModel Initialize(IModel model, IDiagnosticsLogger validationLogger) + => model; - public static NullConventionSetBuilder Instance { get; } = new(); + public static DummyModelRuntimeInitializer Instance { get; } = new(); } private class Blog diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 2f21ece065c..6e99aa8c2a2 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -303,7 +303,7 @@ public virtual void Model_annotations_are_stored_in_snapshot() .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn);"), o => { - Assert.Equal(7, o.GetAnnotations().Count()); + Assert.Equal(6, o.GetAnnotations().Count()); Assert.Equal("AnnotationValue", o["AnnotationName"]); }); } @@ -326,7 +326,7 @@ public virtual void Model_default_schema_annotation_is_stored_in_snapshot_as_flu .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn);"), o => { - Assert.Equal(5, o.GetAnnotations().Count()); + Assert.Equal(4, o.GetAnnotations().Count()); Assert.Equal("AnnotationValue", o["AnnotationName"]); Assert.Equal("DefaultSchema", o[RelationalAnnotationNames.DefaultSchema]); }); @@ -430,7 +430,7 @@ public virtual void Entities_are_stored_in_model_snapshot_for_TPT() });"), o => { - Assert.Equal(3, o.GetAnnotations().Count()); + Assert.Equal(2, o.GetAnnotations().Count()); Assert.Equal( "DerivedEntity", @@ -487,7 +487,7 @@ public virtual void Entities_are_stored_in_model_snapshot_for_TPT_with_one_exclu });"), o => { - Assert.Equal(3, o.GetAnnotations().Count()); + Assert.Equal(2, o.GetAnnotations().Count()); Assert.Equal( "DerivedEntity", @@ -597,7 +597,7 @@ public virtual void Sequence_is_stored_in_snapshot_as_fluent_api() .IsCyclic();"), o => { - Assert.Equal(4, o.GetAnnotations().Count()); + Assert.Equal(3, o.GetAnnotations().Count()); }); } @@ -632,7 +632,7 @@ public virtual void CheckConstraint_is_stored_in_snapshot_as_fluent_api() });"), o => { - Assert.Equal(3, o.GetAnnotations().Count()); + Assert.Equal(2, o.GetAnnotations().Count()); }); } @@ -680,7 +680,7 @@ public virtual void CheckConstraint_is_only_stored_in_snapshot_once_for_TPH() });"), o => { - Assert.Equal(3, o.GetAnnotations().Count()); + Assert.Equal(2, o.GetAnnotations().Count()); }); } @@ -710,7 +710,7 @@ public virtual void Model_use_identity_columns() .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn);"), o => { - Assert.Equal(5, o.GetAnnotations().Count()); + Assert.Equal(4, o.GetAnnotations().Count()); Assert.Equal(SqlServerValueGenerationStrategy.IdentityColumn, o.GetValueGenerationStrategy()); Assert.Equal(1, o.GetIdentitySeed()); Assert.Equal(1, o.GetIdentityIncrement()); @@ -731,7 +731,7 @@ public virtual void Model_use_identity_columns_custom_seed() .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn);"), o => { - Assert.Equal(5, o.GetAnnotations().Count()); + Assert.Equal(4, o.GetAnnotations().Count()); Assert.Equal(SqlServerValueGenerationStrategy.IdentityColumn, o.GetValueGenerationStrategy()); Assert.Equal(5, o.GetIdentitySeed()); Assert.Equal(1, o.GetIdentityIncrement()); @@ -752,7 +752,7 @@ public virtual void Model_use_identity_columns_custom_increment() .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn);"), o => { - Assert.Equal(5, o.GetAnnotations().Count()); + Assert.Equal(4, o.GetAnnotations().Count()); Assert.Equal(SqlServerValueGenerationStrategy.IdentityColumn, o.GetValueGenerationStrategy()); Assert.Equal(1, o.GetIdentitySeed()); Assert.Equal(5, o.GetIdentityIncrement()); @@ -773,7 +773,7 @@ public virtual void Model_use_identity_columns_custom_seed_increment() .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn);"), o => { - Assert.Equal(5, o.GetAnnotations().Count()); + Assert.Equal(4, o.GetAnnotations().Count()); Assert.Equal(SqlServerValueGenerationStrategy.IdentityColumn, o.GetValueGenerationStrategy()); Assert.Equal(5, o.GetIdentitySeed()); Assert.Equal(5, o.GetIdentityIncrement()); @@ -812,7 +812,7 @@ public virtual void EntityType_annotations_are_stored_in_snapshot() });"), o => { - Assert.Equal(5, o.GetEntityTypes().First().GetAnnotations().Count()); + Assert.Equal(3, o.GetEntityTypes().First().GetAnnotations().Count()); Assert.Equal("AnnotationValue", o.GetEntityTypes().First()["AnnotationName"]); }); } @@ -2597,7 +2597,8 @@ public virtual void Property_ValueGenerated_non_identity() x => { x.Property(e => e.Id).Metadata.SetValueGenerationStrategy(SqlServerValueGenerationStrategy.None); - x.Property(e => e.Day).ValueGeneratedOnAdd(); + x.Property(e => e.Day).ValueGeneratedOnAdd() + .Metadata.SetValueGenerationStrategy(SqlServerValueGenerationStrategy.None); }), AddBoilerPlate( GetHeading() @@ -3243,7 +3244,7 @@ public virtual void Property_multiple_annotations_are_stored_in_snapshot() o => { var property = o.GetEntityTypes().First().FindProperty("AlternateId"); - Assert.Equal(5, property.GetAnnotations().Count()); + Assert.Equal(3, property.GetAnnotations().Count()); Assert.Equal("AnnotationValue", property["AnnotationName"]); Assert.Equal("CName", property["Relational:ColumnName"]); Assert.Equal("int", property["Relational:ColumnType"]); @@ -3569,7 +3570,7 @@ public virtual void Key_multiple_annotations_are_stored_in_snapshot() o => { var key = o.GetEntityTypes().First().GetKeys().Where(k => !k.IsPrimaryKey()).First(); - Assert.Equal(3, key.GetAnnotations().Count()); + Assert.Equal(2, key.GetAnnotations().Count()); Assert.Equal("AnnotationValue", key["AnnotationName"]); Assert.Equal("IndexName", key["Relational:Name"]); }); @@ -3752,7 +3753,7 @@ public virtual void Index_multiple_annotations_are_stored_in_snapshot() { var index = o.GetEntityTypes().First().GetIndexes().First(); Assert.Equal("IndexName", index.Name); - Assert.Equal(2, index.GetAnnotations().Count()); + Assert.Single(index.GetAnnotations()); Assert.Equal("AnnotationValue", index["AnnotationName"]); Assert.Null(index["RelationalAnnotationNames.Name"]); }); @@ -4472,7 +4473,7 @@ public virtual void ForeignKey_multiple_annotations_are_stored_in_snapshot() o => { var fk = o.FindEntityType(typeof(EntityWithTwoProperties)).GetForeignKeys().First(); - Assert.Equal(3, fk.GetAnnotations().Count()); + Assert.Equal(2, fk.GetAnnotations().Count()); Assert.Equal("AnnotationValue", fk["AnnotationName"]); Assert.Equal("Constraint", fk["Relational:Name"]); }); @@ -5381,6 +5382,12 @@ protected void Test(IModel model, string expectedCode, Action assert) protected void Test(IModel model, string expectedCode, Action assert) { + var serviceProvider = SqlServerTestHelpers.Instance.CreateContextServices( + new ServiceCollection() + .AddEntityFrameworkSqlServerNetTopologySuite()); + + serviceProvider.GetService().Initialize(model, validationLogger: null); + var generator = CreateMigrationsGenerator(); var code = generator.GenerateSnapshot("RootNamespace", typeof(DbContext), "Snapshot", model); Assert.Equal(expectedCode, code, ignoreLineEndingDifferences: true); @@ -5417,7 +5424,7 @@ protected IModel BuildModelFromSnapshotSource(string code) var services = SqlServerTestHelpers.Instance.CreateContextServices(); - var processor = new SnapshotModelProcessor(new TestOperationReporter(), services.GetService()); + var processor = new SnapshotModelProcessor(new TestOperationReporter(), services.GetService()); return processor.Process(builder.Model); } diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/ModelCodeGeneratorTestBase.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/ModelCodeGeneratorTestBase.cs index 213627ea0f8..31c30ae5966 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/ModelCodeGeneratorTestBase.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/ModelCodeGeneratorTestBase.cs @@ -22,7 +22,7 @@ protected void Test( Action assertScaffold, Action assertModel) { - var modelBuilder = SqlServerTestHelpers.Instance.CreateConventionBuilder(skipValidation: true); + var modelBuilder = SqlServerTestHelpers.Instance.CreateConventionBuilder(); modelBuilder.Model.RemoveAnnotation(CoreAnnotationNames.ProductVersion); buildModel(modelBuilder); var _ = modelBuilder.Model.GetEntityTypeErrors(); diff --git a/test/EFCore.InMemory.Tests/InMemoryValueGeneratorSelectorTest.cs b/test/EFCore.InMemory.Tests/InMemoryValueGeneratorSelectorTest.cs index 590c195cc33..32535b054b6 100644 --- a/test/EFCore.InMemory.Tests/InMemoryValueGeneratorSelectorTest.cs +++ b/test/EFCore.InMemory.Tests/InMemoryValueGeneratorSelectorTest.cs @@ -105,7 +105,7 @@ private static IModel BuildModel(bool generateValues = true) property.ValueGenerated = generateValues ? ValueGenerated.OnAdd : ValueGenerated.Never; } - return model.FinalizeModel(); + return InMemoryTestHelpers.Instance.Finalize(builder); } private class AnEntity diff --git a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs index 3b1647783f3..05d05f30d84 100644 --- a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs @@ -306,17 +306,12 @@ public virtual void Can_get_active_provider() protected virtual void DiffSnapshot(ModelSnapshot snapshot, DbContext context) { - var dependencies = context.GetService(); - var relationalDependencies = context.GetService(); - var typeMappingConvention = new TypeMappingConvention(dependencies); - typeMappingConvention.ProcessModelFinalizing(((IConventionModel)snapshot.Model).Builder, null); - - var relationalModelConvention = new RelationalModelConvention(dependencies, relationalDependencies); - var sourceModel = relationalModelConvention.ProcessModelFinalized(snapshot.Model); + var sourceModel = context.GetService().Initialize( + ((IMutableModel)snapshot.Model).FinalizeModel(), validationLogger: null); var modelDiffer = context.GetService(); var operations = modelDiffer.GetDifferences( - ((IMutableModel)sourceModel).FinalizeModel().GetRelationalModel(), + sourceModel.GetRelationalModel(), context.Model.GetRelationalModel()); Assert.Equal(0, operations.Count); diff --git a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsSqlGeneratorTestBase.cs b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsSqlGeneratorTestBase.cs index fbb66564ec3..206ebbd129b 100644 --- a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsSqlGeneratorTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsSqlGeneratorTestBase.cs @@ -7,8 +7,6 @@ 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.Metadata.Internal; using Microsoft.EntityFrameworkCore.Migrations.Operations; using Microsoft.EntityFrameworkCore.TestUtilities; @@ -771,14 +769,7 @@ protected virtual void Generate( modelBuilder.Model.RemoveAnnotation(CoreAnnotationNames.ProductVersion); buildAction(modelBuilder); - model = modelBuilder.Model; - var conventionSet = services.GetRequiredService().CreateConventionSet(); - - var typeMappingConvention = conventionSet.ModelFinalizingConventions.OfType().FirstOrDefault(); - typeMappingConvention.ProcessModelFinalizing(((IConventionModel)model).Builder, null); - - var relationalModelConvention = conventionSet.ModelFinalizedConventions.OfType().First(); - model = relationalModelConvention.ProcessModelFinalized((IConventionModel)model); + model = services.GetService().Initialize(modelBuilder.FinalizeModel(), validationLogger: null); } var batch = services.GetRequiredService().Generate(operation, model, options); diff --git a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs index 8261c108d7e..081d247b504 100644 --- a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs @@ -3,9 +3,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions; @@ -1602,26 +1604,39 @@ protected virtual Task Test( Action buildTargetAction, Action asserter) { + var context = CreateContext(); + var modelDiffer = context.GetService(); + var modelRuntimeInitializer = context.GetService(); + // Build the source and target models. Add current/latest product version if one wasn't set. var sourceModelBuilder = CreateConventionlessModelBuilder(); buildCommonAction(sourceModelBuilder); buildSourceAction(sourceModelBuilder); - var sourceModel = sourceModelBuilder.FinalizeModel(); + var sourceModel = modelRuntimeInitializer.Initialize(sourceModelBuilder.FinalizeModel(), validationLogger: null); var targetModelBuilder = CreateConventionlessModelBuilder(); buildCommonAction(targetModelBuilder); buildTargetAction(targetModelBuilder); - var targetModel = targetModelBuilder.FinalizeModel(); - var context = CreateContext(); - var serviceProvider = ((IInfrastructure)context).Instance; - var modelDiffer = serviceProvider.GetRequiredService(); + var targetModel = modelRuntimeInitializer.Initialize(targetModelBuilder.FinalizeModel(), validationLogger: null); // CreateValidationLogger() var operations = modelDiffer.GetDifferences(sourceModel.GetRelationalModel(), targetModel.GetRelationalModel()); return Test(sourceModel, targetModel, operations, asserter); } + protected DiagnosticsLogger CreateValidationLogger(bool sensitiveDataLoggingEnabled = false) + { + var options = new LoggingOptions(); + options.Initialize(new DbContextOptionsBuilder().EnableSensitiveDataLogging(sensitiveDataLoggingEnabled).Options); + return new DiagnosticsLogger( + Fixture.TestSqlLoggerFactory, + options, + new DiagnosticListener("Fake"), + Fixture.TestHelpers.LoggingDefinitions, + new NullDbContextLogger()); + } + protected virtual Task Test( Action buildSourceAction, MigrationOperation operation, @@ -1640,7 +1655,9 @@ protected virtual Task Test( sourceModelBuilder.Model.SetProductVersion(ProductInfo.GetVersion()); } - var sourceModel = sourceModelBuilder.FinalizeModel(); + var context = CreateContext(); + var modelRuntimeInitializer = context.GetService(); + var sourceModel = modelRuntimeInitializer.Initialize(sourceModelBuilder.FinalizeModel(), validationLogger: null); return Test(sourceModel, targetModel: null, operations, asserter); } @@ -1712,17 +1729,7 @@ public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; } - protected virtual ModelBuilder CreateConventionlessModelBuilder(bool sensitiveDataLoggingEnabled = false) - { - var conventionSet = new ConventionSet(); - - var dependencies = Fixture.TestHelpers.CreateContextServices().GetRequiredService(); - var relationalDependencies = Fixture.TestHelpers.CreateContextServices() - .GetRequiredService(); - conventionSet.ModelFinalizingConventions.Add(new TypeMappingConvention(dependencies)); - conventionSet.ModelFinalizedConventions.Add(new RelationalModelConvention(dependencies, relationalDependencies)); - - return new ModelBuilder(conventionSet); - } + protected virtual ModelBuilder CreateConventionlessModelBuilder() + => new ModelBuilder(new ConventionSet()); } } diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs index 5d4cbfe59a1..d5b82803a50 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs @@ -11,9 +11,9 @@ using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Xunit; @@ -467,8 +467,8 @@ public virtual void Detects_duplicate_column_names() { var modelBuilder = CreateConventionalModelBuilder(); - GenerateMapping(modelBuilder.Entity().Property(b => b.Id).HasColumnName("Name").Metadata); - GenerateMapping(modelBuilder.Entity().Property(d => d.Name).HasColumnName("Name").IsRequired().Metadata); + modelBuilder.Entity().Property(b => b.Id).HasColumnName("Name"); + modelBuilder.Entity().Property(d => d.Name).HasColumnName("Name").IsRequired(); VerifyError( RelationalStrings.DuplicateColumnNameDataTypeMismatch( @@ -483,8 +483,8 @@ public virtual void Detects_duplicate_columns_in_derived_types_with_different_ty var modelBuilder = CreateConventionalModelBuilder(); modelBuilder.Entity(); - GenerateMapping(modelBuilder.Entity().Property(c => c.Type).HasColumnName("Type").IsRequired().Metadata); - GenerateMapping(modelBuilder.Entity().Property(d => d.Type).HasColumnName("Type").Metadata); + modelBuilder.Entity().Property(c => c.Type).HasColumnName("Type").IsRequired(); + modelBuilder.Entity().Property(d => d.Type).HasColumnName("Type"); VerifyError( RelationalStrings.DuplicateColumnNameDataTypeMismatch( @@ -498,8 +498,8 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe var modelBuilder = CreateConventionalModelBuilder(); modelBuilder.Entity(); - GenerateMapping(modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").HasMaxLength(30).Metadata); - GenerateMapping(modelBuilder.Entity().Property(d => d.Breed).HasColumnName("Breed").HasMaxLength(15).Metadata); + modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").HasMaxLength(30); + modelBuilder.Entity().Property(d => d.Breed).HasColumnName("Breed").HasMaxLength(15); VerifyError( RelationalStrings.DuplicateColumnNameMaxLengthMismatch( @@ -513,8 +513,8 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe var modelBuilder = CreateConventionalModelBuilder(); modelBuilder.Entity(); - GenerateMapping(modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").IsUnicode().Metadata); - GenerateMapping(modelBuilder.Entity().Property(d => d.Breed).HasColumnName("Breed").Metadata); + modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").IsUnicode(); + modelBuilder.Entity().Property(d => d.Breed).HasColumnName("Breed"); VerifyError( RelationalStrings.DuplicateColumnNameUnicodenessMismatch( @@ -527,8 +527,8 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe var modelBuilder = CreateConventionalModelBuilder(); modelBuilder.Entity(); - GenerateMapping(modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").IsFixedLength().Metadata); - GenerateMapping(modelBuilder.Entity().Property(d => d.Breed).HasColumnName("Breed").Metadata); + modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").IsFixedLength(); + modelBuilder.Entity().Property(d => d.Breed).HasColumnName("Breed"); VerifyError( RelationalStrings.DuplicateColumnNameFixedLengthMismatch( @@ -541,8 +541,8 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe var modelBuilder = CreateConventionalModelBuilder(); modelBuilder.Entity(); - GenerateMapping(modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").IsConcurrencyToken().Metadata); - GenerateMapping(modelBuilder.Entity().Property(d => d.Breed).HasColumnName("Breed").Metadata); + modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").IsConcurrencyToken(); + modelBuilder.Entity().Property(d => d.Breed).HasColumnName("Breed"); VerifyError( RelationalStrings.DuplicateColumnNameConcurrencyTokenMismatch( @@ -1937,13 +1937,6 @@ public virtual void Passes_for_relational_override_without_inheritance() Assert.DoesNotContain(LoggerFactory.Log, l => l.Level == LogLevel.Warning); } - private static void GenerateMapping(IMutableProperty property) - => property.SetTypeMapping( - new TestRelationalTypeMappingSource( - TestServiceFactory.Instance.Create(), - TestServiceFactory.Instance.Create()) - .FindMapping(property)); - protected override void SetBaseType(IMutableEntityType entityType, IMutableEntityType baseEntityType) { base.SetBaseType(entityType, baseEntityType); diff --git a/test/EFCore.Relational.Tests/Metadata/Conventions/Internal/TableValuedDbFunctionConventionTest.cs b/test/EFCore.Relational.Tests/Metadata/Conventions/Internal/TableValuedDbFunctionConventionTest.cs index 98d1ceb0287..ba4c9f1714f 100644 --- a/test/EFCore.Relational.Tests/Metadata/Conventions/Internal/TableValuedDbFunctionConventionTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/Conventions/Internal/TableValuedDbFunctionConventionTest.cs @@ -25,8 +25,7 @@ public void Configures_return_entity_as_not_mapped() BindingFlags.NonPublic | BindingFlags.Static)); modelBuilder.Entity().HasNoKey(); - - var model = modelBuilder.FinalizeModel(); + var model = Finalize(modelBuilder); var entityType = model.FindEntityType(typeof(KeylessEntity)); @@ -44,7 +43,7 @@ public void Finds_existing_entity_type() nameof(GetEntities), BindingFlags.NonPublic | BindingFlags.Static)); - var model = modelBuilder.FinalizeModel(); + var model = Finalize(modelBuilder); var entityType = model.FindEntityType(typeof(TestEntity)); @@ -66,7 +65,7 @@ public void Throws_when_adding_a_function_returning_an_owned_type() RelationalStrings.DbFunctionInvalidIQueryableOwnedReturnType( "Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.TableValuedDbFunctionConventionTest.GetKeylessEntities(System.Int32)", typeof(KeylessEntity).ShortDisplayName()), - Assert.Throws(() => modelBuilder.FinalizeModel()).Message); + Assert.Throws(() => Finalize(modelBuilder)).Message); } [ConditionalFact] @@ -83,7 +82,7 @@ public void Throws_when_adding_a_function_returning_an_existing_owned_type() RelationalStrings.DbFunctionInvalidIQueryableOwnedReturnType( "Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.TableValuedDbFunctionConventionTest.GetKeylessEntities(System.Int32)", typeof(KeylessEntity).ShortDisplayName()), - Assert.Throws(() => modelBuilder.FinalizeModel()).Message); + Assert.Throws(() => Finalize(modelBuilder)).Message); } [ConditionalFact] @@ -99,12 +98,15 @@ public void Throws_when_adding_a_function_returning_a_scalar() RelationalStrings.DbFunctionInvalidIQueryableReturnType( "Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.TableValuedDbFunctionConventionTest.GetScalars(System.Int32)", typeof(IQueryable).ShortDisplayName()), - Assert.Throws(() => modelBuilder.FinalizeModel()).Message); + Assert.Throws(() => Finalize(modelBuilder)).Message); } private static ModelBuilder CreateModelBuilder() => RelationalTestHelpers.Instance.CreateConventionBuilder(); + private static IModel Finalize(ModelBuilder modelBuilder) + => RelationalTestHelpers.Instance.Finalize(modelBuilder); + private static IQueryable GetEntities(int id) => throw new NotImplementedException(); diff --git a/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs b/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs index 338f3d9c561..edc09e93a78 100644 --- a/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs +++ b/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs @@ -23,13 +23,13 @@ namespace Microsoft.EntityFrameworkCore.Metadata { public class DbFunctionMetadataTests { - public class Foo + protected class Foo { public int I { get; set; } public int J { get; set; } } - public class MyNonDbContext + protected class MyNonDbContext { public int NonStatic() { @@ -42,7 +42,7 @@ public static int DuplicateNameTest() } } - public class MyBaseContext : DbContext + protected class MyBaseContext : DbContext { public static readonly string[] FunctionNames = { @@ -115,7 +115,7 @@ public virtual int VirtualBase() => throw new Exception(); } - public class MyDerivedContext : MyBaseContext + protected class MyDerivedContext : MyBaseContext { public static new readonly string[] FunctionNames = { @@ -325,15 +325,13 @@ var dup2methodInfo [ConditionalFact] public virtual void Finds_DbFunctions_on_DbContext() { - var context = new MyDerivedContext(); - var modelBuilder = GetModelBuilder(context); - - modelBuilder.FinalizeModel(); + var model = RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService() + .Initialize(GetModelBuilder(new MyDerivedContext()).FinalizeModel(), validationLogger: null); foreach (var function in MyBaseContext.FunctionNames) { Assert.NotNull( - modelBuilder.Model.FindDbFunction( + model.FindDbFunction( typeof(MyBaseContext).GetMethod( function, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))); } @@ -341,7 +339,7 @@ public virtual void Finds_DbFunctions_on_DbContext() foreach (var function in MyDerivedContext.FunctionNames) { Assert.NotNull( - modelBuilder.Model.FindDbFunction( + model.FindDbFunction( typeof(MyDerivedContext).GetMethod( function, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))); } @@ -681,7 +679,8 @@ var queryableNoParams var functionName = modelBuilder.HasDbFunction(queryableNoParams).Metadata.ModelName; - var model = modelBuilder.FinalizeModel(); + var model = RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService() + .Initialize(modelBuilder.FinalizeModel(), validationLogger: null); var function = model.FindDbFunction(functionName); var entityType = model.FindEntityType(typeof(Foo)); @@ -863,17 +862,20 @@ public void DbFunction_Queryable_custom_translation() private ModelBuilder GetModelBuilder(DbContext dbContext = null) { + if (dbContext == null) + { + return RelationalTestHelpers.Instance.CreateConventionBuilder(); + } + var conventionSet = new ConventionSet(); - var dependencies = CreateDependencies() - .With(new CurrentDbContext(dbContext ?? new DbContext(new DbContextOptions()))); + var dependencies = CreateDependencies().With(new CurrentDbContext(dbContext)); var relationalDependencies = CreateRelationalDependencies(); var dbFunctionAttributeConvention = new RelationalDbFunctionAttributeConvention(dependencies, relationalDependencies); conventionSet.ModelInitializedConventions.Add(dbFunctionAttributeConvention); conventionSet.ModelFinalizingConventions.Add(dbFunctionAttributeConvention); conventionSet.ModelFinalizingConventions.Add(new DbFunctionTypeMappingConvention(dependencies, relationalDependencies)); conventionSet.ModelFinalizingConventions.Add(new TableValuedDbFunctionConvention(dependencies, relationalDependencies)); - conventionSet.ModelFinalizedConventions.Add(new RelationalModelConvention(dependencies, relationalDependencies)); return new ModelBuilder(conventionSet); } diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index 79791a0a8fb..c3371f9ad2e 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -22,7 +22,7 @@ public void GetRelationalModel_throws_if_convention_has_not_run() var modelBuilder = CreateConventionModelBuilder(); Assert.Equal( - RelationalStrings.DatabaseModelMissing, + CoreStrings.ModelNotFinalized("GetRelationalModel"), Assert.Throws( () => modelBuilder.Model.GetRelationalModel()).Message); } @@ -688,7 +688,7 @@ private IRelationalModel CreateTestModel(bool mapToTables = false, bool mapToVie } }); - return modelBuilder.FinalizeModel().GetRelationalModel(); + return Finalize(modelBuilder); } [ConditionalFact] @@ -710,7 +710,7 @@ public void Can_use_relational_model_with_keyless_TPH() cb.Property(s => s.Speciality).IsRequired(); }); - var model = modelBuilder.FinalizeModel().GetRelationalModel(); + var model = Finalize(modelBuilder); Assert.Equal(2, model.Model.GetEntityTypes().Count()); Assert.Empty(model.Tables); @@ -754,7 +754,7 @@ public void Can_use_relational_model_with_SQL_queries() cb.HasNoKey(); }); - var model = modelBuilder.FinalizeModel().GetRelationalModel(); + var model = Finalize(modelBuilder); Assert.Single(model.Model.GetEntityTypes()); Assert.Single(model.Queries); @@ -829,7 +829,7 @@ public void Can_use_relational_model_with_functions() typeof(RelationalModelTest).GetMethod( nameof(GetOrdersForCustomer), BindingFlags.NonPublic | BindingFlags.Static)); - var model = modelBuilder.FinalizeModel().GetRelationalModel(); + var model = Finalize(modelBuilder); Assert.Single(model.Model.GetEntityTypes()); Assert.Equal(2, model.Functions.Count()); @@ -904,6 +904,9 @@ public void Can_use_relational_model_with_functions() Assert.Same(tvfDbFunction.Parameters.Single(), tvfFunction.Parameters.Single().DbFunctionParameters.Single()); } + private static IRelationalModel Finalize(ModelBuilder modelBuilder) + => RelationalTestHelpers.Instance.Finalize(modelBuilder).GetRelationalModel(); + protected virtual ModelBuilder CreateConventionModelBuilder() => RelationalTestHelpers.Instance.CreateConventionBuilder(); diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs index ef3511c3744..d8c9c83ab8a 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs @@ -78,6 +78,10 @@ protected void Execute( builderOptionsAction(targetOptionsBuilder); } + var modelRuntimeInitializer = TestHelpers.CreateContextServices().GetService(); + sourceModel = modelRuntimeInitializer.Initialize(sourceModel, validationLogger: null); + targetModel = modelRuntimeInitializer.Initialize(targetModel, validationLogger: null); + var modelDiffer = CreateModelDiffer(targetOptionsBuilder.Options); var operationsUp = modelDiffer.GetDifferences(sourceModel.GetRelationalModel(), targetModel.GetRelationalModel()); @@ -146,21 +150,8 @@ protected static T[][] ToJaggedArray(T[,] twoDimensionalArray, bool firstDime protected virtual ModelBuilder CreateModelBuilder(bool skipConventions) => skipConventions - ? new ModelBuilder(CreateEmptyConventionSet()) - : TestHelpers.CreateConventionBuilder(skipValidation: true); - - private ConventionSet CreateEmptyConventionSet() - { - var conventions = new ConventionSet(); - var conventionSetDependencies = TestHelpers.CreateContextServices() - .GetRequiredService(); - var relationalConventionSetDependencies = TestHelpers.CreateContextServices() - .GetRequiredService(); - conventions.ModelFinalizingConventions.Add(new TypeMappingConvention(conventionSetDependencies)); - conventions.ModelFinalizedConventions.Add( - new RelationalModelConvention(conventionSetDependencies, relationalConventionSetDependencies)); - return conventions; - } + ? new ModelBuilder(new ConventionSet()) + : TestHelpers.CreateConventionBuilder(); protected virtual MigrationsModelDiffer CreateModelDiffer(DbContextOptions options) { diff --git a/test/EFCore.Relational.Tests/Storage/RelationalParameterBuilderTest.cs b/test/EFCore.Relational.Tests/Storage/RelationalParameterBuilderTest.cs index 1836d189ef5..9c935eda87c 100644 --- a/test/EFCore.Relational.Tests/Storage/RelationalParameterBuilderTest.cs +++ b/test/EFCore.Relational.Tests/Storage/RelationalParameterBuilderTest.cs @@ -3,10 +3,12 @@ using System.Collections.Generic; using System.Data; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage.Internal; using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.Extensions.DependencyInjection; using Xunit; namespace Microsoft.EntityFrameworkCore.Storage @@ -77,13 +79,15 @@ public void Can_add_type_mapped_parameter_by_property(bool nullable) TestServiceFactory.Instance.Create(), TestServiceFactory.Instance.Create()); - var property = ((IMutableModel)new Model()).AddEntityType("MyType").AddProperty("MyProp", typeof(string)); + var model = (IMutableModel)new Model(); + var property = model.AddEntityType("MyType").AddProperty("MyProp", typeof(string)); property.IsNullable = nullable; - property.SetTypeMapping(GetMapping(typeMapper, property)); + + RelationalTestHelpers.Instance.CreateContextServices().GetRequiredService() + .Initialize(model.FinalizeModel(), validationLogger: null); var parameterBuilder = new RelationalCommandBuilder( - new RelationalCommandBuilderDependencies( - typeMapper)); + new RelationalCommandBuilderDependencies(typeMapper)); parameterBuilder.AddParameter( "InvariantName", @@ -98,7 +102,7 @@ public void Can_add_type_mapped_parameter_by_property(bool nullable) Assert.NotNull(parameter); Assert.Equal("InvariantName", parameter.InvariantName); Assert.Equal("Name", parameter.Name); - Assert.Equal(GetMapping(typeMapper, property), parameter.RelationalTypeMapping); + Assert.Equal(property.GetTypeMapping(), parameter.RelationalTypeMapping); Assert.Equal(nullable, parameter.IsNullable); } diff --git a/test/EFCore.Relational.Tests/TestUtilities/RelationalTestHelpers.cs b/test/EFCore.Relational.Tests/TestUtilities/RelationalTestHelpers.cs index e0922fc1aaa..b3da75deb9c 100644 --- a/test/EFCore.Relational.Tests/TestUtilities/RelationalTestHelpers.cs +++ b/test/EFCore.Relational.Tests/TestUtilities/RelationalTestHelpers.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.TestUtilities.FakeProvider; using Microsoft.Extensions.DependencyInjection; diff --git a/test/EFCore.Relational.Tests/Update/ModificationCommandComparerTest.cs b/test/EFCore.Relational.Tests/Update/ModificationCommandComparerTest.cs index 2491856bff7..1e602b27f09 100644 --- a/test/EFCore.Relational.Tests/Update/ModificationCommandComparerTest.cs +++ b/test/EFCore.Relational.Tests/Update/ModificationCommandComparerTest.cs @@ -153,7 +153,6 @@ public void Compare_returns_0_only_for_entries_that_have_same_key_values() FlagsEnum.Default, FlagsEnum.First | FlagsEnum.Second); Compare_returns_0_only_for_entries_that_have_same_key_values_generic(new Guid().ToByteArray(), Guid.NewGuid().ToByteArray()); - Compare_returns_0_only_for_entries_that_have_same_key_values_generic(new[] { 1 }, new[] { 2 }); Compare_returns_0_only_for_entries_that_have_same_key_values_generic("1", "2"); } diff --git a/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs b/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs index f4739088536..e3787cb7ab8 100644 --- a/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs +++ b/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs @@ -55,7 +55,12 @@ public virtual ModelBuilder CreateModelBuilder() } protected virtual IModel Validate(ModelBuilder modelBuilder) - => modelBuilder.FinalizeModel(); + { + var context = CreateContext(); + var modelRuntimeInitializer = context.GetService(); + var logger = context.GetService>(); + return modelRuntimeInitializer.Initialize(modelBuilder.FinalizeModel(), logger); + } protected class Person { diff --git a/test/EFCore.Specification.Tests/OptimisticConcurrencyTestBase.cs b/test/EFCore.Specification.Tests/OptimisticConcurrencyTestBase.cs index f26db3bd871..f6991f5d04b 100644 --- a/test/EFCore.Specification.Tests/OptimisticConcurrencyTestBase.cs +++ b/test/EFCore.Specification.Tests/OptimisticConcurrencyTestBase.cs @@ -32,11 +32,13 @@ public virtual void External_model_builder_uses_validation() { var modelBuilder = Fixture.CreateModelBuilder(); modelBuilder.Entity("Dummy"); + var model = modelBuilder.FinalizeModel(); + + var context = new F1Context(new DbContextOptionsBuilder(Fixture.CreateOptions()).UseModel(model).Options); Assert.Equal( CoreStrings.EntityRequiresKey("Dummy (Dictionary)"), - Assert.Throws - (() => modelBuilder.FinalizeModel()).Message); + Assert.Throws(() => context.Model).Message); } [ConditionalFact] diff --git a/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs b/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs index cf4129f0a69..7659450c63f 100644 --- a/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs @@ -9,7 +9,6 @@ using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; @@ -136,19 +135,19 @@ public IMutableModel BuildModelFor() return builder.Model; } - public ModelBuilder CreateConventionBuilder(bool skipValidation = false) + public IModel Finalize(ModelBuilder modelBuilder, bool skipValidation = false) { - var conventionSet = CreateConventionSetBuilder().CreateConventionSet(); + var contextServices = CreateContextServices(); - if (skipValidation) - { - // Use public API to remove convention, issue #214 - ConventionSet.Remove(conventionSet.ModelFinalizedConventions, typeof(ValidatingConvention)); - } - - return new ModelBuilder(conventionSet); + var modelRuntimeInitializer = contextServices.GetRequiredService(); + return modelRuntimeInitializer.Initialize(modelBuilder.FinalizeModel(), skipValidation + ? null + : new TestLogger(LoggingDefinitions)); } + public ModelBuilder CreateConventionBuilder() + => new ModelBuilder(CreateConventionSetBuilder().CreateConventionSet()); + public virtual IConventionSetBuilder CreateConventionSetBuilder() => CreateContextServices().GetRequiredService(); diff --git a/test/EFCore.Specification.Tests/TestUtilities/TestLogger.cs b/test/EFCore.Specification.Tests/TestUtilities/TestLogger.cs index 479caff80eb..d6f738c0cdc 100644 --- a/test/EFCore.Specification.Tests/TestUtilities/TestLogger.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/TestLogger.cs @@ -4,18 +4,31 @@ using System; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Diagnostics.Internal; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.Extensions.Logging; using Xunit; namespace Microsoft.EntityFrameworkCore.TestUtilities { - public class TestLogger : TestLoggerBase, IDiagnosticsLogger, ILogger - where TDefinitions : LoggingDefinitions, new() + public class TestLogger : TestLoggerBase, IDiagnosticsLogger, ILogger { + public TestLogger(LoggingDefinitions definitions) + { + Definitions = definitions; + } + public ILoggingOptions Options => new LoggingOptions(); + public bool ShouldLogSensitiveData() + => false; + + public ILogger Logger + => this; + + public virtual LoggingDefinitions Definitions { get; } + + public IInterceptors Interceptors { get; } + public bool IsEnabled(LogLevel logLevel) => EnabledFor == logLevel; @@ -34,15 +47,5 @@ public void Log( Assert.Equal(LoggedEvent, eventId); Message = formatter(state, exception); } - - public bool ShouldLogSensitiveData() - => false; - - public ILogger Logger - => this; - - public virtual LoggingDefinitions Definitions { get; } = new TDefinitions(); - - public IInterceptors Interceptors { get; } } } diff --git a/test/EFCore.Specification.Tests/TestUtilities/TestLogger`.cs b/test/EFCore.Specification.Tests/TestUtilities/TestLogger`.cs index e9324d85851..59bb776bced 100644 --- a/test/EFCore.Specification.Tests/TestUtilities/TestLogger`.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/TestLogger`.cs @@ -5,9 +5,16 @@ namespace Microsoft.EntityFrameworkCore.TestUtilities { - public class TestLogger : TestLogger, IDiagnosticsLogger - where TCategory : LoggerCategory, new() + public class TestLogger : TestLogger where TDefinitions : LoggingDefinitions, new() { + public TestLogger() + : base(new TDefinitions()) + { } + + public TestLogger(LoggingDefinitions definitions) + : base(definitions) + { + } } } diff --git a/test/EFCore.Specification.Tests/TestUtilities/TestLogger``.cs b/test/EFCore.Specification.Tests/TestUtilities/TestLogger``.cs new file mode 100644 index 00000000000..548be4f58b7 --- /dev/null +++ b/test/EFCore.Specification.Tests/TestUtilities/TestLogger``.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.EntityFrameworkCore.Diagnostics; + +namespace Microsoft.EntityFrameworkCore.TestUtilities +{ + public class TestLogger : TestLogger, IDiagnosticsLogger + where TCategory : LoggerCategory, new() + where TDefinitions : LoggingDefinitions, new() + { + public TestLogger() + : base(new TDefinitions()) + { + } + + public TestLogger(LoggingDefinitions definitions) + : base(definitions) + { + } + } +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs index 1012587d3bb..52228090eb6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -37,20 +37,15 @@ // ReSharper disable UnusedMember.Local namespace Microsoft.EntityFrameworkCore.Query { - public class QueryBugsTest : NonSharedModelTestBase, IClassFixture + public class QueryBugsTest : NonSharedModelTestBase { // ReSharper disable once UnusedParameter.Local #pragma warning disable IDE0060 // Remove unused parameter - public QueryBugsTest(SqlServerFixture fixture, ITestOutputHelper testOutputHelper) + public QueryBugsTest(ITestOutputHelper testOutputHelper) #pragma warning restore IDE0060 // Remove unused parameter { - Fixture = fixture; - Fixture.TestSqlLoggerFactory.Clear(); - //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } - protected SqlServerFixture Fixture { get; } - #region Issue14095 [ConditionalTheory] diff --git a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs index 5644face999..416aac3bb1a 100644 --- a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs +++ b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs @@ -8,8 +8,6 @@ using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.SqlServer.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Internal; -using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; -using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; @@ -22,8 +20,8 @@ public override void Detects_duplicate_column_names() { var modelBuilder = CreateConventionalModelBuilder(); - GenerateMapping(modelBuilder.Entity().Property(b => b.Id).HasColumnName("Name").Metadata); - GenerateMapping(modelBuilder.Entity().Property(d => d.Name).IsRequired().HasColumnName("Name").Metadata); + modelBuilder.Entity().Property(b => b.Id).HasColumnName("Name"); + modelBuilder.Entity().Property(d => d.Name).IsRequired().HasColumnName("Name"); VerifyError( RelationalStrings.DuplicateColumnNameDataTypeMismatch( @@ -47,6 +45,20 @@ public override void Detects_incompatible_shared_columns_with_shared_table() nameof(A), nameof(A.P0), nameof(B), nameof(B.P0), nameof(B.P0), "Table", "someInt", "int"), modelBuilder.Model); } + public override void Detects_duplicate_columns_in_derived_types_with_different_types() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity(); + + modelBuilder.Entity().Property(c => c.Type).HasColumnName("Type").IsRequired(); + modelBuilder.Entity().Property(d => d.Type).HasColumnName("Type"); + + VerifyError( + RelationalStrings.DuplicateColumnNameDataTypeMismatch( + nameof(Cat), nameof(Cat.Type), nameof(Dog), nameof(Dog.Type), nameof(Cat.Type), nameof(Animal), "nvarchar(max)", + "int"), modelBuilder.Model); + } + [ConditionalFact] public virtual void Passes_for_duplicate_column_names_within_hierarchy_with_identity() { @@ -611,13 +623,6 @@ protected virtual void ConfigureProperty(IMutableProperty property, string confi } } - private static void GenerateMapping(IMutableProperty property) - => property.SetTypeMapping( - new SqlServerTypeMappingSource( - TestServiceFactory.Instance.Create(), - TestServiceFactory.Instance.Create()) - .FindMapping(property)); - private class Cheese { public int Id { get; set; } diff --git a/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationsAnnotationProviderTest.cs b/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationsAnnotationProviderTest.cs index 8c95dd4e542..93f9f9c58d5 100644 --- a/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationsAnnotationProviderTest.cs +++ b/test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationsAnnotationProviderTest.cs @@ -11,22 +11,22 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Migrations.Internal { public class SqlServerMigrationsAnnotationProviderTest { - private readonly ModelBuilder _modelBuilder; private readonly SqlServerAnnotationProvider _annotations; public SqlServerMigrationsAnnotationProviderTest() { - _modelBuilder = SqlServerTestHelpers.Instance.CreateConventionBuilder( /*skipValidation: true*/); _annotations = new SqlServerAnnotationProvider(new RelationalAnnotationProviderDependencies()); } [Fact] public void For_property_handles_identity_annotations() { - var property = _modelBuilder.Entity() + var modelBuilder = SqlServerTestHelpers.Instance.CreateConventionBuilder(); + var property = modelBuilder.Entity() .Property("Id").UseIdentityColumn(2, 3) .Metadata; - _modelBuilder.FinalizeModel(); + + SqlServerTestHelpers.Instance.Finalize(modelBuilder); var migrationAnnotations = _annotations.For(property.GetTableColumnMappings().Single().Column).ToList(); @@ -37,9 +37,10 @@ public void For_property_handles_identity_annotations() [ConditionalFact] public void Resolves_column_names_for_Index_with_included_properties() { - _modelBuilder.Entity().Property(e => e.IncludedProp).HasColumnName("IncludedColumn"); - var index = _modelBuilder.Entity().HasIndex(e => e.IndexedProp).IncludeProperties(e => e.IncludedProp).Metadata; - _modelBuilder.FinalizeModel(); + var modelBuilder = SqlServerTestHelpers.Instance.CreateConventionBuilder(); + modelBuilder.Entity().Property(e => e.IncludedProp).HasColumnName("IncludedColumn"); + var index = modelBuilder.Entity().HasIndex(e => e.IndexedProp).IncludeProperties(e => e.IncludedProp).Metadata; + SqlServerTestHelpers.Instance.Finalize(modelBuilder); Assert.Contains( _annotations.For(index.GetMappedTableIndexes().Single()), diff --git a/test/EFCore.SqlServer.Tests/SqlServerValueGeneratorCacheTest.cs b/test/EFCore.SqlServer.Tests/SqlServerValueGeneratorCacheTest.cs index 8333f766a4d..464980fceab 100644 --- a/test/EFCore.SqlServer.Tests/SqlServerValueGeneratorCacheTest.cs +++ b/test/EFCore.SqlServer.Tests/SqlServerValueGeneratorCacheTest.cs @@ -459,12 +459,16 @@ private static IModel CreateModel() modelBuilder.Entity( b => { + b.Property("Id"); b.Property(e => e.Zeppelin).UseHiLo("Heaven"); + b.HasAlternateKey(e => e.Zeppelin); b.Property(e => e.Stairway).UseHiLo("Heaven"); + b.HasAlternateKey(e => e.Stairway); b.Property(e => e.WholeLotta).UseHiLo("Rosie"); + b.HasAlternateKey(e => e.WholeLotta); }); - return modelBuilder.Model; + return modelBuilder.Model.FinalizeModel(); } private class Led diff --git a/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs b/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs index 72b260f0579..4ba630228ac 100644 --- a/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs +++ b/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs @@ -2,10 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Sqlite.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Sqlite.Internal; -using Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; @@ -18,8 +16,8 @@ public override void Detects_duplicate_column_names() { var modelBuilder = CreateConventionalModelBuilder(); - GenerateMapping(modelBuilder.Entity().Property(b => b.Id).HasColumnName("Name").Metadata); - GenerateMapping(modelBuilder.Entity().Property(d => d.Name).IsRequired().HasColumnName("Name").Metadata); + modelBuilder.Entity().Property(b => b.Id).HasColumnName("Name"); + modelBuilder.Entity().Property(d => d.Name).IsRequired().HasColumnName("Name"); VerifyError( RelationalStrings.DuplicateColumnNameDataTypeMismatch( @@ -33,8 +31,8 @@ public override void Detects_duplicate_columns_in_derived_types_with_different_t var modelBuilder = CreateConventionalModelBuilder(); modelBuilder.Entity(); - GenerateMapping(modelBuilder.Entity().Property(c => c.Type).IsRequired().HasColumnName("Type").Metadata); - GenerateMapping(modelBuilder.Entity().Property(d => d.Type).HasColumnName("Type").Metadata); + modelBuilder.Entity().Property(c => c.Type).IsRequired().HasColumnName("Type"); + modelBuilder.Entity().Property(d => d.Type).HasColumnName("Type"); VerifyError( RelationalStrings.DuplicateColumnNameDataTypeMismatch( @@ -47,8 +45,8 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe var modelBuilder = CreateConventionalModelBuilder(); modelBuilder.Entity(); - GenerateMapping(modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").HasSrid(30).Metadata); - GenerateMapping(modelBuilder.Entity().Property(d => d.Breed).HasColumnName("Breed").HasSrid(15).Metadata); + modelBuilder.Entity().Property(c => c.Breed).HasColumnName("Breed").HasSrid(30); + modelBuilder.Entity().Property(d => d.Breed).HasColumnName("Breed").HasSrid(15); VerifyError( SqliteStrings.DuplicateColumnNameSridMismatch( @@ -65,8 +63,8 @@ public override void Detects_incompatible_shared_columns_with_shared_table() modelBuilder.Entity().Property(a => a.P0).HasColumnName(nameof(A.P0)); modelBuilder.Entity().ToTable("Table"); - GenerateMapping(modelBuilder.Entity().Property(b => b.P0).Metadata); - GenerateMapping(modelBuilder.Entity().Property(d => d.P0).Metadata); + modelBuilder.Entity().Property(b => b.P0); + modelBuilder.Entity().Property(d => d.P0); VerifyError( RelationalStrings.DuplicateColumnNameDataTypeMismatch( @@ -95,11 +93,6 @@ public void Detects_sequences() modelBuilder.Model); } - private static void GenerateMapping(IMutableProperty property) - => property.SetTypeMapping( - TestServiceFactory.Instance.Create() - .FindMapping(property)); - protected override TestHelpers TestHelpers => SqliteTestHelpers.Instance; } diff --git a/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationAnnotationProviderTest.cs b/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationAnnotationProviderTest.cs index 4f32caef513..ba708970627 100644 --- a/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationAnnotationProviderTest.cs +++ b/test/EFCore.Sqlite.Tests/Migrations/SqliteMigrationAnnotationProviderTest.cs @@ -12,23 +12,15 @@ namespace Microsoft.EntityFrameworkCore.Migrations { public class SqliteMigrationAnnotationProviderTest { - private readonly ModelBuilder _modelBuilder; - private readonly SqliteAnnotationProvider _provider; - + private readonly ModelBuilder _modelBuilder = SqliteTestHelpers.Instance.CreateConventionBuilder(); + private readonly SqliteAnnotationProvider _provider = new SqliteAnnotationProvider(new RelationalAnnotationProviderDependencies()); private readonly Annotation _autoincrement = new(SqliteAnnotationNames.Autoincrement, true); - public SqliteMigrationAnnotationProviderTest() - { - _modelBuilder = SqliteTestHelpers.Instance.CreateConventionBuilder(); - - _provider = new SqliteAnnotationProvider(new RelationalAnnotationProviderDependencies()); - } - [ConditionalFact] public void Does_not_add_Autoincrement_for_OnAdd_integer_property_non_key() { var property = _modelBuilder.Entity().Property(e => e.IntProp).ValueGeneratedOnAdd().Metadata; - _modelBuilder.FinalizeModel(); + FinalizeModel(); Assert.DoesNotContain( _provider.For(property.GetTableColumnMappings().Single().Column), @@ -40,7 +32,7 @@ public void Adds_Autoincrement_for_OnAdd_integer_property_primary_key() { var property = _modelBuilder.Entity().Property(e => e.IntProp).ValueGeneratedOnAdd().Metadata; _modelBuilder.Entity().HasKey(e => e.IntProp); - _modelBuilder.FinalizeModel(); + FinalizeModel(); Assert.Contains( _provider.For(property.GetTableColumnMappings().Single().Column), @@ -51,7 +43,7 @@ public void Adds_Autoincrement_for_OnAdd_integer_property_primary_key() public void Does_not_add_Autoincrement_for_OnAddOrUpdate_integer_property() { var property = _modelBuilder.Entity().Property(e => e.IntProp).ValueGeneratedOnAddOrUpdate().Metadata; - _modelBuilder.FinalizeModel(); + FinalizeModel(); Assert.DoesNotContain( _provider.For(property.GetTableColumnMappings().Single().Column), @@ -62,7 +54,7 @@ public void Does_not_add_Autoincrement_for_OnAddOrUpdate_integer_property() public void Does_not_add_Autoincrement_for_OnUpdate_integer_property() { var property = _modelBuilder.Entity().Property(e => e.IntProp).ValueGeneratedOnUpdate().Metadata; - _modelBuilder.FinalizeModel(); + FinalizeModel(); Assert.DoesNotContain( _provider.For(property.GetTableColumnMappings().Single().Column), @@ -73,7 +65,7 @@ public void Does_not_add_Autoincrement_for_OnUpdate_integer_property() public void Does_not_add_Autoincrement_for_Never_value_generated_integer_property() { var property = _modelBuilder.Entity().Property(e => e.IntProp).ValueGeneratedNever().Metadata; - _modelBuilder.FinalizeModel(); + FinalizeModel(); Assert.DoesNotContain( _provider.For(property.GetTableColumnMappings().Single().Column), @@ -84,7 +76,7 @@ public void Does_not_add_Autoincrement_for_Never_value_generated_integer_propert public void Does_not_add_Autoincrement_for_default_integer_property() { var property = _modelBuilder.Entity().Property(e => e.IntProp).Metadata; - _modelBuilder.FinalizeModel(); + FinalizeModel(); Assert.DoesNotContain( _provider.For(property.GetTableColumnMappings().Single().Column), @@ -95,13 +87,16 @@ public void Does_not_add_Autoincrement_for_default_integer_property() public void Does_not_add_Autoincrement_for_non_integer_OnAdd_property() { var property = _modelBuilder.Entity().Property(e => e.StringProp).ValueGeneratedOnAdd().Metadata; - _modelBuilder.FinalizeModel(); + FinalizeModel(); Assert.DoesNotContain( _provider.For(property.GetTableColumnMappings().Single().Column), a => a.Name == _autoincrement.Name); } + private IModel FinalizeModel() + => SqliteTestHelpers.Instance.Finalize(_modelBuilder); + private class Entity { public int Id { get; set; } diff --git a/test/EFCore.Sqlite.Tests/SqliteEventIdTest.cs b/test/EFCore.Sqlite.Tests/SqliteEventIdTest.cs index 5bc8460be50..b56abc88844 100644 --- a/test/EFCore.Sqlite.Tests/SqliteEventIdTest.cs +++ b/test/EFCore.Sqlite.Tests/SqliteEventIdTest.cs @@ -38,11 +38,8 @@ public void Every_eventId_has_a_logger_method_and_logs_when_level_enabled() fakeFactories); } - private class FakeSequence : ISequence + private class FakeSequence : Annotatable, ISequence { - public object this[string name] - => throw new NotImplementedException(); - public string Name => "SequenceName"; @@ -72,16 +69,6 @@ public IModel Model public bool IsCyclic => throw new NotImplementedException(); - - public IAnnotation FindAnnotation(string name) - { - throw new NotImplementedException(); - } - - public IEnumerable GetAnnotations() - { - throw new NotImplementedException(); - } } } } diff --git a/test/EFCore.Tests/ApiConsistencyTest.cs b/test/EFCore.Tests/ApiConsistencyTest.cs index ce36d4facdf..99493e25134 100644 --- a/test/EFCore.Tests/ApiConsistencyTest.cs +++ b/test/EFCore.Tests/ApiConsistencyTest.cs @@ -132,6 +132,11 @@ public override typeof(IConventionPropertyBase).GetMethod(nameof(IConventionPropertyBase.SetField), new[] { typeof(string), typeof(bool) }), typeof(IAnnotatable).GetMethod(nameof(IAnnotatable.FindAnnotation)), typeof(IAnnotatable).GetMethod(nameof(IAnnotatable.GetAnnotations)), + typeof(IAnnotatable).GetMethod(nameof(IAnnotatable.FindRuntimeAnnotation)), + typeof(IAnnotatable).GetMethod(nameof(IAnnotatable.GetRuntimeAnnotations)), + typeof(IAnnotatable).GetMethod(nameof(IAnnotatable.AddRuntimeAnnotation)), + typeof(IAnnotatable).GetMethod(nameof(IAnnotatable.SetRuntimeAnnotation)), + typeof(IAnnotatable).GetMethod(nameof(IAnnotatable.RemoveRuntimeAnnotation)), typeof(IMutableAnnotatable).GetMethod("set_Item"), typeof(IConventionAnnotatable).GetMethod(nameof(IConventionAnnotatable.SetAnnotation)), typeof(ConventionAnnotatableExtensions).GetMethod(nameof(ConventionAnnotatableExtensions.SetOrRemoveAnnotation)), diff --git a/test/EFCore.Tests/ChangeTracking/Internal/InternalEntityEntryFactoryTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/InternalEntityEntryFactoryTest.cs index 9edd1834342..f9b7b320472 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/InternalEntityEntryFactoryTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/InternalEntityEntryFactoryTest.cs @@ -16,54 +16,56 @@ public class InternalEntityEntryFactoryTest [ConditionalFact] public void Creates_CLR_only_entry_when_entity_has_no_shadow_properties() { - var model = CreateModel(); - var entityType = model.AddEntityType(typeof(RedHook)); - entityType.AddProperty("Long", typeof(int)); - entityType.AddProperty("Hammer", typeof(string)); - model.FinalizeModel(); + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + var entityTypeBuilder = modelBuilder.Entity(); + entityTypeBuilder.Property("Id"); + entityTypeBuilder.Property("Long"); + entityTypeBuilder.Property("Hammer"); + + var model = modelBuilder.FinalizeModel(); var contextServices = InMemoryTestHelpers.Instance.CreateContextServices(model); var stateManager = contextServices.GetRequiredService(); var factory = contextServices.GetRequiredService(); var entity = new RedHook(); - var entry = factory.Create(stateManager, entityType, entity); + var entry = factory.Create(stateManager, entityTypeBuilder.Metadata, entity); Assert.IsType(entry); Assert.Same(stateManager, entry.StateManager); - Assert.Same(entityType, entry.EntityType); + Assert.Same(entityTypeBuilder.Metadata, entry.EntityType); Assert.Same(entity, entry.Entity); } [ConditionalFact] public void Creates_mixed_entry_when_entity_CLR_entity_type_and_shadow_properties() { - var model = CreateModel(); - var entityType = model.AddEntityType(typeof(RedHook)); - entityType.AddProperty("Long", typeof(int)); - entityType.AddProperty("Spanner", typeof(string)); - model.FinalizeModel(); + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + var entityTypeBuilder = modelBuilder.Entity(); + entityTypeBuilder.Property("Id"); + entityTypeBuilder.Property("Long"); + entityTypeBuilder.Property("Spanner"); + + var model = modelBuilder.FinalizeModel(); var contextServices = InMemoryTestHelpers.Instance.CreateContextServices(model); var stateManager = contextServices.GetRequiredService(); var factory = contextServices.GetRequiredService(); var entity = new RedHook(); - var entry = factory.Create(stateManager, entityType, entity); + var entry = factory.Create(stateManager, entityTypeBuilder.Metadata, entity); Assert.IsType(entry); Assert.Same(stateManager, entry.StateManager); - Assert.Same(entityType, entry.EntityType); + Assert.Same(entityTypeBuilder.Metadata, entry.EntityType); Assert.Same(entity, entry.Entity); } - private static IMutableModel CreateModel() - => new Model(); - private class RedHook { + public int Id { get; set; } public int Long { get; set; } public string Hammer { get; set; } } diff --git a/test/EFCore.Tests/DbContextServicesTest.cs b/test/EFCore.Tests/DbContextServicesTest.cs index 0b081509fae..23c755cc130 100644 --- a/test/EFCore.Tests/DbContextServicesTest.cs +++ b/test/EFCore.Tests/DbContextServicesTest.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Diagnostics.Internal; @@ -13,6 +14,7 @@ using Microsoft.EntityFrameworkCore.InMemory.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.InMemory.Storage.Internal; using Microsoft.EntityFrameworkCore.InMemory.ValueGeneration.Internal; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -657,6 +659,11 @@ public IModel GetModel( IConventionSetBuilder conventionSetBuilder, ModelDependencies modelDependencies) => new Model(); + + public IModel GetModel( + DbContext context, + IModelCreationDependencies modelCreationDependencies) + => new Model(); } [ConditionalFact] diff --git a/test/EFCore.Tests/DbContextTest.cs b/test/EFCore.Tests/DbContextTest.cs index cd979ee39d2..f4e37282292 100644 --- a/test/EFCore.Tests/DbContextTest.cs +++ b/test/EFCore.Tests/DbContextTest.cs @@ -98,14 +98,14 @@ public void Local_does_not_call_DetectChanges_when_disabled() [ConditionalFact] public void Set_throws_for_shared_types() { - var model = new Model(); - var question = model.AddEntityType("SharedQuestion", typeof(Question), ConfigurationSource.Explicit); + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + modelBuilder.Model.AddEntityType("SharedQuestion", typeof(Question)); var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder .UseInMemoryDatabase(Guid.NewGuid().ToString()) .UseInternalServiceProvider(InMemoryTestHelpers.Instance.CreateServiceProvider()) - .UseModel(model.FinalizeModel()); + .UseModel(modelBuilder.FinalizeModel()); using var context = new DbContext(optionsBuilder.Options); var ex = Assert.Throws(() => context.Set().Local); Assert.Equal(CoreStrings.InvalidSetSharedType(typeof(Question).ShortDisplayName()), ex.Message); @@ -118,14 +118,16 @@ public void SaveChanges_calls_DetectChanges() .AddScoped() .AddScoped(); - var model = new ModelBuilder().Entity().Metadata.Model; + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + modelBuilder.Entity(); + var serviceProvider = InMemoryTestHelpers.Instance.CreateServiceProvider(services); using var context = new DbContext( new DbContextOptionsBuilder() .UseInternalServiceProvider(serviceProvider) .UseInMemoryDatabase(Guid.NewGuid().ToString()) - .UseModel(model.FinalizeModel()) + .UseModel(modelBuilder.FinalizeModel()) .Options); var changeDetector = (FakeChangeDetector)context.GetService(); @@ -298,12 +300,12 @@ public void Context_can_build_model_using_DbSet_properties() [ConditionalFact] public void Context_will_use_explicit_model_if_set_in_config() { - IMutableModel model = new Model(); - model.AddEntityType(typeof(TheGu)); + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + modelBuilder.Entity(); using var context = new EarlyLearningCenter( InMemoryTestHelpers.Instance.CreateServiceProvider(), - new DbContextOptionsBuilder().UseModel(model.FinalizeModel()).Options); + new DbContextOptionsBuilder().UseModel(modelBuilder.FinalizeModel()).Options); Assert.Equal( new[] { typeof(TheGu).FullName }, context.Model.GetEntityTypes().Select(e => e.Name).ToArray()); diff --git a/test/EFCore.Tests/Infrastructure/AnnotatableTest.cs b/test/EFCore.Tests/Infrastructure/AnnotatableTest.cs index d1732d1262f..e92fe90989e 100644 --- a/test/EFCore.Tests/Infrastructure/AnnotatableTest.cs +++ b/test/EFCore.Tests/Infrastructure/AnnotatableTest.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Xunit; namespace Microsoft.EntityFrameworkCore.Infrastructure @@ -35,7 +36,7 @@ public void Can_add_and_remove_annotation() } [ConditionalFact] - public void Addind_duplicate_annotation_throws() + public void Adding_duplicate_annotation_throws() { var annotatable = new Annotatable(); @@ -50,6 +51,7 @@ public void Addind_duplicate_annotation_throws() public void Can_get_and_set_model_annotations() { var annotatable = new Annotatable(); + Assert.Empty(annotatable.GetAnnotations()); var annotation = annotatable.AddAnnotation("Foo", "Bar"); Assert.NotNull(annotation); @@ -82,5 +84,45 @@ public void Annotations_are_ordered_by_name() Assert.True(new[] { annotation2, annotation1 }.SequenceEqual(annotatable.GetAnnotations())); } + + [ConditionalFact] + public void Can_add_and_remove_runtime_annotation() + { + var annotatable = new Model().FinalizeModel(); + Assert.Empty(annotatable.GetRuntimeAnnotations()); + Assert.Null(annotatable.FindRuntimeAnnotation("Foo")); + Assert.Null(annotatable.RemoveRuntimeAnnotation("Foo")); + + var annotation = annotatable.AddRuntimeAnnotation("Foo", "Bar"); + + Assert.NotNull(annotation); + Assert.Equal("Bar", annotation.Value); + Assert.Null(annotatable["Foo"]); + Assert.Same(annotation, annotatable.FindRuntimeAnnotation("Foo")); + + var annotation2 = annotatable.SetRuntimeAnnotation("A", "Foo"); + Assert.Equal(new[] { annotation2, annotation }, annotatable.GetRuntimeAnnotations()); + Assert.Empty(annotatable.GetAnnotations()); + + Assert.Same(annotation, annotatable.RemoveRuntimeAnnotation(annotation.Name)); + Assert.Same(annotation2, annotatable.RemoveRuntimeAnnotation(annotation2.Name)); + + Assert.Empty(annotatable.GetRuntimeAnnotations()); + Assert.Null(annotatable.RemoveRuntimeAnnotation(annotation.Name)); + Assert.Null(annotatable["Foo"]); + Assert.Null(annotatable.FindRuntimeAnnotation("Foo")); + } + + [ConditionalFact] + public void Adding_duplicate_runtime_annotation_throws() + { + var annotatable = new Model().FinalizeModel(); + + annotatable.AddRuntimeAnnotation("Foo", "Bar"); + + Assert.Equal( + CoreStrings.DuplicateAnnotation("Foo", annotatable.ToString()), + Assert.Throws(() => annotatable.AddRuntimeAnnotation("Foo", "Bar")).Message); + } } } diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs index 8ff3a6f7773..bb79b430a3e 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs @@ -1078,7 +1078,8 @@ public virtual void Detects_duplicate_seeds(bool sensitiveDataLoggingEnabled) sensitiveDataLoggingEnabled ? CoreStrings.SeedDatumDuplicateSensitive(nameof(D), $"{nameof(A.Id)}:1") : CoreStrings.SeedDatumDuplicate(nameof(D), $"{{'{nameof(A.Id)}'}}"), - modelBuilder.Model); + modelBuilder.Model, + sensitiveDataLoggingEnabled); } [ConditionalTheory] @@ -1098,7 +1099,8 @@ public virtual void Detects_incompatible_values(bool sensitiveDataLoggingEnabled sensitiveDataLoggingEnabled ? CoreStrings.SeedDatumIncompatibleValueSensitive(nameof(A), "invalid", nameof(A.P0), "System.Nullable") : CoreStrings.SeedDatumIncompatibleValue(nameof(A), nameof(A.P0), "System.Nullable"), - modelBuilder.Model); + modelBuilder.Model, + sensitiveDataLoggingEnabled); } [ConditionalTheory] @@ -1127,7 +1129,8 @@ public virtual void Detects_reference_navigations_in_seeds(bool sensitiveDataLog nameof(SampleEntity.ReferencedEntity), nameof(ReferencedEntity), $"{{'{nameof(ReferencedEntity.SampleEntityId)}'}}"), - modelBuilder.Model); + modelBuilder.Model, + sensitiveDataLoggingEnabled); } [ConditionalTheory] @@ -1158,7 +1161,8 @@ public virtual void Detects_reference_navigations_in_seeds2(bool sensitiveDataLo nameof(Order.Products), "OrderProduct (Dictionary)", "{'OrdersId'}"), - modelBuilder.Model); + modelBuilder.Model, + sensitiveDataLoggingEnabled); } [ConditionalTheory] @@ -1192,7 +1196,8 @@ public virtual void Detects_collection_navigations_in_seeds(bool sensitiveDataLo nameof(SampleEntity.OtherSamples), nameof(SampleEntity), "{'SampleEntityId'}"), - modelBuilder.Model); + modelBuilder.Model, + sensitiveDataLoggingEnabled); } [ConditionalFact] diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs index 910572c2767..87edff2fd2f 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs @@ -9,7 +9,6 @@ using System.Reflection; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Diagnostics.Internal; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; @@ -278,9 +277,9 @@ protected virtual void VerifyWarnings(string[] expectedMessages, IMutableModel m } } - protected virtual void VerifyError(string expectedMessage, IMutableModel model) + protected virtual void VerifyError(string expectedMessage, IMutableModel model, bool sensitiveDataLoggingEnabled = false) { - var message = Assert.Throws(() => Validate(model)).Message; + var message = Assert.Throws(() => Validate(model, sensitiveDataLoggingEnabled)).Message; Assert.Equal(expectedMessage, message); } @@ -293,8 +292,14 @@ protected virtual void VerifyLogDoesNotContain(string expectedMessage, IMutableM Assert.Empty(logEntries); } - protected virtual IModel Validate(IMutableModel model) - => model.FinalizeModel(); + protected virtual IModel Validate(IMutableModel model, bool sensitiveDataLoggingEnabled = false) + { + var serviceProvider = CreateServiceProvider(sensitiveDataLoggingEnabled); + var modelRuntimeInitializer = serviceProvider.GetRequiredService(); + var validationLogger = CreateValidationLogger(sensitiveDataLoggingEnabled); + + return modelRuntimeInitializer.Initialize(model.FinalizeModel(), validationLogger); + } protected DiagnosticsLogger CreateValidationLogger(bool sensitiveDataLoggingEnabled = false) { @@ -326,15 +331,8 @@ protected virtual ModelBuilder CreateConventionalModelBuilder(bool sensitiveData protected virtual ModelBuilder CreateConventionlessModelBuilder(bool sensitiveDataLoggingEnabled = false) { - var conventionSet = new ConventionSet(); var serviceProvider = CreateServiceProvider(sensitiveDataLoggingEnabled); - - // Use public API to add conventions, issue #214 - var dependencies = serviceProvider.GetRequiredService(); - conventionSet.ModelFinalizingConventions.Add(new TypeMappingConvention(dependencies)); - conventionSet.ModelFinalizedConventions.Add(new ValidatingConvention(dependencies)); - - return new ModelBuilder(conventionSet, serviceProvider.GetRequiredService()); + return new ModelBuilder(new ConventionSet(), serviceProvider.GetRequiredService()); } protected IServiceProvider CreateServiceProvider(bool sensitiveDataLoggingEnabled = false) diff --git a/test/EFCore.Tests/Metadata/Conventions/PropertyMappingValidationConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/PropertyMappingValidationConventionTest.cs index f3db8483703..33d20e4bb62 100644 --- a/test/EFCore.Tests/Metadata/Conventions/PropertyMappingValidationConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/PropertyMappingValidationConventionTest.cs @@ -190,30 +190,21 @@ public virtual void Does_not_throw_when_non_candidate_property_is_not_added() CreatePropertyMappingValidator()(modelBuilder.Metadata); } - protected override ModelBuilder CreateConventionlessModelBuilder(bool sensitiveDataLoggingEnabled = false) - { - var conventionSet = new ConventionSet(); - var serviceProvider = CreateServiceProvider(sensitiveDataLoggingEnabled); - - // Use public API to add conventions, issue #214 - var dependencies = serviceProvider.GetService(); - conventionSet.ModelFinalizingConventions.Add(new TypeMappingConvention(dependencies)); - - return new ModelBuilder(conventionSet, serviceProvider.GetService()); - } - - protected virtual Action CreatePropertyMappingValidator() + protected virtual Action CreatePropertyMappingValidator() { var validator = CreateModelValidator(); var logger = new TestLogger(); var validatePropertyMappingMethod = typeof(ModelValidator).GetRuntimeMethods().Single(e => e.Name == "ValidatePropertyMapping"); + var modelRuntimeInitializer = TestHelpers.CreateContextServices().GetRequiredService(); + return m => { try { - ((Model)m).FinalizeModel(); + m.FinalizeModel(); + modelRuntimeInitializer.Initialize(m, validationLogger: null); validatePropertyMappingMethod.Invoke( validator, new object[] { m, logger }); } diff --git a/test/EFCore.Tests/Metadata/Internal/ClrCollectionAccessorFactoryTest.cs b/test/EFCore.Tests/Metadata/Internal/ClrCollectionAccessorFactoryTest.cs index 9f67a0770ad..7bec8ce7395 100644 --- a/test/EFCore.Tests/Metadata/Internal/ClrCollectionAccessorFactoryTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/ClrCollectionAccessorFactoryTest.cs @@ -41,17 +41,8 @@ public void Navigation_is_returned_if_it_implements_IClrCollectionAccessor() Assert.Same(navigation, new ClrCollectionAccessorFactory().Create(navigation)); } - private class FakeNavigation : INavigation, IClrCollectionAccessor + private class FakeNavigation : Annotatable, INavigation, IClrCollectionAccessor { - public object this[string name] - => throw new NotImplementedException(); - - public IAnnotation FindAnnotation(string name) - => throw new NotImplementedException(); - - public IEnumerable GetAnnotations() - => throw new NotImplementedException(); - public string Name { get; } public ITypeBase DeclaringType { get; } public Type ClrType { get; } diff --git a/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs b/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs index 35163725efd..4adcd6d37f6 100644 --- a/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs @@ -21,7 +21,7 @@ public void Property_is_returned_if_it_implements_IClrPropertyGetter() Assert.Same(property, new ClrPropertyGetterFactory().Create(property)); } - private class FakeProperty : IProperty, IClrPropertyGetter + private class FakeProperty : Annotatable, IProperty, IClrPropertyGetter { public object GetClrValue(object entity) => throw new NotImplementedException(); @@ -29,15 +29,6 @@ public object GetClrValue(object entity) public bool HasDefaultValue(object entity) => throw new NotImplementedException(); - public object this[string name] - => throw new NotImplementedException(); - - public IAnnotation FindAnnotation(string name) - => throw new NotImplementedException(); - - public IEnumerable GetAnnotations() - => throw new NotImplementedException(); - public string Name { get; } public ITypeBase DeclaringType { get; } public Type ClrType { get; } diff --git a/test/EFCore.Tests/Metadata/Internal/ClrPropertySetterFactoryTest.cs b/test/EFCore.Tests/Metadata/Internal/ClrPropertySetterFactoryTest.cs index 49466a485ec..3fe4c6b8489 100644 --- a/test/EFCore.Tests/Metadata/Internal/ClrPropertySetterFactoryTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/ClrPropertySetterFactoryTest.cs @@ -22,20 +22,11 @@ public void Property_is_returned_if_it_implements_IClrPropertySetter() Assert.Same(property, new ClrPropertySetterFactory().Create(property)); } - private class FakeProperty : IProperty, IClrPropertySetter + private class FakeProperty : Annotatable, IProperty, IClrPropertySetter { public void SetClrValue(object instance, object value) => throw new NotImplementedException(); - public object this[string name] - => throw new NotImplementedException(); - - public IAnnotation FindAnnotation(string name) - => throw new NotImplementedException(); - - public IEnumerable GetAnnotations() - => throw new NotImplementedException(); - public string Name { get; } public ITypeBase DeclaringType { get; } public Type ClrType { get; } diff --git a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs index 51f55d08b62..ba11b6ca235 100644 --- a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs @@ -36,17 +36,8 @@ public void Use_of_custom_IEntityType_throws() Assert.Throws(() => type.AsEntityType()).Message); } - private class FakeEntityType : IEntityType + private class FakeEntityType : Annotatable, IEntityType { - public object this[string name] - => throw new NotImplementedException(); - - public IAnnotation FindAnnotation(string name) - => throw new NotImplementedException(); - - public IEnumerable GetAnnotations() - => throw new NotImplementedException(); - public IModel Model { get; } public string Name { get; } public bool HasSharedClrType { get; } diff --git a/test/EFCore.Tests/Metadata/Internal/ForeignKeyTest.cs b/test/EFCore.Tests/Metadata/Internal/ForeignKeyTest.cs index a7849aa2888..a9de1ad1eee 100644 --- a/test/EFCore.Tests/Metadata/Internal/ForeignKeyTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/ForeignKeyTest.cs @@ -23,17 +23,8 @@ public void Use_of_custom_IForeignKey_throws() Assert.Throws(() => foreignKey.AsForeignKey()).Message); } - public class FakeForeignKey : IForeignKey + public class FakeForeignKey : Annotatable, IForeignKey { - public object this[string name] - => throw new NotImplementedException(); - - public IAnnotation FindAnnotation(string name) - => throw new NotImplementedException(); - - public IEnumerable GetAnnotations() - => throw new NotImplementedException(); - public IEntityType DeclaringEntityType { get; } public IReadOnlyList Properties { get; } public IEntityType PrincipalEntityType { get; } diff --git a/test/EFCore.Tests/Metadata/Internal/KeyTest.cs b/test/EFCore.Tests/Metadata/Internal/KeyTest.cs index 7e6b6016692..435f4b27591 100644 --- a/test/EFCore.Tests/Metadata/Internal/KeyTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/KeyTest.cs @@ -23,17 +23,8 @@ public void Use_of_custom_IKey_throws() Assert.Throws(() => key.AsKey()).Message); } - private class FakeKey : IKey + private class FakeKey : Annotatable, IKey { - public object this[string name] - => throw new NotImplementedException(); - - public IAnnotation FindAnnotation(string name) - => throw new NotImplementedException(); - - public IEnumerable GetAnnotations() - => throw new NotImplementedException(); - public IReadOnlyList Properties { get; } public IEntityType DeclaringEntityType { get; } } diff --git a/test/EFCore.Tests/Metadata/Internal/ModelTest.cs b/test/EFCore.Tests/Metadata/Internal/ModelTest.cs index f9459d5ca03..5c60d28c21f 100644 --- a/test/EFCore.Tests/Metadata/Internal/ModelTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/ModelTest.cs @@ -25,17 +25,8 @@ public void Use_of_custom_IModel_throws() Assert.Throws(() => model.AsModel()).Message); } - private class FakeModel : IModel + private class FakeModel : Annotatable, IModel { - public object this[string name] - => throw new NotImplementedException(); - - public IAnnotation FindAnnotation(string name) - => throw new NotImplementedException(); - - public IEnumerable GetAnnotations() - => throw new NotImplementedException(); - public IEnumerable GetEntityTypes() => throw new NotImplementedException(); diff --git a/test/EFCore.Tests/Metadata/Internal/NavigationTest.cs b/test/EFCore.Tests/Metadata/Internal/NavigationTest.cs index c3bdd042451..ea21040f4ec 100644 --- a/test/EFCore.Tests/Metadata/Internal/NavigationTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/NavigationTest.cs @@ -22,17 +22,8 @@ public void Use_of_custom_INavigation_throws() Assert.Throws(() => navigation.AsNavigation()).Message); } - private class FakeNavigation : INavigation + private class FakeNavigation : Annotatable, INavigation { - public object this[string name] - => throw new NotImplementedException(); - - public IAnnotation FindAnnotation(string name) - => throw new NotImplementedException(); - - public IEnumerable GetAnnotations() - => throw new NotImplementedException(); - public string Name { get; } public ITypeBase DeclaringType { get; } public Type ClrType { get; } diff --git a/test/EFCore.Tests/Metadata/Internal/PropertyAccessorsFactoryTest.cs b/test/EFCore.Tests/Metadata/Internal/PropertyAccessorsFactoryTest.cs index 2129127a597..05dd1f6be47 100644 --- a/test/EFCore.Tests/Metadata/Internal/PropertyAccessorsFactoryTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/PropertyAccessorsFactoryTest.cs @@ -17,18 +17,19 @@ public class PropertyAccessorsFactoryTest [ConditionalFact] public void Can_use_PropertyAccessorsFactory_on_indexed_property() { - IMutableModel model = new Model(); - var entityType = model.AddEntityType(typeof(IndexedClass)); - var id = entityType.AddProperty("Id", typeof(int)); - var propertyA = entityType.AddIndexerProperty("PropertyA", typeof(string)); - model.FinalizeModel(); + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + var entityTypeBuilder = modelBuilder.Entity(); + entityTypeBuilder.Property("Id"); + var propertyA = entityTypeBuilder.IndexerProperty("PropertyA").Metadata; + + var model = modelBuilder.FinalizeModel(); var contextServices = InMemoryTestHelpers.Instance.CreateContextServices(model); var stateManager = contextServices.GetRequiredService(); var factory = contextServices.GetRequiredService(); var entity = new IndexedClass(); - var entry = factory.Create(stateManager, entityType, entity); + var entry = factory.Create(stateManager, entityTypeBuilder.Metadata, entity); var propertyAccessors = new PropertyAccessorsFactory().Create(propertyA); Assert.Equal("ValueA", ((Func)propertyAccessors.CurrentValueGetter)(entry)); @@ -43,18 +44,19 @@ public void Can_use_PropertyAccessorsFactory_on_indexed_property() [ConditionalFact] public void Can_use_PropertyAccessorsFactory_on_non_indexed_property() { - IMutableModel model = new Model(); - var entityType = model.AddEntityType(typeof(NonIndexedClass)); - entityType.AddProperty("Id", typeof(int)); - var propA = entityType.AddProperty("PropA", typeof(string)); - model.FinalizeModel(); + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + var entityTypeBuilder = modelBuilder.Entity(); + entityTypeBuilder.Property("Id"); + var propA = entityTypeBuilder.Property("PropA").Metadata; + + var model = modelBuilder.FinalizeModel(); var contextServices = InMemoryTestHelpers.Instance.CreateContextServices(model); var stateManager = contextServices.GetRequiredService(); var factory = contextServices.GetRequiredService(); var entity = new NonIndexedClass(); - var entry = factory.Create(stateManager, entityType, entity); + var entry = factory.Create(stateManager, entityTypeBuilder.Metadata, entity); var propertyAccessors = new PropertyAccessorsFactory().Create(propA); Assert.Equal("ValueA", ((Func)propertyAccessors.CurrentValueGetter)(entry)); diff --git a/test/EFCore.Tests/Metadata/Internal/PropertyBaseTest.cs b/test/EFCore.Tests/Metadata/Internal/PropertyBaseTest.cs index a8956cc071d..ad604831923 100644 --- a/test/EFCore.Tests/Metadata/Internal/PropertyBaseTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/PropertyBaseTest.cs @@ -867,8 +867,10 @@ private void MemberInfoTestCommon( try { var model = ((Model)propertyBase.DeclaringType.Model).FinalizeModel(); - InMemoryTestHelpers.Instance.CreateContextServices().GetRequiredService() - .Validate(model, new TestLogger()); + var contextServices = InMemoryTestHelpers.Instance.CreateContextServices(); + var modelRuntimeInitializer = contextServices.GetRequiredService(); + + model = modelRuntimeInitializer.Initialize(model, new TestLogger()); Assert.Null(failMessage); } diff --git a/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs b/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs index 65bb3ee808b..8d4cf132c13 100644 --- a/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs @@ -37,17 +37,8 @@ public void Use_of_custom_IPropertyBase_throws() Assert.Throws(() => property.AsPropertyBase()).Message); } - private class FakeProperty : IProperty + private class FakeProperty : Annotatable, IProperty { - public object this[string name] - => throw new NotImplementedException(); - - public IAnnotation FindAnnotation(string name) - => throw new NotImplementedException(); - - public IEnumerable GetAnnotations() - => throw new NotImplementedException(); - public string Name { get; } public ITypeBase DeclaringType { get; } public Type ClrType { get; } diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs index 0b01b574ffc..81316aa32f9 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs @@ -6,12 +6,13 @@ using System.Diagnostics; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Diagnostics.Internal; -using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.EntityFrameworkCore.ValueGeneration; +using Microsoft.Extensions.DependencyInjection; using Xunit; // ReSharper disable InconsistentNaming @@ -116,7 +117,7 @@ protected TestModelBuilder(TestHelpers testHelpers) var options = new LoggingOptions(); options.Initialize(new DbContextOptionsBuilder().EnableSensitiveDataLogging(false).Options); ValidationLoggerFactory = new ListLoggerFactory(l => l == DbLoggerCategory.Model.Validation.Name); - var validationLogger = new DiagnosticsLogger( + ValidationLogger = new DiagnosticsLogger( ValidationLoggerFactory, options, new DiagnosticListener("Fake"), @@ -131,15 +132,19 @@ protected TestModelBuilder(TestHelpers testHelpers) testHelpers.LoggingDefinitions, new NullDbContextLogger()); - ModelBuilder = testHelpers.CreateConventionBuilder(modelLogger, validationLogger); + ModelBuilder = testHelpers.CreateConventionBuilder(modelLogger, ValidationLogger); + TestHelpers = testHelpers; } + protected virtual TestHelpers TestHelpers { get; } + public virtual IMutableModel Model => ModelBuilder.Model; public ModelBuilder ModelBuilder { get; } public ListLoggerFactory ValidationLoggerFactory { get; } public ListLoggerFactory ModelLoggerFactory { get; } + protected virtual DiagnosticsLogger ValidationLogger { get; } public TestModelBuilder HasAnnotation(string annotation, object value) { @@ -166,7 +171,12 @@ public abstract TestModelBuilder Ignore() where TEntity : class; public virtual IModel FinalizeModel() - => ModelBuilder.FinalizeModel(); + { + var serviceProvider = TestHelpers.CreateContextServices(); + var modelRuntimeInitializer = serviceProvider.GetRequiredService(); + + return modelRuntimeInitializer.Initialize(ModelBuilder.FinalizeModel(), ValidationLogger); + } public virtual string GetDisplayName(Type entityType) => entityType.Name; diff --git a/test/EFCore.Tests/ModelBuilding/ShadowEntityTypeTest.cs b/test/EFCore.Tests/ModelBuilding/ShadowEntityTypeTest.cs index d3f4afd6b6c..94320e85051 100644 --- a/test/EFCore.Tests/ModelBuilding/ShadowEntityTypeTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ShadowEntityTypeTest.cs @@ -81,7 +81,7 @@ public virtual void Can_create_one_to_one_shadow_navigations_between_shadow_enti Assert.Equal( CoreStrings.EntityRequiresKey("Order (Dictionary)"), - Assert.Throws(() => modelBuilder.FinalizeModel()).Message); + Assert.Throws(() => InMemoryTestHelpers.Instance.Finalize(modelBuilder)).Message); } [ConditionalFact] @@ -99,7 +99,7 @@ public virtual void Can_create_one_to_many_shadow_navigations_between_shadow_ent Assert.Equal( CoreStrings.EntityRequiresKey("Customer (Dictionary)"), - Assert.Throws(() => modelBuilder.FinalizeModel()).Message); + Assert.Throws(() => InMemoryTestHelpers.Instance.Finalize(modelBuilder)).Message); } [ConditionalFact] diff --git a/test/EFCore.Tests/ModelSourceTest.cs b/test/EFCore.Tests/ModelSourceTest.cs index 877d4b249d6..43cd397fc50 100644 --- a/test/EFCore.Tests/ModelSourceTest.cs +++ b/test/EFCore.Tests/ModelSourceTest.cs @@ -9,14 +9,11 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.TestUtilities; -using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -26,12 +23,6 @@ namespace Microsoft.EntityFrameworkCore { public class ModelSourceTest { - private readonly IConventionSetBuilder _nullConventionSetBuilder - = new NullConventionSetBuilder(); - - private readonly ModelDependencies _testModelDependencies - = new(new TestLogger()); - [ConditionalFact] public void OnModelCreating_is_only_called_once() { @@ -76,18 +67,13 @@ protected internal override void OnConfiguring(DbContextOptionsBuilder optionsBu [ConditionalFact] public void Adds_all_entities_based_on_all_distinct_entity_types_found() { - var setFinder = new FakeSetFinder(); - var context = InMemoryTestHelpers.Instance.CreateContext(); - var serviceProvider = context.GetInfrastructure(); + var serviceProvider = InMemoryTestHelpers.Instance.CreateContextServices(); - var model = CreateDefaultModelSource(setFinder) + var model = serviceProvider.GetRequiredService() .GetModel( - context, - new RuntimeConventionSetBuilder( - new ProviderConventionSetBuilder( - serviceProvider.GetRequiredService() with { SetFinder = setFinder }), - new List()), - serviceProvider.GetRequiredService()); + new Context1(), + (serviceProvider.GetRequiredService() as ModelCreationDependencies) + with { ConventionSetBuilder = CreateRuntimeConventionSetBuilder(new FakeSetFinder(), serviceProvider) }); Assert.Equal( new[] { typeof(SetA).DisplayName(), typeof(SetB).DisplayName() }, @@ -133,22 +119,26 @@ private class SetB [ConditionalFact] public void Caches_model_by_context_type() { - var modelSource = CreateDefaultModelSource(new DbSetFinder()); + var serviceProvider = InMemoryTestHelpers.Instance.CreateContextServices(); + var modelSource = serviceProvider.GetRequiredService(); + var testModelDependencies = serviceProvider.GetRequiredService(); - var model1 = modelSource.GetModel(new Context1(), _nullConventionSetBuilder, _testModelDependencies); - var model2 = modelSource.GetModel(new Context2(), _nullConventionSetBuilder, _testModelDependencies); + var model1 = modelSource.GetModel(new Context1(), testModelDependencies); + var model2 = modelSource.GetModel(new Context2(), testModelDependencies); Assert.NotSame(model1, model2); - Assert.Same(model1, modelSource.GetModel(new Context1(), _nullConventionSetBuilder, _testModelDependencies)); - Assert.Same(model2, modelSource.GetModel(new Context2(), _nullConventionSetBuilder, _testModelDependencies)); + Assert.Same(model1, modelSource.GetModel(new Context1(), testModelDependencies)); + Assert.Same(model2, modelSource.GetModel(new Context2(), testModelDependencies)); } [ConditionalFact] public void Stores_model_version_information_as_annotation_on_model() { - var modelSource = CreateDefaultModelSource(new DbSetFinder()); + var serviceProvider = InMemoryTestHelpers.Instance.CreateContextServices(); + var modelSource = serviceProvider.GetRequiredService(); + var testModelDependencies = serviceProvider.GetRequiredService(); - var model = modelSource.GetModel(new Context1(), _nullConventionSetBuilder, _testModelDependencies); + var model = modelSource.GetModel(new Context1(), testModelDependencies); var packageVersion = typeof(Context1).Assembly.GetCustomAttributes() .Single(m => m.Key == "PackageVersion").Value; @@ -169,25 +159,12 @@ private class Context2 : DbContext { } - private static IModelSource CreateDefaultModelSource(IDbSetFinder setFinder) - => new ConcreteModelSource(setFinder); - - private class ConcreteModelSource : ModelSource - { - public ConcreteModelSource(IDbSetFinder setFinder) - : base( - new ModelSourceDependencies( - new ModelCustomizer(new ModelCustomizerDependencies(setFinder)), - InMemoryTestHelpers.Instance.CreateContextServices().GetRequiredService(), - new MemoryCache(new MemoryCacheOptions { SizeLimit = 200 }))) - { - } - } - - private class NullConventionSetBuilder : IConventionSetBuilder - { - public ConventionSet CreateConventionSet() - => new(); - } + private static RuntimeConventionSetBuilder CreateRuntimeConventionSetBuilder( + IDbSetFinder setFinder, + IServiceProvider serviceProvider) + => new RuntimeConventionSetBuilder( + new ProviderConventionSetBuilder( + serviceProvider.GetRequiredService() with { SetFinder = setFinder }), + new List()); } } diff --git a/test/EFCore.Tests/ValueGeneration/ValueGeneratorCacheTest.cs b/test/EFCore.Tests/ValueGeneration/ValueGeneratorCacheTest.cs index 4bf9cfe9636..54b1cd9b38c 100644 --- a/test/EFCore.Tests/ValueGeneration/ValueGeneratorCacheTest.cs +++ b/test/EFCore.Tests/ValueGeneration/ValueGeneratorCacheTest.cs @@ -3,7 +3,6 @@ using System; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -19,7 +18,8 @@ public void Uses_single_generator_per_property() var entityType = model.FindEntityType("Led"); var property1 = entityType.FindProperty("Zeppelin"); var property2 = entityType.FindProperty("Stairway"); - var cache = InMemoryTestHelpers.Instance.CreateContextServices(model).GetRequiredService(); + var cache = InMemoryTestHelpers.Instance.CreateContextServices(model) + .GetRequiredService(); var generator1 = cache.GetOrAdd(property1, entityType, (p, et) => new GuidValueGenerator()); Assert.NotNull(generator1); @@ -31,16 +31,21 @@ public void Uses_single_generator_per_property() Assert.NotSame(generator1, generator2); } - private static IMutableModel CreateModel(bool generateValues = true) + private static IModel CreateModel(bool generateValues = true) { - IMutableModel model = new Model(); + var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + modelBuilder.Entity("Led", eb => + { + eb.Property("Id"); + eb.Property("Zeppelin"); + var property = eb.Property("Stairway"); + if (generateValues) + { + property.ValueGeneratedOnAdd(); + } + }); - var entityType = model.AddEntityType("Led"); - entityType.AddProperty("Zeppelin", typeof(Guid)); - entityType.AddProperty("Stairway", typeof(Guid)) - .ValueGenerated = generateValues ? ValueGenerated.OnAdd : ValueGenerated.Never; - - return model; + return modelBuilder.FinalizeModel(); } } } diff --git a/test/EFCore.Tests/ValueGeneration/ValueGeneratorSelectorTest.cs b/test/EFCore.Tests/ValueGeneration/ValueGeneratorSelectorTest.cs index 383143fcea5..3f7db80bc23 100644 --- a/test/EFCore.Tests/ValueGeneration/ValueGeneratorSelectorTest.cs +++ b/test/EFCore.Tests/ValueGeneration/ValueGeneratorSelectorTest.cs @@ -93,7 +93,7 @@ private static IModel BuildModel(bool generateValues = true) property.ValueGenerated = generateValues ? ValueGenerated.OnAdd : ValueGenerated.Never; } - return builder.FinalizeModel(); + return InMemoryTestHelpers.Instance.Finalize(builder); } private class AnEntity