diff --git a/src/EFCore.Cosmos/Infrastructure/Internal/CosmosDbOptionExtension.cs b/src/EFCore.Cosmos/Infrastructure/Internal/CosmosDbOptionExtension.cs index 415211e072a..97fcfa28b66 100644 --- a/src/EFCore.Cosmos/Infrastructure/Internal/CosmosDbOptionExtension.cs +++ b/src/EFCore.Cosmos/Infrastructure/Internal/CosmosDbOptionExtension.cs @@ -181,8 +181,8 @@ public virtual CosmosOptionsExtension WithConnectionString([CanBeNull] string? c /// 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 string? DatabaseName - => _databaseName; + public virtual string DatabaseName + => _databaseName!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs b/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs index f752814c8ea..88842a71f18 100644 --- a/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs +++ b/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs @@ -8,9 +8,7 @@ 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.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations.Design; using Microsoft.EntityFrameworkCore.Migrations.Internal; diff --git a/src/EFCore.Design/Design/Internal/DesignTimeServicesBuilder.cs b/src/EFCore.Design/Design/Internal/DesignTimeServicesBuilder.cs index 5058b5801f2..6fe6a47e8c5 100644 --- a/src/EFCore.Design/Design/Internal/DesignTimeServicesBuilder.cs +++ b/src/EFCore.Design/Design/Internal/DesignTimeServicesBuilder.cs @@ -51,9 +51,16 @@ public DesignTimeServicesBuilder( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IServiceProvider Build([NotNull] DbContext context) - { - Check.NotNull(context, nameof(context)); + => CreateServiceCollection(Check.NotNull(context, nameof(context))).BuildServiceProvider(); + /// + /// 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 IServiceCollection CreateServiceCollection([NotNull] DbContext context) + { var services = new ServiceCollection() .AddEntityFrameworkDesignTimeServices(_reporter) .AddDbContextDesignTimeServices(context); @@ -61,8 +68,7 @@ public virtual IServiceProvider Build([NotNull] DbContext context) ConfigureProviderServices(provider, services); ConfigureReferencedServices(services, provider); ConfigureUserServices(services); - - return services.BuildServiceProvider(); + return services; } /// @@ -72,16 +78,22 @@ public virtual IServiceProvider Build([NotNull] DbContext context) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IServiceProvider Build([NotNull] string provider) - { - Check.NotEmpty(provider, nameof(provider)); + => CreateServiceCollection(Check.NotEmpty(provider, nameof(provider))).BuildServiceProvider(); + /// + /// 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 IServiceCollection CreateServiceCollection([NotNull] string provider) + { var services = new ServiceCollection() .AddEntityFrameworkDesignTimeServices(_reporter, GetApplicationServices); ConfigureProviderServices(provider, services, throwOnError: true); ConfigureReferencedServices(services, provider); ConfigureUserServices(services); - - return services.BuildServiceProvider(); + return services; } private IServiceProvider GetApplicationServices() diff --git a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs index f8659453cae..da98df67899 100644 --- a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs +++ b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs @@ -9,6 +9,7 @@ using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -44,6 +45,7 @@ public class RelationalScaffoldingModelFactory : IScaffoldingModelFactory private readonly ICSharpUtilities _cSharpUtilities; private readonly IScaffoldingTypeMapper _scaffoldingTypeMapper; private readonly LoggingDefinitions _loggingDefinitions; + private readonly IModelRuntimeInitializer _modelRuntimeInitializer; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -57,7 +59,8 @@ public RelationalScaffoldingModelFactory( [NotNull] IPluralizer pluralizer, [NotNull] ICSharpUtilities cSharpUtilities, [NotNull] IScaffoldingTypeMapper scaffoldingTypeMapper, - [NotNull] LoggingDefinitions loggingDefinitions) + [NotNull] LoggingDefinitions loggingDefinitions, + [NotNull] IModelRuntimeInitializer modelRuntimeInitializer) { Check.NotNull(reporter, nameof(reporter)); Check.NotNull(candidateNamingService, nameof(candidateNamingService)); @@ -65,6 +68,7 @@ public RelationalScaffoldingModelFactory( Check.NotNull(cSharpUtilities, nameof(cSharpUtilities)); Check.NotNull(scaffoldingTypeMapper, nameof(scaffoldingTypeMapper)); Check.NotNull(loggingDefinitions, nameof(loggingDefinitions)); + Check.NotNull(modelRuntimeInitializer, nameof(modelRuntimeInitializer)); _reporter = reporter; _candidateNamingService = candidateNamingService; @@ -72,6 +76,7 @@ public RelationalScaffoldingModelFactory( _cSharpUtilities = cSharpUtilities; _scaffoldingTypeMapper = scaffoldingTypeMapper; _loggingDefinitions = loggingDefinitions; + _modelRuntimeInitializer = modelRuntimeInitializer; } /// @@ -108,7 +113,7 @@ public virtual IModel Create(DatabaseModel databaseModel, ModelReverseEngineerOp VisitDatabaseModel(modelBuilder, databaseModel); - return modelBuilder.FinalizeModel(); + return _modelRuntimeInitializer.Initialize(modelBuilder.FinalizeModel(), null); } /// diff --git a/src/EFCore.Proxies/Proxies/Internal/IProxyFactory.cs b/src/EFCore.Proxies/Proxies/Internal/IProxyFactory.cs index f41aa5ba2e8..24721fedba9 100644 --- a/src/EFCore.Proxies/Proxies/Internal/IProxyFactory.cs +++ b/src/EFCore.Proxies/Proxies/Internal/IProxyFactory.cs @@ -49,7 +49,7 @@ object CreateProxy( /// Type CreateProxyType( [NotNull] ProxiesOptionsExtension options, - [NotNull] IEntityType entityType); + [NotNull] IReadOnlyEntityType entityType); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Proxies/Proxies/Internal/ProxyBindingRewriter.cs b/src/EFCore.Proxies/Proxies/Internal/ProxyBindingRewriter.cs index 4800928633b..a0ac5286dc7 100644 --- a/src/EFCore.Proxies/Proxies/Internal/ProxyBindingRewriter.cs +++ b/src/EFCore.Proxies/Proxies/Internal/ProxyBindingRewriter.cs @@ -68,175 +68,178 @@ public virtual void ProcessModelFinalizing( foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) { var clrType = entityType.ClrType; - if (!clrType.IsAbstract) + if (clrType.IsAbstract) { - if (clrType.IsSealed) - { - throw new InvalidOperationException(ProxiesStrings.ItsASeal(entityType.DisplayName())); - } + continue; + } - var proxyType = _proxyFactory.CreateProxyType(_options, (IEntityType)entityType); + if (clrType.IsSealed) + { + throw new InvalidOperationException(ProxiesStrings.ItsASeal(entityType.DisplayName())); + } - // WARNING: This code is EF internal; it should not be copied. See #10789 #14554 + var proxyType = _proxyFactory.CreateProxyType(_options, entityType); + + // WARNING: This code is EF internal; it should not be copied. See #10789 #14554 #pragma warning disable EF1001 // Internal EF Core API usage. - var binding = (InstantiationBinding?)entityType[CoreAnnotationNames.ConstructorBinding]; - if (binding == null) - { - _directBindingConvention.ProcessModelFinalizing(modelBuilder, context); - } + var binding = ((EntityType)entityType).ConstructorBinding; + if (binding == null) + { + _directBindingConvention.ProcessModelFinalizing(modelBuilder, context); + binding = ((EntityType)entityType).ConstructorBinding!; + } + + ((EntityType)entityType).SetConstructorBinding( + UpdateConstructorBindings(entityType, proxyType, binding), + ConfigurationSource.Convention); - binding = (InstantiationBinding)entityType[CoreAnnotationNames.ConstructorBinding]!; - UpdateConstructorBindings(CoreAnnotationNames.ConstructorBinding, binding); + binding = ((EntityType)entityType).ServiceOnlyConstructorBinding; + if (binding != null) + { + ((EntityType)entityType).SetServiceOnlyConstructorBinding( + UpdateConstructorBindings(entityType, proxyType, binding), + ConfigurationSource.Convention); + } +#pragma warning restore EF1001 // Internal EF Core API usage. - binding = (InstantiationBinding?)entityType[CoreAnnotationNames.ServiceOnlyConstructorBinding]; - if (binding != null) + foreach (var navigationBase in entityType.GetDeclaredNavigations() + .Concat(entityType.GetDeclaredSkipNavigations())) + { + if (navigationBase.PropertyInfo == null) { - UpdateConstructorBindings(CoreAnnotationNames.ServiceOnlyConstructorBinding, binding); + throw new InvalidOperationException( + ProxiesStrings.FieldProperty(navigationBase.Name, entityType.DisplayName())); } -#pragma warning restore EF1001 // Internal EF Core API usage. - foreach (var navigationBase in entityType.GetDeclaredNavigations() - .Concat(entityType.GetDeclaredSkipNavigations())) + if (_options.UseChangeTrackingProxies + && navigationBase.PropertyInfo.SetMethod?.IsReallyVirtual() == false) { - if (navigationBase.PropertyInfo == null) - { - throw new InvalidOperationException( - ProxiesStrings.FieldProperty(navigationBase.Name, entityType.DisplayName())); - } + throw new InvalidOperationException( + ProxiesStrings.NonVirtualProperty(navigationBase.Name, entityType.DisplayName())); + } - if (_options.UseChangeTrackingProxies - && navigationBase.PropertyInfo.SetMethod?.IsReallyVirtual() == false) + if (_options.UseLazyLoadingProxies) + { + if (!navigationBase.PropertyInfo.GetMethod.IsReallyVirtual() + && (!(navigationBase is INavigation navigation + && navigation.ForeignKey.IsOwnership))) { throw new InvalidOperationException( ProxiesStrings.NonVirtualProperty(navigationBase.Name, entityType.DisplayName())); } - if (_options.UseLazyLoadingProxies) - { - if (!navigationBase.PropertyInfo.GetMethod.IsReallyVirtual() - && (!(navigationBase is INavigation navigation - && navigation.ForeignKey.IsOwnership))) - { - throw new InvalidOperationException( - ProxiesStrings.NonVirtualProperty(navigationBase.Name, entityType.DisplayName())); - } - - navigationBase.SetPropertyAccessMode(PropertyAccessMode.Field); - } + navigationBase.SetPropertyAccessMode(PropertyAccessMode.Field); } + } - if (_options.UseChangeTrackingProxies) + if (_options.UseChangeTrackingProxies) + { + var indexerChecked = false; + foreach (var property in entityType.GetDeclaredProperties() + .Where(p => !p.IsShadowProperty())) { - var indexerChecked = false; - foreach (var property in entityType.GetDeclaredProperties() - .Where(p => !p.IsShadowProperty())) + if (property.IsIndexerProperty()) { - if (property.IsIndexerProperty()) + if (!indexerChecked) { - if (!indexerChecked) - { - indexerChecked = true; + indexerChecked = true; - if (!property.PropertyInfo!.SetMethod.IsReallyVirtual()) + if (!property.PropertyInfo!.SetMethod.IsReallyVirtual()) + { + if (clrType.IsGenericType + && clrType.GetGenericTypeDefinition() == typeof(Dictionary<,>) + && clrType.GenericTypeArguments[0] == typeof(string)) { - if (clrType.IsGenericType - && clrType.GetGenericTypeDefinition() == typeof(Dictionary<,>) - && clrType.GenericTypeArguments[0] == typeof(string)) - { - if (entityType.GetProperties().Any(p => !p.IsPrimaryKey())) - { - throw new InvalidOperationException( - ProxiesStrings.DictionaryCannotBeProxied( - clrType.ShortDisplayName(), - entityType.DisplayName(), - typeof(IDictionary<,>).MakeGenericType(clrType.GenericTypeArguments) - .ShortDisplayName())); - } - } - else + if (entityType.GetProperties().Any(p => !p.IsPrimaryKey())) { throw new InvalidOperationException( - ProxiesStrings.NonVirtualIndexerProperty(entityType.DisplayName())); + ProxiesStrings.DictionaryCannotBeProxied( + clrType.ShortDisplayName(), + entityType.DisplayName(), + typeof(IDictionary<,>).MakeGenericType(clrType.GenericTypeArguments) + .ShortDisplayName())); } } - } - } - else - { - if (property.PropertyInfo == null) - { - throw new InvalidOperationException( - ProxiesStrings.FieldProperty(property.Name, entityType.DisplayName())); - } - - if (property.PropertyInfo.SetMethod?.IsReallyVirtual() == false) - { - throw new InvalidOperationException( - ProxiesStrings.NonVirtualProperty(property.Name, entityType.DisplayName())); + else + { + throw new InvalidOperationException( + ProxiesStrings.NonVirtualIndexerProperty(entityType.DisplayName())); + } } } } - } - - void UpdateConstructorBindings(string bindingAnnotationName, InstantiationBinding binding) - { - if (_options.UseLazyLoadingProxies) + else { - foreach (var conflictingProperty in entityType.GetDerivedTypes() - .SelectMany(e => e.GetDeclaredServiceProperties().Where(p => p.ClrType == typeof(ILazyLoader))) - .ToList()) + if (property.PropertyInfo == null) { - conflictingProperty.DeclaringEntityType.RemoveServiceProperty(conflictingProperty.Name); + throw new InvalidOperationException( + ProxiesStrings.FieldProperty(property.Name, entityType.DisplayName())); } - var serviceProperty = entityType.GetServiceProperties() - .FirstOrDefault(e => e.ClrType == typeof(ILazyLoader)); - if (serviceProperty == null) + if (property.PropertyInfo.SetMethod?.IsReallyVirtual() == false) { - serviceProperty = entityType.AddServiceProperty(_lazyLoaderProperty); - serviceProperty.SetParameterBinding( - (ServiceParameterBinding)new LazyLoaderParameterBindingFactory( - _lazyLoaderParameterBindingFactoryDependencies) - .Bind( - entityType, - typeof(ILazyLoader), - nameof(IProxyLazyLoader.LazyLoader))); + throw new InvalidOperationException( + ProxiesStrings.NonVirtualProperty(property.Name, entityType.DisplayName())); } - - entityType.SetAnnotation( - bindingAnnotationName, - new FactoryMethodBinding( - _proxyFactory, - _createLazyLoadingProxyMethod, - new List - { - new ContextParameterBinding(typeof(DbContext)), - new EntityTypeParameterBinding(), - new DependencyInjectionParameterBinding( - typeof(ILazyLoader), typeof(ILazyLoader), (IPropertyBase)serviceProperty), - new ObjectArrayParameterBinding(binding.ParameterBindings) - }, - proxyType)); - } - else - { - entityType.SetAnnotation( - bindingAnnotationName, - new FactoryMethodBinding( - _proxyFactory, - _createProxyMethod, - new List - { - new ContextParameterBinding(typeof(DbContext)), - new EntityTypeParameterBinding(), - new ObjectArrayParameterBinding(binding.ParameterBindings) - }, - proxyType)); } } } } } } + + private InstantiationBinding UpdateConstructorBindings( + IConventionEntityType entityType, Type proxyType, InstantiationBinding binding) + { + if (_options?.UseLazyLoadingProxies == true) + { + foreach (var conflictingProperty in entityType.GetDerivedTypes() + .SelectMany(e => e.GetDeclaredServiceProperties().Where(p => p.ClrType == typeof(ILazyLoader))) + .ToList()) + { + conflictingProperty.DeclaringEntityType.RemoveServiceProperty(conflictingProperty.Name); + } + + var serviceProperty = entityType.GetServiceProperties() + .FirstOrDefault(e => e.ClrType == typeof(ILazyLoader)); + if (serviceProperty == null) + { + serviceProperty = entityType.AddServiceProperty(_lazyLoaderProperty); + serviceProperty.SetParameterBinding( + (ServiceParameterBinding)new LazyLoaderParameterBindingFactory( + _lazyLoaderParameterBindingFactoryDependencies) + .Bind( + entityType, + typeof(ILazyLoader), + nameof(IProxyLazyLoader.LazyLoader))); + } + + return new FactoryMethodBinding( + _proxyFactory, + _createLazyLoadingProxyMethod, + new List + { + new ContextParameterBinding(typeof(DbContext)), + new EntityTypeParameterBinding(), + new DependencyInjectionParameterBinding( + typeof(ILazyLoader), typeof(ILazyLoader), (IPropertyBase)serviceProperty), + new ObjectArrayParameterBinding(binding.ParameterBindings) + }, + proxyType); + } + else + { + return new FactoryMethodBinding( + _proxyFactory, + _createProxyMethod, + new List + { + new ContextParameterBinding(typeof(DbContext)), + new EntityTypeParameterBinding(), + new ObjectArrayParameterBinding(binding.ParameterBindings) + }, + proxyType); + } + } } } diff --git a/src/EFCore.Proxies/Proxies/Internal/ProxyFactory.cs b/src/EFCore.Proxies/Proxies/Internal/ProxyFactory.cs index 313967d4b25..c3cddb2ff14 100644 --- a/src/EFCore.Proxies/Proxies/Internal/ProxyFactory.cs +++ b/src/EFCore.Proxies/Proxies/Internal/ProxyFactory.cs @@ -62,10 +62,10 @@ public virtual object Create( /// public virtual Type CreateProxyType( ProxiesOptionsExtension options, - IEntityType entityType) + IReadOnlyEntityType entityType) => _generator.ProxyBuilder.CreateClassProxyType( entityType.ClrType, - GetInterfacesToProxy(options, entityType), + GetInterfacesToProxy(options, entityType.ClrType), ProxyGenerationOptions.Default); /// @@ -100,7 +100,7 @@ private object CreateLazyLoadingProxy( object[] constructorArguments) => _generator.CreateClassProxy( entityType.ClrType, - GetInterfacesToProxy(options, entityType), + GetInterfacesToProxy(options, entityType.ClrType), ProxyGenerationOptions.Default, constructorArguments, GetNotifyChangeInterceptors(options, entityType, new LazyLoadingInterceptor(entityType, loader))); @@ -143,14 +143,14 @@ private object CreateProxy( object[] constructorArguments) => _generator.CreateClassProxy( entityType.ClrType, - GetInterfacesToProxy(options, entityType), + GetInterfacesToProxy(options, entityType.ClrType), ProxyGenerationOptions.Default, constructorArguments, GetNotifyChangeInterceptors(options, entityType)); private Type[] GetInterfacesToProxy( ProxiesOptionsExtension options, - IEntityType entityType) + Type type) { var interfacesToProxy = new List(); @@ -161,12 +161,12 @@ private Type[] GetInterfacesToProxy( if (options.UseChangeTrackingProxies) { - if (!_notifyPropertyChangedInterface.IsAssignableFrom(entityType.ClrType)) + if (!_notifyPropertyChangedInterface.IsAssignableFrom(type)) { interfacesToProxy.Add(_notifyPropertyChangedInterface); } - if (!_notifyPropertyChangingInterface.IsAssignableFrom(entityType.ClrType)) + if (!_notifyPropertyChangingInterface.IsAssignableFrom(type)) { interfacesToProxy.Add(_notifyPropertyChangingInterface); } diff --git a/src/EFCore.Relational/Metadata/Conventions/DbFunctionTypeMappingConvention.cs b/src/EFCore.Relational/Metadata/Conventions/DbFunctionTypeMappingConvention.cs index 46b471a136f..d727652938b 100644 --- a/src/EFCore.Relational/Metadata/Conventions/DbFunctionTypeMappingConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/DbFunctionTypeMappingConvention.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.Builders; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; @@ -14,6 +15,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions /// /// A convention configure type mapping for instances. /// + [Obsolete("Use IModelRuntimeInitializer.Initialize instead.")] public class DbFunctionTypeMappingConvention : IModelFinalizingConvention { private readonly IRelationalTypeMappingSource _relationalTypeMappingSource; @@ -44,6 +46,7 @@ public virtual void ProcessModelFinalizing( foreach (var dbFunction in modelBuilder.Metadata.GetDbFunctions()) { // TODO: This check needs to be updated to skip over enumerable parameter of aggregate. + // Also in DbFunctionParameter.TypeMapping foreach (var parameter in dbFunction.Parameters) { parameter.Builder!.HasTypeMapping( diff --git a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs index 6a14683ff0b..233691a1242 100644 --- a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs +++ b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs @@ -107,7 +107,6 @@ public override ConventionSet CreateConventionSet() conventionSet.ModelFinalizingConventions.Add(new EntityTypeHierarchyMappingConvention(Dependencies, RelationalDependencies)); conventionSet.ModelFinalizingConventions.Add(new SequenceUniquificationConvention(Dependencies, RelationalDependencies)); conventionSet.ModelFinalizingConventions.Add(new SharedTableConvention(Dependencies, RelationalDependencies)); - conventionSet.ModelFinalizingConventions.Add(new DbFunctionTypeMappingConvention(Dependencies, RelationalDependencies)); ReplaceConvention( conventionSet.ModelFinalizingConventions, (QueryFilterRewritingConvention)new RelationalQueryFilterRewritingConvention( diff --git a/src/EFCore.Relational/Metadata/IConventionDbFunctionParameter.cs b/src/EFCore.Relational/Metadata/IConventionDbFunctionParameter.cs index ec0d0eb4e0e..83b93617126 100644 --- a/src/EFCore.Relational/Metadata/IConventionDbFunctionParameter.cs +++ b/src/EFCore.Relational/Metadata/IConventionDbFunctionParameter.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.Builders; using Microsoft.EntityFrameworkCore.Storage; @@ -22,7 +23,8 @@ public interface IConventionDbFunctionParameter : IConventionAnnotatable, IReadO /// /// The for configuring this function parameter. /// - new IConventionDbFunctionParameterBuilder? Builder { get; } + /// If the function has been removed from the model. + new IConventionDbFunctionParameterBuilder Builder { get; } /// /// Returns the configuration source for the parameter. diff --git a/src/EFCore.Relational/Metadata/IDbFunctionParameter.cs b/src/EFCore.Relational/Metadata/IDbFunctionParameter.cs index 5a3edbc7a85..d9e284b9a9c 100644 --- a/src/EFCore.Relational/Metadata/IDbFunctionParameter.cs +++ b/src/EFCore.Relational/Metadata/IDbFunctionParameter.cs @@ -18,16 +18,6 @@ public interface IDbFunctionParameter : IReadOnlyDbFunctionParameter, IAnnotatab /// new IDbFunction Function { get; } - /// - /// Gets the store type of this parameter. - /// - new string StoreType { get; } - - /// - /// Gets the for this parameter. - /// - new RelationalTypeMapping TypeMapping { get; } - /// /// Gets the associated . /// diff --git a/src/EFCore.Relational/Metadata/Internal/Column.cs b/src/EFCore.Relational/Metadata/Internal/Column.cs index e2742ec5cd3..e98e8d424d0 100644 --- a/src/EFCore.Relational/Metadata/Internal/Column.cs +++ b/src/EFCore.Relational/Metadata/Internal/Column.cs @@ -30,9 +30,14 @@ public Column([NotNull] string name, [NotNull] string type, [NotNull] Table tabl { } - /// - public new virtual ITable Table - => (ITable)base.Table; + /// + /// 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 new virtual Table Table + => (Table)base.Table; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -43,6 +48,13 @@ public Column([NotNull] string name, [NotNull] string type, [NotNull] Table tabl public override string ToString() => this.ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + /// + ITable IColumn.Table + { + [DebuggerStepThrough] + get => Table; + } + /// IEnumerable IColumn.PropertyMappings { diff --git a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs index a6fb4e8c597..3131109a6d4 100644 --- a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs +++ b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs @@ -542,7 +542,16 @@ public virtual string? StoreType /// public virtual RelationalTypeMapping? TypeMapping { - get => _typeMapping; + get => IsReadOnly && IsScalar + ? NonCapturingLazyInitializer.EnsureInitialized(ref _typeMapping, this, static dbFunction => + { + var relationalTypeMappingSource = + (IRelationalTypeMappingSource)((IModel)dbFunction.Model).GetModelDependencies().TypeMappingSource; + return !string.IsNullOrEmpty(dbFunction._storeType) + ? relationalTypeMappingSource.FindMapping(dbFunction._storeType)! + : relationalTypeMappingSource.FindMapping(dbFunction.ReturnType)!; + }) + : _typeMapping; set => SetTypeMapping(value, ConfigurationSource.Explicit); } diff --git a/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs b/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs index 1494e9a44b2..929ead1d371 100644 --- a/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs +++ b/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs @@ -6,6 +6,7 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders.Internal; using Microsoft.EntityFrameworkCore.Storage; @@ -34,6 +35,7 @@ public class DbFunctionParameter : private ConfigurationSource? _storeTypeConfigurationSource; private ConfigurationSource? _typeMappingConfigurationSource; private ConfigurationSource? _propagatesNullabilityConfigurationSource; + private InternalDbFunctionParameterBuilder? _builder; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -53,7 +55,7 @@ public DbFunctionParameter( Name = name; Function = function; ClrType = clrType; - Builder = new InternalDbFunctionParameterBuilder(this, function.Builder.ModelBuilder); + _builder = new InternalDbFunctionParameterBuilder(this, function.Builder.ModelBuilder); } /// @@ -62,11 +64,11 @@ public DbFunctionParameter( /// 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 InternalDbFunctionParameterBuilder? Builder { get; } - - /// - IConventionDbFunctionParameterBuilder? IConventionDbFunctionParameter.Builder - => Builder; + public virtual InternalDbFunctionParameterBuilder Builder + { + [DebuggerStepThrough] + get => _builder ?? throw new InvalidOperationException(CoreStrings.ObjectRemovedFromModel); + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -74,28 +76,30 @@ public DbFunctionParameter( /// 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 DbFunction Function { get; } + public virtual bool IsInModel + => _builder is not null; - /// - IConventionDbFunction IConventionDbFunctionParameter.Function - { - [DebuggerStepThrough] - get => Function; - } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void SetRemovedFromModel() + => _builder = null; - /// - IReadOnlyDbFunction IReadOnlyDbFunctionParameter.Function - { - [DebuggerStepThrough] - get => Function; - } + /// + /// Indicates whether the function parameter is read-only. + /// + public override bool IsReadOnly => ((Annotatable)Function.Model).IsReadOnly; - /// - IMutableDbFunction IMutableDbFunctionParameter.Function - { - [DebuggerStepThrough] - get => Function; - } + /// + /// 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 DbFunction Function { get; } /// public virtual string Name { get; } @@ -108,6 +112,9 @@ IMutableDbFunction IMutableDbFunctionParameter.Function public virtual ConfigurationSource GetConfigurationSource() => Function.GetConfigurationSource(); + /// + public virtual IStoreFunctionParameter StoreFunctionParameter { get; [param: NotNull] set; } = default!; + /// /// 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 @@ -144,11 +151,6 @@ public virtual string? StoreType public virtual ConfigurationSource? GetStoreTypeConfigurationSource() => _storeTypeConfigurationSource; - /// - [DebuggerStepThrough] - string? IConventionDbFunctionParameter.SetStoreType(string? storeType, bool fromDataAnnotation) - => SetStoreType(storeType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - /// /// 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 @@ -157,14 +159,19 @@ public virtual string? StoreType /// public virtual RelationalTypeMapping? TypeMapping { - get => _typeMapping; + get => IsReadOnly + ? NonCapturingLazyInitializer.EnsureInitialized(ref _typeMapping, this, static parameter => + { + var relationalTypeMappingSource = + (IRelationalTypeMappingSource)((IModel)parameter.Function.Model).GetModelDependencies().TypeMappingSource; + return !string.IsNullOrEmpty(parameter._storeType) + ? relationalTypeMappingSource.FindMapping(parameter._storeType)! + : relationalTypeMappingSource.FindMapping(parameter.ClrType)!; + }) + : _typeMapping; set => SetTypeMapping(value, ConfigurationSource.Explicit); } - // Model validation ensures all parameters have a type mapping - RelationalTypeMapping IDbFunctionParameter.TypeMapping - => _typeMapping!; - /// /// 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 @@ -231,9 +238,6 @@ public virtual bool SetPropagatesNullability(bool propagatesNullability, Configu public virtual ConfigurationSource? GetTypeMappingConfigurationSource() => _typeMappingConfigurationSource; - /// - public virtual IStoreFunctionParameter StoreFunctionParameter { get; [param: NotNull] set; } = default!; - /// /// 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 @@ -244,10 +248,31 @@ public override string ToString() => this.ToDebugString(MetadataDebugStringOptions.SingleLineDefault); /// - string IDbFunctionParameter.StoreType + IConventionDbFunctionParameterBuilder IConventionDbFunctionParameter.Builder + { + [DebuggerStepThrough] + get => Builder; + } + + /// + IConventionDbFunction IConventionDbFunctionParameter.Function + { + [DebuggerStepThrough] + get => Function; + } + + /// + IReadOnlyDbFunction IReadOnlyDbFunctionParameter.Function { [DebuggerStepThrough] - get => StoreType!; // Model validation ensures all parameters have a type mapping + get => Function; + } + + /// + IMutableDbFunction IMutableDbFunctionParameter.Function + { + [DebuggerStepThrough] + get => Function; } /// @@ -261,5 +286,10 @@ IDbFunction IDbFunctionParameter.Function [DebuggerStepThrough] RelationalTypeMapping? IConventionDbFunctionParameter.SetTypeMapping(RelationalTypeMapping? typeMapping, bool fromDataAnnotation) => SetTypeMapping(typeMapping, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + string? IConventionDbFunctionParameter.SetStoreType(string? storeType, bool fromDataAnnotation) + => SetStoreType(storeType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); } } diff --git a/src/EFCore.Relational/Metadata/Internal/ForeignKeyConstraint.cs b/src/EFCore.Relational/Metadata/Internal/ForeignKeyConstraint.cs index c8a2979833d..0d2259ad6a9 100644 --- a/src/EFCore.Relational/Metadata/Internal/ForeignKeyConstraint.cs +++ b/src/EFCore.Relational/Metadata/Internal/ForeignKeyConstraint.cs @@ -83,6 +83,14 @@ public ForeignKeyConstraint( /// public virtual IReadOnlyList PrincipalColumns { 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. + /// + public override bool IsReadOnly => Table.Model.IsReadOnly; + /// public virtual ReferentialAction OnDeleteAction { get; set; } diff --git a/src/EFCore.Relational/Metadata/Internal/FunctionColumn.cs b/src/EFCore.Relational/Metadata/Internal/FunctionColumn.cs index fcbbe265bad..7ca253ce836 100644 --- a/src/EFCore.Relational/Metadata/Internal/FunctionColumn.cs +++ b/src/EFCore.Relational/Metadata/Internal/FunctionColumn.cs @@ -30,9 +30,14 @@ public FunctionColumn([NotNull] string name, [NotNull] string type, [NotNull] St { } - /// - public virtual IStoreFunction Function - => (IStoreFunction)Table; + /// + /// 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 StoreFunction Function + => (StoreFunction)Table; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -43,6 +48,13 @@ public virtual IStoreFunction Function public override string ToString() => this.ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + /// + IStoreFunction IFunctionColumn.Function + { + [DebuggerStepThrough] + get => Function; + } + /// IEnumerable IFunctionColumn.PropertyMappings { diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index 968962105ee..0377d221d17 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -22,6 +22,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal /// public class RelationalModel : Annotatable, IRelationalModel { + private bool _isReadOnly; + /// /// 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 @@ -36,6 +38,14 @@ public RelationalModel([NotNull] IModel model) /// public virtual IModel Model { 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. + /// + public override bool IsReadOnly => _isReadOnly; + /// /// 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 @@ -219,6 +229,7 @@ public static IModel Add( databaseModel.AddAnnotations(relationalAnnotationProvider.For(databaseModel)); } + databaseModel._isReadOnly = true; model.AddRuntimeAnnotation(RelationalAnnotationNames.RelationalModel, databaseModel); return model; } @@ -235,7 +246,8 @@ private static void AddDefaultMappings(RelationalModel databaseModel, IEntityTyp var tableMapping = new TableMappingBase(entityType, defaultTable, includesDerivedTypes: true) { - IsSharedTablePrincipal = true, IsSplitEntityTypePrincipal = true + IsSharedTablePrincipal = true, + IsSplitEntityTypePrincipal = true }; foreach (var property in entityType.GetProperties()) @@ -249,7 +261,7 @@ private static void AddDefaultMappings(RelationalModel databaseModel, IEntityTyp var column = (ColumnBase?)defaultTable.FindColumn(columnName); if (column == null) { - column = new (columnName, property.GetColumnType()!, defaultTable); + column = new(columnName, property.GetColumnType()!, defaultTable); column.IsNullable = property.IsColumnNullable(); defaultTable.Columns.Add(columnName, column); } @@ -342,7 +354,7 @@ private static void AddTables(RelationalModel databaseModel, IEntityType entityT var column = (Column?)table.FindColumn(columnName); if (column == null) { - column = new (columnName, property.GetColumnType(mappedTable)!, table); + column = new(columnName, property.GetColumnType(mappedTable)!, table); column.IsNullable = property.IsColumnNullable(mappedTable); table.Columns.Add(columnName, column); } @@ -433,7 +445,7 @@ private static void AddViews(RelationalModel databaseModel, IEntityType entityTy var column = (ViewColumn?)view.FindColumn(columnName); if (column == null) { - column = new (columnName, property.GetColumnType(mappedView)!, view); + column = new(columnName, property.GetColumnType(mappedView)!, view); column.IsNullable = property.IsColumnNullable(mappedView); view.Columns.Add(columnName, column); } @@ -538,7 +550,7 @@ private static void AddSqlQueries(RelationalModel databaseModel, IEntityType ent var column = (SqlQueryColumn?)sqlQuery.FindColumn(columnName); if (column == null) { - column = new (columnName, property.GetColumnType(mappedQuery)!, sqlQuery); + column = new(columnName, property.GetColumnType(mappedQuery)!, sqlQuery); column.IsNullable = property.IsColumnNullable(mappedQuery); sqlQuery.Columns.Add(columnName, column); } @@ -687,7 +699,7 @@ private static FunctionMapping CreateFunctionMapping( var column = (FunctionColumn?)storeFunction.FindColumn(columnName); if (column == null) { - column = new (columnName, property.GetColumnType(mappedFunction)!, storeFunction); + column = new(columnName, property.GetColumnType(mappedFunction)!, storeFunction); column.IsNullable = property.IsColumnNullable(mappedFunction); storeFunction.Columns.Add(columnName, column); } diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs index 547a7ec5f58..9555b9470ab 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs @@ -21,6 +21,33 @@ public class RelationalPropertyOverrides : ConventionAnnotatable private ConfigurationSource? _columnNameConfigurationSource; + /// + /// 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 RelationalPropertyOverrides([NotNull] IReadOnlyProperty property) + { + Property = property; + } + + /// + /// 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 IReadOnlyProperty Property { 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. + /// + public override bool IsReadOnly => ((Annotatable)Property).IsReadOnly; + /// /// 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 @@ -42,6 +69,8 @@ public virtual string? ColumnName /// public virtual string? SetColumnName([CanBeNull] string? columnName, ConfigurationSource configurationSource) { + EnsureMutable(); + _columnName = columnName; _columnNameConfigurationSource = configurationSource.Max(_columnNameConfigurationSource); @@ -93,7 +122,7 @@ public static RelationalPropertyOverrides GetOrCreate( if (!tableOverrides.TryGetValue(storeObject, out var overrides)) { - overrides = new RelationalPropertyOverrides(); + overrides = new RelationalPropertyOverrides(property); tableOverrides.Add(storeObject, overrides); } diff --git a/src/EFCore.Relational/Metadata/Internal/SqlQueryColumn.cs b/src/EFCore.Relational/Metadata/Internal/SqlQueryColumn.cs index bcedbd595c1..37a4838a916 100644 --- a/src/EFCore.Relational/Metadata/Internal/SqlQueryColumn.cs +++ b/src/EFCore.Relational/Metadata/Internal/SqlQueryColumn.cs @@ -30,9 +30,14 @@ public SqlQueryColumn([NotNull] string name, [NotNull] string type, [NotNull] Sq { } - /// - public virtual ISqlQuery SqlQuery - => (ISqlQuery)Table; + /// + /// 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 SqlQuery SqlQuery + => (SqlQuery)Table; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -43,6 +48,13 @@ public virtual ISqlQuery SqlQuery public override string ToString() => this.ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + /// + ISqlQuery ISqlQueryColumn.SqlQuery + { + [DebuggerStepThrough] + get => SqlQuery; + } + /// IEnumerable ISqlQueryColumn.PropertyMappings { diff --git a/src/EFCore.Relational/Metadata/Internal/StoreFunctionParameter.cs b/src/EFCore.Relational/Metadata/Internal/StoreFunctionParameter.cs index 34761edbd30..65614b44495 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoreFunctionParameter.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoreFunctionParameter.cs @@ -35,8 +35,21 @@ public StoreFunctionParameter( parameter.StoreFunctionParameter = this; } - /// - public virtual IStoreFunction Function { 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. + /// + public virtual StoreFunction Function { 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. + /// + public override bool IsReadOnly => Function.IsReadOnly; /// public virtual string Name { get; } @@ -61,10 +74,19 @@ public StoreFunctionParameter( public override string ToString() => this.ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + /// + IStoreFunction IStoreFunctionParameter.Function + { + [DebuggerStepThrough] + get => Function; + } + + /// IEnumerable IStoreFunctionParameter.DbFunctionParameters { [DebuggerStepThrough] get => DbFunctionParameters; } + } } diff --git a/src/EFCore.Relational/Metadata/Internal/TableIndex.cs b/src/EFCore.Relational/Metadata/Internal/TableIndex.cs index e53fe194560..3162715bc4b 100644 --- a/src/EFCore.Relational/Metadata/Internal/TableIndex.cs +++ b/src/EFCore.Relational/Metadata/Internal/TableIndex.cs @@ -64,6 +64,14 @@ public TableIndex( /// public virtual IReadOnlyList Columns { 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. + /// + public override bool IsReadOnly => Table.Model.IsReadOnly; + /// public virtual bool IsUnique { get; } diff --git a/src/EFCore.Relational/Metadata/Internal/UniqueConstraint.cs b/src/EFCore.Relational/Metadata/Internal/UniqueConstraint.cs index 71fd4112139..b412b661a41 100644 --- a/src/EFCore.Relational/Metadata/Internal/UniqueConstraint.cs +++ b/src/EFCore.Relational/Metadata/Internal/UniqueConstraint.cs @@ -60,6 +60,14 @@ public UniqueConstraint( /// public virtual IReadOnlyList Columns { 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. + /// + public override bool IsReadOnly => Table.Model.IsReadOnly; + /// ITable IUniqueConstraint.Table => Table; diff --git a/src/EFCore.Relational/Metadata/Internal/ViewColumn.cs b/src/EFCore.Relational/Metadata/Internal/ViewColumn.cs index 8e9a003b6ea..16f09409ac4 100644 --- a/src/EFCore.Relational/Metadata/Internal/ViewColumn.cs +++ b/src/EFCore.Relational/Metadata/Internal/ViewColumn.cs @@ -30,9 +30,14 @@ public ViewColumn([NotNull] string name, [NotNull] string type, [NotNull] View v { } - /// - public virtual IView View - => (IView)Table; + /// + /// 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 View View + => (View)Table; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -43,6 +48,13 @@ public virtual IView View public override string ToString() => this.ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + /// + IView IViewColumn.View + { + [DebuggerStepThrough] + get => View; + } + /// IEnumerable IViewColumn.PropertyMappings { diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs index 73f5ddcc722..433a93f39cb 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs @@ -361,7 +361,7 @@ private void SetServiceProperties(EntityState oldState, EntityState newState) { this[serviceProperty] = serviceProperty - .GetParameterBinding() + .ParameterBinding .ServiceDelegate( new MaterializationContext( ValueBuffer.Empty, diff --git a/src/EFCore/Infrastructure/ModelRuntimeInitializer.cs b/src/EFCore/Infrastructure/ModelRuntimeInitializer.cs index 1b3db088bfc..1873857b413 100644 --- a/src/EFCore/Infrastructure/ModelRuntimeInitializer.cs +++ b/src/EFCore/Infrastructure/ModelRuntimeInitializer.cs @@ -4,6 +4,7 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.Extensions.DependencyInjection; @@ -56,12 +57,19 @@ public virtual IModel Initialize( if (model.SetModelDependencies(Dependencies.ModelDependencies)) { InitializeModel(model, preValidation: true); + if (validationLogger != null - && model is IMutableModel) + && model is IConventionModel) { Dependencies.ModelValidator.Validate(model, validationLogger); } + InitializeModel(model, preValidation: false); + + if (model is Model mutableModel) + { + model = mutableModel.OnModelFinalized(); + } } return model; diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index 3bc46350477..e846db25cdc 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -843,8 +843,7 @@ protected virtual void ValidateFieldMapping( .Concat(entityType.GetDeclaredNavigations()) .Where(p => !p.IsShadowProperty() && !p.IsIndexerProperty())); - var constructorBinding = (InstantiationBinding?)entityType[CoreAnnotationNames.ConstructorBinding]; - + var constructorBinding = entityType.ConstructorBinding; if (constructorBinding != null) { foreach (var consumedProperty in constructorBinding.ParameterBindings.SelectMany(p => p.ConsumedProperties)) diff --git a/src/EFCore/Infrastructure/SingletonModelDependencies.cs b/src/EFCore/Infrastructure/SingletonModelDependencies.cs index 8f8d73286bd..374ab522e50 100644 --- a/src/EFCore/Infrastructure/SingletonModelDependencies.cs +++ b/src/EFCore/Infrastructure/SingletonModelDependencies.cs @@ -55,16 +55,32 @@ public sealed record SingletonModelDependencies /// [EntityFrameworkInternal] public SingletonModelDependencies( - [NotNull] ITypeMappingSource typeMappingSource) + [NotNull] ITypeMappingSource typeMappingSource, + [NotNull] IConstructorBindingFactory constructorBindingFactory, + [NotNull] IParameterBindingFactories parameterBindingFactories) { Check.NotNull(typeMappingSource, nameof(typeMappingSource)); + Check.NotNull(constructorBindingFactory, nameof(constructorBindingFactory)); + Check.NotNull(parameterBindingFactories, nameof(parameterBindingFactories)); TypeMappingSource = typeMappingSource; + ConstructorBindingFactory = constructorBindingFactory; + ParameterBindingFactories = parameterBindingFactories; } /// /// The type mapper. /// public ITypeMappingSource TypeMappingSource { get; [param: NotNull] init; } + + /// + /// The constructor binding factory. + /// + public IConstructorBindingFactory ConstructorBindingFactory { get; [param: NotNull] init; } + + /// + /// The parameter binding factories. + /// + public IParameterBindingFactories ParameterBindingFactories { get; [param: NotNull] init; } } } diff --git a/src/EFCore/Metadata/Conventions/ConstructorBindingConvention.cs b/src/EFCore/Metadata/Conventions/ConstructorBindingConvention.cs index 12183b86f4b..29893a60c57 100644 --- a/src/EFCore/Metadata/Conventions/ConstructorBindingConvention.cs +++ b/src/EFCore/Metadata/Conventions/ConstructorBindingConvention.cs @@ -1,13 +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.Collections.Generic; -using System.Linq; -using System.Reflection; using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -46,122 +40,18 @@ public virtual void ProcessModelFinalizing( IConventionModelBuilder modelBuilder, IConventionContext context) { - foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) + foreach (EntityType entityType in modelBuilder.Metadata.GetEntityTypes()) { - if (entityType.ClrType?.IsAbstract == false - && entityType.Builder.CanSetAnnotation(CoreAnnotationNames.ConstructorBinding, null)) + if (!entityType.ClrType.IsAbstract + && ConfigurationSource.Convention.Overrides(entityType.GetConstructorBindingConfigurationSource())) { - var maxServiceParams = 0; - var maxServiceOnlyParams = 0; - var minPropertyParams = int.MaxValue; - var foundBindings = new List(); - var foundServiceOnlyBindings = new List(); - var bindingFailures = new List>(); + Dependencies.ConstructorBindingFactory.GetBindings( + (IMutableEntityType)entityType, out var constructorBinding, out var serviceOnlyBinding); - foreach (var constructor in entityType.ClrType.GetTypeInfo() - .DeclaredConstructors - .Where(c => !c.IsStatic)) - { - // Trying to find the constructor with the most service properties - // followed by the least scalar property parameters - if (Dependencies.ConstructorBindingFactory.TryBindConstructor( - entityType, constructor, out var binding, out var failures)) - { - var serviceParamCount = binding.ParameterBindings.OfType().Count(); - var propertyParamCount = binding.ParameterBindings.Count - serviceParamCount; - - if (propertyParamCount == 0) - { - if (serviceParamCount == maxServiceOnlyParams) - { - foundServiceOnlyBindings.Add(binding); - } - else if (serviceParamCount > maxServiceOnlyParams) - { - foundServiceOnlyBindings.Clear(); - foundServiceOnlyBindings.Add(binding); - - maxServiceOnlyParams = serviceParamCount; - } - } - - if (serviceParamCount == maxServiceParams - && propertyParamCount == minPropertyParams) - { - foundBindings.Add(binding); - } - else if (serviceParamCount > maxServiceParams) - { - foundBindings.Clear(); - foundBindings.Add(binding); - - maxServiceParams = serviceParamCount; - minPropertyParams = propertyParamCount; - } - else if (propertyParamCount < minPropertyParams) - { - foundBindings.Clear(); - foundBindings.Add(binding); - - maxServiceParams = serviceParamCount; - minPropertyParams = propertyParamCount; - } - } - else - { - bindingFailures.Add(failures); - } - } - - if (foundBindings.Count == 0) - { - var constructorErrors = bindingFailures.SelectMany(f => f) - .GroupBy(f => (ConstructorInfo)f.Member) - .Select( - x => CoreStrings.ConstructorBindingFailed( - string.Join("', '", x.Select(f => f.Name)), - entityType.DisplayName() - + "(" - + string.Join( - ", ", x.Key.GetParameters().Select( - y => y.ParameterType.ShortDisplayName() + " " + y.Name) - ) - + ")" - ) - ); - - throw new InvalidOperationException( - CoreStrings.ConstructorNotFound( - entityType.DisplayName(), - string.Join("; ", constructorErrors))); - } - - if (foundBindings.Count > 1) - { - throw new InvalidOperationException( - CoreStrings.ConstructorConflict( - FormatConstructorString(entityType, foundBindings[0]), - FormatConstructorString(entityType, foundBindings[1]))); - } - - entityType.Builder.HasAnnotation( - CoreAnnotationNames.ConstructorBinding, - foundBindings[0]); - - if (foundServiceOnlyBindings.Count == 1) - { - entityType.Builder.HasAnnotation( - CoreAnnotationNames.ServiceOnlyConstructorBinding, - foundServiceOnlyBindings[0]); - } + entityType.Builder.HasConstructorBinding(constructorBinding, ConfigurationSource.Convention); + entityType.Builder.HasServiceOnlyConstructorBinding(serviceOnlyBinding, ConfigurationSource.Convention); } } } - - private static string FormatConstructorString(IReadOnlyEntityType entityType, InstantiationBinding binding) - => entityType.ClrType.ShortDisplayName() - + "(" - + string.Join(", ", binding.ParameterBindings.Select(b => b.ParameterType.ShortDisplayName())) - + ")"; } } diff --git a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilderDependencies.cs b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilderDependencies.cs index 8cd0fe3ebb1..0203df06a35 100644 --- a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilderDependencies.cs +++ b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilderDependencies.cs @@ -94,13 +94,13 @@ public ProviderConventionSetBuilderDependencies( Check.NotNull(validator, nameof(validator)); TypeMappingSource = typeMappingSource; + ConstructorBindingFactory = constructorBindingFactory; ParameterBindingFactories = parameterBindingFactories; 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 diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs index 0003db732a6..dcdc53c4bb2 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs @@ -85,16 +85,6 @@ public IConventionModelBuilder OnModelFinalizing([NotNull] IConventionModelBuild return modelBuilder; } - public IModel OnModelFinalized([NotNull] IModel model) - { - foreach (var modelConvention in _conventionSet.ModelFinalizedConventions) - { - model = modelConvention.ProcessModelFinalized(model); - } - - return model; - } - public IConventionModelBuilder OnModelInitialized([NotNull] IConventionModelBuilder modelBuilder) { using (_dispatcher.DelayConventions()) diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs index 1871f56708f..ab8a63a00cf 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs @@ -66,15 +66,6 @@ public virtual IConventionModelBuilder OnModelInitialized([NotNull] IConventionM public virtual IConventionModelBuilder OnModelFinalizing([NotNull] IConventionModelBuilder modelBuilder) => _immediateConventionScope.OnModelFinalizing(modelBuilder); - /// - /// 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 IModel OnModelFinalized([NotNull] IModel model) - => _immediateConventionScope.OnModelFinalized(model); - /// /// 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/Conventions/ServicePropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/ServicePropertyDiscoveryConvention.cs index 9a5ce788620..b8a9ea984d2 100644 --- a/src/EFCore/Metadata/Conventions/ServicePropertyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/ServicePropertyDiscoveryConvention.cs @@ -118,7 +118,7 @@ private void Process(IConventionEntityTypeBuilder entityTypeBuilder) } entityTypeBuilder.ServiceProperty(propertyInfo)?.HasParameterBinding( - (ServiceParameterBinding)factory.Bind(entityType, propertyInfo.PropertyType, propertyInfo.GetSimpleMemberName())); + (ServiceParameterBinding)factory.Bind(entityType, propertyInfo.PropertyType, name)); } } diff --git a/src/EFCore/Metadata/IConstructorBindingFactory.cs b/src/EFCore/Metadata/IConstructorBindingFactory.cs index 9e3fbfde1ca..5465413f060 100644 --- a/src/EFCore/Metadata/IConstructorBindingFactory.cs +++ b/src/EFCore/Metadata/IConstructorBindingFactory.cs @@ -24,6 +24,42 @@ namespace Microsoft.EntityFrameworkCore.Metadata /// public interface IConstructorBindingFactory { + /// + /// Create a for the constructor with most parameters and + /// the constructor with only service property parameters. + /// + /// The entity type. + /// The binding for the constructor with most parameters. + /// The binding for the constructor with only service property parameters. + void GetBindings( + [NotNull] IConventionEntityType entityType, + [NotNull] out InstantiationBinding constructorBinding, + [NotNull] out InstantiationBinding? serviceOnlyBinding); + + /// + /// Create a for the constructor with most parameters and + /// the constructor with only service property parameters. + /// + /// The entity type. + /// The binding for the constructor with most parameters. + /// The binding for the constructor with only service property parameters. + void GetBindings( + [NotNull] IMutableEntityType entityType, + [NotNull] out InstantiationBinding constructorBinding, + [NotNull] out InstantiationBinding? serviceOnlyBinding); + + /// + /// Create a for the constructor with most parameters and + /// the constructor with only service property parameters. + /// + /// The entity type. + /// The binding for the constructor with most parameters. + /// The binding for the constructor with only service property parameters. + void GetBindings( + [NotNull] IReadOnlyEntityType entityType, + [NotNull] out InstantiationBinding constructorBinding, + [NotNull] out InstantiationBinding? serviceOnlyBinding); + /// /// Attempts to create a for the given entity type and /// diff --git a/src/EFCore/Metadata/IConventionEntityType.cs b/src/EFCore/Metadata/IConventionEntityType.cs index e1f9f07bf48..85c4cb2b7a6 100644 --- a/src/EFCore/Metadata/IConventionEntityType.cs +++ b/src/EFCore/Metadata/IConventionEntityType.cs @@ -102,9 +102,9 @@ void HasNoKey(bool? keyless, bool fromDataAnnotation = false) => SetIsKeyless(keyless, fromDataAnnotation); /// - /// Returns the configuration source for the IsKeyless property. + /// Returns the configuration source for . /// - /// The configuration source for the IsKeyless property. + /// The configuration source for . ConfigurationSource? GetIsKeylessConfigurationSource(); /// diff --git a/src/EFCore/Metadata/IEntityType.cs b/src/EFCore/Metadata/IEntityType.cs index ea11a369fde..2492869b7fa 100644 --- a/src/EFCore/Metadata/IEntityType.cs +++ b/src/EFCore/Metadata/IEntityType.cs @@ -22,6 +22,11 @@ public interface IEntityType : IReadOnlyEntityType, ITypeBase /// new IEntityType? BaseType { get; } + /// + /// Gets the for the preferred constructor. + /// + InstantiationBinding? ConstructorBinding { get; } + /// /// Gets primary key for this entity type. Returns if no primary key is defined. /// diff --git a/src/EFCore/Metadata/IModel.cs b/src/EFCore/Metadata/IModel.cs index 0e7190a9dcd..5a463aaec4c 100644 --- a/src/EFCore/Metadata/IModel.cs +++ b/src/EFCore/Metadata/IModel.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; @@ -83,6 +84,20 @@ public interface IModel : IReadOnlyModel, IAnnotatable SingletonModelDependencies? ModelDependencies => (SingletonModelDependencies?)FindRuntimeAnnotationValue(CoreAnnotationNames.ModelDependencies); + /// + /// Gets the runtime service dependencies. + /// + SingletonModelDependencies GetModelDependencies() + { + var dependencies = ModelDependencies; + if (dependencies == null) + { + throw new InvalidOperationException(CoreStrings.ModelNotFinalized(nameof(GetModelDependencies))); + } + + return dependencies; + } + /// /// Set the runtime service dependencies. /// diff --git a/src/EFCore/Metadata/IParameterBindingFactory.cs b/src/EFCore/Metadata/IParameterBindingFactory.cs index 94bd65ae590..fd64a251274 100644 --- a/src/EFCore/Metadata/IParameterBindingFactory.cs +++ b/src/EFCore/Metadata/IParameterBindingFactory.cs @@ -32,6 +32,18 @@ bool CanBind( [NotNull] Type parameterType, [NotNull] string parameterName); + /// + /// Creates a for the given type and name on the given entity type. + /// + /// The entity type. + /// The parameter type. + /// The parameter name. + /// The binding. + ParameterBinding Bind( + [NotNull] IReadOnlyEntityType entityType, + [NotNull] Type parameterType, + [NotNull] string parameterName); + /// /// Creates a for the given type and name on the given entity type. /// diff --git a/src/EFCore/Metadata/IServiceProperty.cs b/src/EFCore/Metadata/IServiceProperty.cs index 505b57abcd2..e6582e50657 100644 --- a/src/EFCore/Metadata/IServiceProperty.cs +++ b/src/EFCore/Metadata/IServiceProperty.cs @@ -15,5 +15,10 @@ public interface IServiceProperty : IReadOnlyServiceProperty, IPropertyBase /// Gets the entity type that this property belongs to. /// new IEntityType DeclaringEntityType { get; } + + /// + /// The for this property. + /// + new ServiceParameterBinding ParameterBinding { get; } } } diff --git a/src/EFCore/Metadata/Internal/ConstructorBindingFactory.cs b/src/EFCore/Metadata/Internal/ConstructorBindingFactory.cs index 427a225d98c..02aa8ebd147 100644 --- a/src/EFCore/Metadata/Internal/ConstructorBindingFactory.cs +++ b/src/EFCore/Metadata/Internal/ConstructorBindingFactory.cs @@ -6,6 +6,8 @@ using System.Linq; using System.Reflection; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.Extensions.DependencyInjection; using CA = System.Diagnostics.CodeAnalysis; @@ -45,6 +47,164 @@ public ConstructorBindingFactory( _factories = factories; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void GetBindings( + IConventionEntityType entityType, + out InstantiationBinding constructorBinding, + out InstantiationBinding? serviceOnlyBinding) + => GetBindings( + entityType, + static (f, e, p, n) => f?.Bind((IConventionEntityType)e, p, n), + out constructorBinding, + out serviceOnlyBinding); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void GetBindings( + IMutableEntityType entityType, + out InstantiationBinding constructorBinding, + out InstantiationBinding? serviceOnlyBinding) + => GetBindings( + entityType, + static (f, e, p, n) => f?.Bind((IMutableEntityType)e, p, n), + out constructorBinding, + out serviceOnlyBinding); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void GetBindings( + IReadOnlyEntityType entityType, + out InstantiationBinding constructorBinding, + out InstantiationBinding? serviceOnlyBinding) + => GetBindings( + entityType, + static (f, e, p, n) => f?.Bind(e, p, n), + out constructorBinding, + out serviceOnlyBinding); + + private void GetBindings( + IReadOnlyEntityType entityType, + Func bind, + out InstantiationBinding constructorBinding, + out InstantiationBinding? serviceOnlyBinding) + { + var maxServiceParams = 0; + var maxServiceOnlyParams = 0; + var minPropertyParams = int.MaxValue; + var foundBindings = new List(); + var foundServiceOnlyBindings = new List(); + var bindingFailures = new List>(); + + foreach (var constructor in entityType.ClrType.GetTypeInfo() + .DeclaredConstructors + .Where(c => !c.IsStatic)) + { + // Trying to find the constructor with the most service properties + // followed by the least scalar property parameters + if (TryBindConstructor( + entityType, constructor, bind, out var binding, out var failures)) + { + var serviceParamCount = binding.ParameterBindings.OfType().Count(); + var propertyParamCount = binding.ParameterBindings.Count - serviceParamCount; + + if (propertyParamCount == 0) + { + if (serviceParamCount == maxServiceOnlyParams) + { + foundServiceOnlyBindings.Add(binding); + } + else if (serviceParamCount > maxServiceOnlyParams) + { + foundServiceOnlyBindings.Clear(); + foundServiceOnlyBindings.Add(binding); + + maxServiceOnlyParams = serviceParamCount; + } + } + + if (serviceParamCount == maxServiceParams + && propertyParamCount == minPropertyParams) + { + foundBindings.Add(binding); + } + else if (serviceParamCount > maxServiceParams) + { + foundBindings.Clear(); + foundBindings.Add(binding); + + maxServiceParams = serviceParamCount; + minPropertyParams = propertyParamCount; + } + else if (propertyParamCount < minPropertyParams) + { + foundBindings.Clear(); + foundBindings.Add(binding); + + maxServiceParams = serviceParamCount; + minPropertyParams = propertyParamCount; + } + } + else + { + bindingFailures.Add(failures); + } + } + + if (foundBindings.Count == 0) + { + var constructorErrors = bindingFailures.SelectMany(f => f) + .GroupBy(f => (ConstructorInfo)f.Member) + .Select( + x => CoreStrings.ConstructorBindingFailed( + string.Join("', '", x.Select(f => f.Name)), + entityType.DisplayName() + + "(" + + string.Join( + ", ", x.Key.GetParameters().Select( + y => y.ParameterType.ShortDisplayName() + " " + y.Name) + ) + + ")" + ) + ); + + throw new InvalidOperationException( + CoreStrings.ConstructorNotFound( + entityType.DisplayName(), + string.Join("; ", constructorErrors))); + } + + if (foundBindings.Count > 1) + { + throw new InvalidOperationException( + CoreStrings.ConstructorConflict( + FormatConstructorString(entityType, foundBindings[0]), + FormatConstructorString(entityType, foundBindings[1]))); + } + + constructorBinding = foundBindings[0]; + if (foundServiceOnlyBindings.Count == 1) + { + serviceOnlyBinding = foundServiceOnlyBindings[0]; + } + else + { + serviceOnlyBinding = 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 @@ -59,7 +219,7 @@ public virtual bool TryBindConstructor( => TryBindConstructor( entityType, constructor, - (f, e, p, n) => f?.Bind((IMutableEntityType)e, p, n), + static (f, e, p, n) => f?.Bind((IMutableEntityType)e, p, n), out binding, out unboundParameters); @@ -77,7 +237,7 @@ public virtual bool TryBindConstructor( => TryBindConstructor( entityType, constructor, - (f, e, p, n) => f?.Bind((IConventionEntityType)e, p, n), + static (f, e, p, n) => f?.Bind((IConventionEntityType)e, p, n), out binding, out unboundParameters); @@ -109,5 +269,11 @@ private bool TryBindConstructor( return true; } + + private static string FormatConstructorString(IReadOnlyEntityType entityType, InstantiationBinding binding) + => entityType.ClrType.ShortDisplayName() + + "(" + + string.Join(", ", binding.ParameterBindings.Select(b => b.ParameterType.ShortDisplayName())) + + ")"; } } diff --git a/src/EFCore/Metadata/Internal/ContextParameterBindingFactory.cs b/src/EFCore/Metadata/Internal/ContextParameterBindingFactory.cs index 9c87cbb2509..38686ceccd4 100644 --- a/src/EFCore/Metadata/Internal/ContextParameterBindingFactory.cs +++ b/src/EFCore/Metadata/Internal/ContextParameterBindingFactory.cs @@ -33,10 +33,11 @@ public virtual bool CanBind( /// 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 ParameterBinding Bind(IMutableEntityType entityType, Type parameterType, string parameterName) - => new ContextParameterBinding( - parameterType, - (IPropertyBase?)entityType.GetServiceProperties().FirstOrDefault(p => p.ClrType == parameterType)); + public virtual ParameterBinding Bind( + IMutableEntityType entityType, + Type parameterType, + string parameterName) + => Bind((IReadOnlyEntityType)entityType, parameterType, parameterName); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -48,6 +49,18 @@ public virtual ParameterBinding Bind( IConventionEntityType entityType, Type parameterType, string parameterName) + => Bind((IReadOnlyEntityType)entityType, parameterType, parameterName); + + /// + /// 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 ParameterBinding Bind( + IReadOnlyEntityType entityType, + Type parameterType, + string parameterName) => new ContextParameterBinding( parameterType, (IPropertyBase?)entityType.GetServiceProperties().FirstOrDefault(p => p.ClrType == parameterType)); diff --git a/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs b/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs index df492680914..3423dba4adb 100644 --- a/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs +++ b/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs @@ -112,22 +112,6 @@ public static class CoreAnnotationNames /// public const string DiscriminatorValue = "DiscriminatorValue"; - /// - /// 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 ConstructorBinding = "ConstructorBinding"; - - /// - /// 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 ServiceOnlyConstructorBinding = "ServiceOnlyConstructorBinding"; - /// /// 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 @@ -295,8 +279,6 @@ public static class CoreAnnotationNames DiscriminatorProperty, DiscriminatorMappingComplete, DiscriminatorValue, - ConstructorBinding, - ServiceOnlyConstructorBinding, ValueConverter, ValueComparer, #pragma warning disable 618 diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index 474d697f744..0055ca36b8d 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -61,11 +61,15 @@ private readonly SortedDictionary _serviceProperties private EntityType? _baseType; private ChangeTrackingStrategy? _changeTrackingStrategy; private InternalEntityTypeBuilder? _builder; + private InstantiationBinding? _constructorBinding; + private InstantiationBinding? _serviceOnlyConstructorBinding; private ConfigurationSource? _primaryKeyConfigurationSource; private ConfigurationSource? _isKeylessConfigurationSource; private ConfigurationSource? _baseTypeConfigurationSource; private ConfigurationSource? _changeTrackingStrategyConfigurationSource; + private ConfigurationSource? _constructorBindingConfigurationSource; + private ConfigurationSource? _serviceOnlyConstructorBindingConfigurationSource; // Warning: Never access these fields directly as access needs to be thread-safe private PropertyCounts? _counts; @@ -247,13 +251,7 @@ public virtual bool IsKeyless public virtual ConfigurationSource? GetIsKeylessConfigurationSource() => _isKeylessConfigurationSource; - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual void UpdateIsKeylessConfigurationSource(ConfigurationSource configurationSource) + private void UpdateIsKeylessConfigurationSource(ConfigurationSource configurationSource) => _isKeylessConfigurationSource = configurationSource.Max(_isKeylessConfigurationSource); /// @@ -2690,10 +2688,15 @@ public virtual Func InstanceFactory { entityType.EnsureReadOnly(); - var binding = (InstantiationBinding?)entityType[CoreAnnotationNames.ServiceOnlyConstructorBinding]; + var binding = entityType.ServiceOnlyConstructorBinding; if (binding == null) { - throw new InvalidOperationException(CoreStrings.NoParameterlessConstructor(entityType.DisplayName())); + var _ = entityType.ConstructorBinding; + binding = entityType.ServiceOnlyConstructorBinding; + if (binding == null) + { + throw new InvalidOperationException(CoreStrings.NoParameterlessConstructor(entityType.DisplayName())); + } } var contextParam = Expression.Parameter(typeof(MaterializationContext), "mc"); @@ -3314,6 +3317,117 @@ public virtual bool IsImplicitlyCreatedJoinEntityType => GetConfigurationSource() == ConfigurationSource.Convention && ClrType == Model.DefaultPropertyBagType; + /// + /// 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 InstantiationBinding? ConstructorBinding + { + get => IsReadOnly && !ClrType.IsAbstract + ? NonCapturingLazyInitializer.EnsureInitialized(ref _constructorBinding, this, static entityType => + { + ((IModel)entityType.Model).GetModelDependencies().ConstructorBindingFactory.GetBindings( + (IReadOnlyEntityType)entityType, + out entityType._constructorBinding, + out entityType._serviceOnlyConstructorBinding); + }) + : _constructorBinding; + + [param: CanBeNull] + set => SetConstructorBinding(value, ConfigurationSource.Explicit); + } + + /// + /// 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 InstantiationBinding? SetConstructorBinding( + [CanBeNull] InstantiationBinding? constructorBinding, + ConfigurationSource configurationSource) + { + EnsureMutable(); + + _constructorBinding = constructorBinding; + + if (_constructorBinding == null) + { + _constructorBindingConfigurationSource = null; + } + else + { + UpdateConstructorBindingConfigurationSource(configurationSource); + } + + return constructorBinding; + } + + /// + /// 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 ConfigurationSource? GetConstructorBindingConfigurationSource() + => _constructorBindingConfigurationSource; + + private void UpdateConstructorBindingConfigurationSource(ConfigurationSource configurationSource) + => _constructorBindingConfigurationSource = configurationSource.Max(_constructorBindingConfigurationSource); + + /// + /// 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 InstantiationBinding? ServiceOnlyConstructorBinding + { + get => _serviceOnlyConstructorBinding; + [param: CanBeNull] + set => SetServiceOnlyConstructorBinding(value, ConfigurationSource.Explicit); + } + + /// + /// 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 InstantiationBinding? SetServiceOnlyConstructorBinding( + [CanBeNull] InstantiationBinding? constructorBinding, + ConfigurationSource configurationSource) + { + EnsureMutable(); + + _serviceOnlyConstructorBinding = constructorBinding; + + if (_serviceOnlyConstructorBinding == null) + { + _serviceOnlyConstructorBindingConfigurationSource = null; + } + else + { + UpdateServiceOnlyConstructorBindingConfigurationSource(configurationSource); + } + + return constructorBinding; + } + + /// + /// 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 ConfigurationSource? GetServiceOnlyConstructorBindingConfigurationSource() + => _serviceOnlyConstructorBindingConfigurationSource; + + private void UpdateServiceOnlyConstructorBindingConfigurationSource(ConfigurationSource configurationSource) + => _serviceOnlyConstructorBindingConfigurationSource = configurationSource.Max(_serviceOnlyConstructorBindingConfigurationSource); + #endregion #region Explicit interface implementations diff --git a/src/EFCore/Metadata/Internal/EntityTypeParameterBindingFactory.cs b/src/EFCore/Metadata/Internal/EntityTypeParameterBindingFactory.cs index 10d65369dbb..7fc168ba874 100644 --- a/src/EFCore/Metadata/Internal/EntityTypeParameterBindingFactory.cs +++ b/src/EFCore/Metadata/Internal/EntityTypeParameterBindingFactory.cs @@ -42,9 +42,11 @@ public virtual bool CanBind( /// 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 ParameterBinding Bind(IMutableEntityType entityType, Type parameterType, string parameterName) - => new EntityTypeParameterBinding( - (IPropertyBase?)entityType.GetServiceProperties().FirstOrDefault(p => p.ClrType == parameterType)); + public virtual ParameterBinding Bind( + IMutableEntityType entityType, + Type parameterType, + string parameterName) + => Bind((IReadOnlyEntityType)entityType, parameterType, parameterName); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -56,6 +58,18 @@ public virtual ParameterBinding Bind( IConventionEntityType entityType, Type parameterType, string parameterName) + => Bind((IReadOnlyEntityType)entityType, parameterType, parameterName); + + /// + /// 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 ParameterBinding Bind( + IReadOnlyEntityType entityType, + Type parameterType, + string parameterName) => new EntityTypeParameterBinding( (IPropertyBase?)entityType.GetServiceProperties().FirstOrDefault(p => p.ClrType == parameterType)); } diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index 737f6a58531..49c6ed06bff 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -4262,6 +4262,64 @@ public virtual bool CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessM => configurationSource.Overrides(Metadata.GetPropertyAccessModeConfigurationSource()) || Metadata.GetPropertyAccessMode() == propertyAccessMode; + /// + /// 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 IConventionEntityTypeBuilder? HasConstructorBinding( + [CanBeNull] InstantiationBinding? constructorBinding, ConfigurationSource configurationSource) + { + if (CanSetConstructorBinding(constructorBinding, configurationSource)) + { + Metadata.SetConstructorBinding(constructorBinding, configurationSource); + + return this; + } + + return 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 + /// 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 bool CanSetConstructorBinding([CanBeNull] InstantiationBinding? constructorBinding, ConfigurationSource configurationSource) + => configurationSource.Overrides(Metadata.GetConstructorBindingConfigurationSource()) + || Metadata.ConstructorBinding == constructorBinding; + + /// + /// 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 IConventionEntityTypeBuilder? HasServiceOnlyConstructorBinding( + [CanBeNull] InstantiationBinding? constructorBinding, ConfigurationSource configurationSource) + { + if (CanSetServiceOnlyConstructorBinding(constructorBinding, configurationSource)) + { + Metadata.SetServiceOnlyConstructorBinding(constructorBinding, configurationSource); + + return this; + } + + return 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 + /// 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 bool CanSetServiceOnlyConstructorBinding([CanBeNull] InstantiationBinding? constructorBinding, ConfigurationSource configurationSource) + => configurationSource.Overrides(Metadata.GetServiceOnlyConstructorBindingConfigurationSource()) + || Metadata.ServiceOnlyConstructorBinding == constructorBinding; + /// /// 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/Model.cs b/src/EFCore/Metadata/Internal/Model.cs index 2a73a1cb1fd..765773a24e0 100644 --- a/src/EFCore/Metadata/Internal/Model.cs +++ b/src/EFCore/Metadata/Internal/Model.cs @@ -53,6 +53,7 @@ public class Model : ConventionAnnotatable, IMutableModel, IConventionModel, IMo new() { { DefaultPropertyBagType, (ConfigurationSource.Convention, new SortedSet(EntityTypeFullNameComparer.Instance)) } }; private ConventionDispatcher? _conventionDispatcher; + private IList? _modelFinalizedConventions; private bool? _skipDetectChanges; private ChangeTrackingStrategy? _changeTrackingStrategy; @@ -85,6 +86,7 @@ public Model([NotNull] ConventionSet conventions, [CanBeNull] ModelDependencies? var dispatcher = new ConventionDispatcher(conventions); var builder = new InternalModelBuilder(this); _conventionDispatcher = dispatcher; + _modelFinalizedConventions = conventions.ModelFinalizedConventions; Builder = builder; dispatcher.OnModelInitialized(builder); } @@ -837,8 +839,6 @@ public virtual IModel FinalizeModel() var finalizedModel = (IModel)ConventionDispatcher.OnModelFinalizing(Builder).Metadata; - finalizedModel = ConventionDispatcher.OnModelFinalized(finalizedModel); - if (finalizedModel is Model model) { finalizedModel = model.MakeReadonly(); @@ -847,6 +847,25 @@ public virtual IModel FinalizeModel() return finalizedModel; } + /// + /// 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 IModel OnModelFinalized() + { + IModel model = this; + foreach (var modelConvention in _modelFinalizedConventions!) + { + model = modelConvention.ProcessModelFinalized(model); + } + + _modelFinalizedConventions = null; + + return model; + } + /// /// 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/Property.cs b/src/EFCore/Metadata/Internal/Property.cs index 4e62de3c61e..f847af88344 100644 --- a/src/EFCore/Metadata/Internal/Property.cs +++ b/src/EFCore/Metadata/Internal/Property.cs @@ -10,6 +10,7 @@ using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -506,9 +507,9 @@ private static bool DefaultIsConcurrencyToken [CA.DisallowNull] public virtual CoreTypeMapping? TypeMapping { - get - => _typeMapping == null && IsReadOnly - ? _typeMapping ??= ((IModel)DeclaringEntityType.Model).ModelDependencies?.TypeMappingSource.FindMapping(this) + get => IsReadOnly + ? NonCapturingLazyInitializer.EnsureInitialized(ref _typeMapping, (IProperty)this, static property => + property.DeclaringEntityType.Model.GetModelDependencies().TypeMappingSource.FindMapping(property)!) : _typeMapping; [param: NotNull] diff --git a/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs b/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs index 9d4ab1db4b8..f992f5e4b4a 100644 --- a/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs +++ b/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs @@ -340,8 +340,7 @@ public static bool TryGetMemberInfo( private static string GetNoFieldErrorMessage(IPropertyBase propertyBase) { - var constructorBinding = (InstantiationBinding?)propertyBase.DeclaringType[CoreAnnotationNames.ConstructorBinding]; - + var constructorBinding = ((EntityType)propertyBase.DeclaringType).ConstructorBinding; return constructorBinding?.ParameterBindings .OfType() .Any(b => b.ServiceType == typeof(ILazyLoader)) diff --git a/src/EFCore/Metadata/Internal/ServiceProperty.cs b/src/EFCore/Metadata/Internal/ServiceProperty.cs index 5bf94f53f91..8dba52349f7 100644 --- a/src/EFCore/Metadata/Internal/ServiceProperty.cs +++ b/src/EFCore/Metadata/Internal/ServiceProperty.cs @@ -7,6 +7,7 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Utilities; @@ -114,7 +115,16 @@ public virtual void SetRemovedFromModel() /// public virtual ServiceParameterBinding? ParameterBinding { - get => _parameterBinding; +#pragma warning disable CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). + get => IsReadOnly + ? NonCapturingLazyInitializer.EnsureInitialized(ref _parameterBinding, (IServiceProperty)this, static property => + { + var entityType = property.DeclaringEntityType; + var factory = entityType.Model.GetModelDependencies().ParameterBindingFactories.FindFactory(property.ClrType, property.Name)!; + return (ServiceParameterBinding)factory.Bind(entityType, property.ClrType, property.Name); + }) + : _parameterBinding; +#pragma warning restore CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). set => SetParameterBinding(value, ConfigurationSource.Explicit); } diff --git a/src/EFCore/Metadata/Internal/ServicePropertyExtensions.cs b/src/EFCore/Metadata/Internal/ServicePropertyExtensions.cs index 214a77ea521..23beb340456 100644 --- a/src/EFCore/Metadata/Internal/ServicePropertyExtensions.cs +++ b/src/EFCore/Metadata/Internal/ServicePropertyExtensions.cs @@ -16,15 +16,6 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal /// public static class ServicePropertyExtensions { - /// - /// 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 ServiceParameterBinding? GetParameterBinding([NotNull] this IServiceProperty serviceProperty) - => serviceProperty.AsServiceProperty().ParameterBinding; - /// /// 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/LazyLoaderParameterBindingFactory.cs b/src/EFCore/Metadata/LazyLoaderParameterBindingFactory.cs index 4f4034c5651..bd267dd8b05 100644 --- a/src/EFCore/Metadata/LazyLoaderParameterBindingFactory.cs +++ b/src/EFCore/Metadata/LazyLoaderParameterBindingFactory.cs @@ -113,6 +113,25 @@ public override ParameterBinding Bind( return Bind((IEntityType)entityType, parameterType); } + /// + /// Creates a for the given type and name on the given entity type. + /// + /// The entity type. + /// The parameter type. + /// The parameter name. + /// The binding. + public override ParameterBinding Bind( + IReadOnlyEntityType entityType, + Type parameterType, + string parameterName) + { + Check.NotNull(entityType, nameof(entityType)); + Check.NotNull(parameterType, nameof(parameterType)); + Check.NotEmpty(parameterName, nameof(parameterName)); + + return Bind((IEntityType)entityType, parameterType); + } + private static ParameterBinding Bind(IEntityType entityType, Type parameterType) => parameterType == typeof(ILazyLoader) ? new DependencyInjectionParameterBinding( diff --git a/src/EFCore/Metadata/ServiceParameterBindingFactory.cs b/src/EFCore/Metadata/ServiceParameterBindingFactory.cs index f3957eb7dc5..e912902852a 100644 --- a/src/EFCore/Metadata/ServiceParameterBindingFactory.cs +++ b/src/EFCore/Metadata/ServiceParameterBindingFactory.cs @@ -55,17 +55,11 @@ public virtual bool CanBind( /// The parameter type. /// The parameter name. /// The binding. - public virtual ParameterBinding Bind(IMutableEntityType entityType, Type parameterType, string parameterName) - { - Check.NotNull(entityType, nameof(entityType)); - Check.NotNull(parameterType, nameof(parameterType)); - Check.NotEmpty(parameterName, nameof(parameterName)); - - return new DependencyInjectionParameterBinding( - _serviceType, - _serviceType, - (IPropertyBase?)entityType.GetServiceProperties().FirstOrDefault(p => p.ClrType == _serviceType)); - } + public virtual ParameterBinding Bind( + IMutableEntityType entityType, + Type parameterType, + string parameterName) + => Bind((IReadOnlyEntityType)entityType, parameterType, parameterName); /// /// Creates a for the given type and name on the given entity type. @@ -78,6 +72,19 @@ public virtual ParameterBinding Bind( IConventionEntityType entityType, Type parameterType, string parameterName) + => Bind((IReadOnlyEntityType)entityType, parameterType, parameterName); + + /// + /// Creates a for the given type and name on the given entity type. + /// + /// The entity type. + /// The parameter type. + /// The parameter name. + /// The binding. + public virtual ParameterBinding Bind( + IReadOnlyEntityType entityType, + Type parameterType, + string parameterName) { Check.NotNull(entityType, nameof(entityType)); Check.NotNull(parameterType, nameof(parameterType)); diff --git a/src/EFCore/Query/Internal/EntityMaterializerSource.cs b/src/EFCore/Query/Internal/EntityMaterializerSource.cs index 1d7edf2a337..ce9753fabef 100644 --- a/src/EFCore/Query/Internal/EntityMaterializerSource.cs +++ b/src/EFCore/Query/Internal/EntityMaterializerSource.cs @@ -64,19 +64,7 @@ public virtual Expression CreateMaterializeExpression( throw new InvalidOperationException(CoreStrings.CannotMaterializeAbstractType(entityType.DisplayName())); } - var constructorBinding = (InstantiationBinding?)entityType[CoreAnnotationNames.ConstructorBinding]; - - if (constructorBinding == null) - { - var constructorInfo = entityType.ClrType.GetDeclaredConstructor(null); - - if (constructorInfo == null) - { - throw new InvalidOperationException(CoreStrings.NoParameterlessConstructor(entityType.DisplayName())); - } - - constructorBinding = new ConstructorBinding(constructorInfo, Array.Empty()); - } + var constructorBinding = entityType.ConstructorBinding!; var bindingInfo = new ParameterBindingInfo( entityType, @@ -121,7 +109,7 @@ var blockExpressions var readValueExpression = property is IServiceProperty serviceProperty - ? serviceProperty.GetParameterBinding()!.BindToParameter(bindingInfo) + ? serviceProperty.ParameterBinding.BindToParameter(bindingInfo) : valueBufferExpression.CreateValueBufferReadValueExpression( memberInfo.GetMemberType(), property.GetIndex(), diff --git a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs index 9d5fad851e6..5089d465fa1 100644 --- a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs +++ b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs @@ -333,6 +333,9 @@ public bool HasSharedClrType public bool IsPropertyBag => throw new NotImplementedException(); + public InstantiationBinding ConstructorBinding + => throw new NotImplementedException(); + IReadOnlyEntityType IReadOnlyEntityType.BaseType => throw new NotImplementedException(); diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index 301bb31e568..3d3867c8cac 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -160,8 +160,6 @@ public void Test_new_annotations_handled_for_properties() { CoreAnnotationNames.ProductVersion, CoreAnnotationNames.OwnedTypes, - CoreAnnotationNames.ConstructorBinding, - CoreAnnotationNames.ServiceOnlyConstructorBinding, CoreAnnotationNames.NavigationAccessMode, CoreAnnotationNames.EagerLoaded, CoreAnnotationNames.QueryFilter, @@ -294,7 +292,7 @@ private static void MissingAnnotationCheck( ? validAnnotations[annotationName].Value : null); - modelBuilder.FinalizeModel(); + SqlServerTestHelpers.Instance.Finalize(modelBuilder); var sb = new IndentedStringBuilder(); @@ -389,7 +387,7 @@ public void Snapshot_with_enum_discriminator_uses_converted_values() eb.Property("EnumDiscriminator").HasConversion(); }); - var finalizedModel = modelBuilder.FinalizeModel(); + var finalizedModel = SqlServerTestHelpers.Instance.Finalize(modelBuilder); var modelSnapshotCode = generator.GenerateSnapshot( "MyNamespace", @@ -509,7 +507,7 @@ protected override void Down(MigrationBuilder migrationBuilder) var modelBuilder = new ModelBuilder(); modelBuilder.HasAnnotation("Some:EnumValue", RegexOptions.Multiline); - modelBuilder.HasAnnotation(RelationalAnnotationNames.DbFunctions, new object()); + modelBuilder.HasAnnotation(RelationalAnnotationNames.DbFunctions, new SortedDictionary()); modelBuilder.Entity( "T1", eb => { @@ -519,7 +517,7 @@ protected override void Down(MigrationBuilder migrationBuilder) eb.HasKey("Id"); }); - var finalizedModel = modelBuilder.FinalizeModel(); + var finalizedModel = SqlServerTestHelpers.Instance.Finalize(modelBuilder); var migrationMetadataCode = generator.GenerateMetadata( "MyNamespace", @@ -651,7 +649,7 @@ public void Snapshots_compile() entityType.SetPrimaryKey(property2); - var finalizedModel = modelBuilder.FinalizeModel(); + var finalizedModel = SqlServerTestHelpers.Instance.Finalize(modelBuilder); var modelSnapshotCode = generator.GenerateSnapshot( "MyNamespace", @@ -767,7 +765,7 @@ public void Snapshot_with_default_values_are_round_tripped() eb.HasKey(e => e.Boolean); }); - var finalizedModel = modelBuilder.FinalizeModel(); + var finalizedModel = SqlServerTestHelpers.Instance.Finalize(modelBuilder); var modelSnapshotCode = generator.GenerateSnapshot( "MyNamespace", diff --git a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs index 72bc33a7732..7b143d62d2b 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs @@ -6,14 +6,11 @@ using System.Linq; using System.Reflection; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; 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; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.DependencyInjection; diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalDatabaseModelFactoryTest.cs similarity index 96% rename from test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs rename to test/EFCore.Design.Tests/Scaffolding/Internal/RelationalDatabaseModelFactoryTest.cs index 6ebfb7126f1..98305092296 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalDatabaseModelFactoryTest.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -12,7 +11,6 @@ using Microsoft.EntityFrameworkCore.Scaffolding.Internal; using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; using Microsoft.EntityFrameworkCore.Scaffolding.Metadata.Internal; -using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -51,14 +49,14 @@ public RelationalDatabaseModelFactoryTest() { _reporter = new TestOperationReporter(); - var services = new ServiceCollection() - .AddEntityFrameworkDesignTimeServices(_reporter) - .AddSingleton(); - new SqlServerDesignTimeServices().ConfigureDesignTimeServices(services); - - _factory = services + var assembly = typeof(RelationalDatabaseModelFactoryTest).Assembly; + _factory = new DesignTimeServicesBuilder(assembly, assembly, _reporter, new string[0]) + .CreateServiceCollection(SqlServerTestHelpers.Instance.CreateContext()) + .AddSingleton() .BuildServiceProvider() .GetRequiredService(); + + _reporter.Clear(); } [ConditionalFact] @@ -374,7 +372,7 @@ public void Column_type_annotation(string storeType, string expectedColumnType) var property = (Property)_factory.Create(info, new ModelReverseEngineerOptions()).FindEntityType("A").FindProperty("Col"); - Assert.Equal(expectedColumnType, property.GetColumnType()); + Assert.Equal(expectedColumnType, property.GetConfiguredColumnType()); } [ConditionalFact] @@ -1541,17 +1539,7 @@ public void Pluralization_of_entity_and_DbSet() } }; - var services = new ServiceCollection() - .AddEntityFrameworkDesignTimeServices(_reporter) - .AddSingleton() - .AddSingleton(); - new SqlServerDesignTimeServices().ConfigureDesignTimeServices(services); - - var factory = services - .BuildServiceProvider() - .GetRequiredService(); - - var model = factory.Create(info, new ModelReverseEngineerOptions()); + var model = _factory.Create(info, new ModelReverseEngineerOptions()); Assert.Collection( model.GetEntityTypes().OrderBy(t => t.Name).Cast(), @@ -1594,17 +1582,7 @@ public void Pluralization_of_entity_and_DbSet_noPluralize() } }; - var services = new ServiceCollection() - .AddEntityFrameworkDesignTimeServices(_reporter) - .AddSingleton() - .AddSingleton(); - new SqlServerDesignTimeServices().ConfigureDesignTimeServices(services); - - var factory = services - .BuildServiceProvider() - .GetRequiredService(); - - var model = factory.Create(info, new ModelReverseEngineerOptions { NoPluralize = true }); + var model = _factory.Create(info, new ModelReverseEngineerOptions { NoPluralize = true }); Assert.Collection( model.GetEntityTypes().OrderBy(t => t.Name).Cast(), @@ -1622,7 +1600,7 @@ public void Pluralization_of_entity_and_DbSet_noPluralize() } ); - model = factory.Create(info, new ModelReverseEngineerOptions { UseDatabaseNames = true, NoPluralize = true }); + model = _factory.Create(info, new ModelReverseEngineerOptions { UseDatabaseNames = true, NoPluralize = true }); Assert.Collection( model.GetEntityTypes().OrderBy(t => t.Name).Cast(), @@ -1682,17 +1660,7 @@ public void Pluralization_of_collection_navigations() var info = new DatabaseModel { Tables = { blogTable, postTable } }; - var services = new ServiceCollection() - .AddEntityFrameworkDesignTimeServices(_reporter) - .AddSingleton() - .AddSingleton(); - new SqlServerDesignTimeServices().ConfigureDesignTimeServices(services); - - var factory = services - .BuildServiceProvider() - .GetRequiredService(); - - var model = factory.Create(info, new ModelReverseEngineerOptions()); + var model = _factory.Create(info, new ModelReverseEngineerOptions()); Assert.Collection( model.GetEntityTypes().OrderBy(t => t.Name).Cast(), @@ -1750,17 +1718,7 @@ public void Pluralization_of_collection_navigations_noPluralize() var info = new DatabaseModel { Tables = { blogTable, postTable } }; - var services = new ServiceCollection() - .AddEntityFrameworkDesignTimeServices(_reporter) - .AddSingleton() - .AddSingleton(); - new SqlServerDesignTimeServices().ConfigureDesignTimeServices(services); - - var factory = services - .BuildServiceProvider() - .GetRequiredService(); - - var model = factory.Create(info, new ModelReverseEngineerOptions { NoPluralize = true }); + var model = _factory.Create(info, new ModelReverseEngineerOptions { NoPluralize = true }); Assert.Collection( model.GetEntityTypes().OrderBy(t => t.Name).Cast(), @@ -1979,11 +1937,11 @@ public void Correct_arguments_to_scaffolding_typemapper() var model = _factory.Create(dbModel, new ModelReverseEngineerOptions()); - Assert.Null(model.FindEntityType("Principal").FindProperty("PrimaryKey").GetColumnType()); - Assert.Null(model.FindEntityType("Principal").FindProperty("AlternateKey").GetColumnType()); - Assert.Null(model.FindEntityType("Principal").FindProperty("Index").GetColumnType()); - Assert.Null(model.FindEntityType("Principal").FindProperty("Rowversion").GetColumnType()); - Assert.Null(model.FindEntityType("Dependent").FindProperty("BlogAlternateKey").GetColumnType()); + Assert.Null(model.FindEntityType("Principal").FindProperty("PrimaryKey").GetConfiguredColumnType()); + Assert.Null(model.FindEntityType("Principal").FindProperty("AlternateKey").GetConfiguredColumnType()); + Assert.Null(model.FindEntityType("Principal").FindProperty("Index").GetConfiguredColumnType()); + Assert.Null(model.FindEntityType("Principal").FindProperty("Rowversion").GetConfiguredColumnType()); + Assert.Null(model.FindEntityType("Dependent").FindProperty("BlogAlternateKey").GetConfiguredColumnType()); } [ConditionalFact] diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/ReverseEngineerScaffolderTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/ReverseEngineerScaffolderTest.cs index 73d04cde77b..6c8864db950 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/ReverseEngineerScaffolderTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/ReverseEngineerScaffolderTest.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.IO; using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -128,14 +129,12 @@ public void Save_throws_when_readonly_files() } private static IReverseEngineerScaffolder CreateScaffolder() - => new ServiceCollection() - .AddEntityFrameworkDesignTimeServices() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() + => new DesignTimeServicesBuilder( + typeof(ReverseEngineerScaffolderTest).Assembly, + typeof(ReverseEngineerScaffolderTest).Assembly, + new TestOperationReporter(), + new string[0]) + .CreateServiceCollection(SqlServerTestHelpers.Instance.CreateContext()) .BuildServiceProvider() .GetRequiredService(); @@ -144,14 +143,14 @@ public void ScaffoldModel_works_with_named_connection_string() { var resolver = new TestNamedConnectionStringResolver("Data Source=Test"); var databaseModelFactory = new TestDatabaseModelFactory(); - var scaffolder = new ServiceCollection() - .AddEntityFrameworkDesignTimeServices() + var scaffolder = new DesignTimeServicesBuilder( + typeof(ReverseEngineerScaffolderTest).Assembly, + typeof(ReverseEngineerScaffolderTest).Assembly, + new TestOperationReporter(), + new string[0]) + .CreateServiceCollection(SqlServerTestHelpers.Instance.CreateContext()) .AddSingleton(resolver) .AddSingleton(databaseModelFactory) - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() .BuildServiceProvider() .GetRequiredService(); @@ -174,14 +173,14 @@ public void ScaffoldModel_works_with_overridden_connection_string() var resolver = new TestNamedConnectionStringResolver("Data Source=Test"); var databaseModelFactory = new TestDatabaseModelFactory(); databaseModelFactory.ScaffoldedConnectionString = "Data Source=ScaffoldedConnectionString"; - var scaffolder = new ServiceCollection() - .AddEntityFrameworkDesignTimeServices() + var scaffolder = new DesignTimeServicesBuilder( + typeof(ReverseEngineerScaffolderTest).Assembly, + typeof(ReverseEngineerScaffolderTest).Assembly, + new TestOperationReporter(), + new string[0]) + .CreateServiceCollection(SqlServerTestHelpers.Instance.CreateContext()) .AddSingleton(resolver) .AddSingleton(databaseModelFactory) - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() .BuildServiceProvider() .GetRequiredService(); diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/ReverseEngineeringConfigurationTests.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/ReverseEngineeringConfigurationTests.cs index 67d18e5453f..288bf030ff9 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/ReverseEngineeringConfigurationTests.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/ReverseEngineeringConfigurationTests.cs @@ -3,6 +3,7 @@ using System; using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Storage; @@ -24,15 +25,9 @@ public void Throws_exceptions_for_invalid_context_name() private void ValidateContextNameInReverseEngineerGenerator(string contextName) { - var reverseEngineer = new ServiceCollection() - .AddEntityFrameworkDesignTimeServices() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .BuildServiceProvider() + var assembly = typeof(ReverseEngineeringConfigurationTests).Assembly; + var reverseEngineer = new DesignTimeServicesBuilder(assembly, assembly, new TestOperationReporter(), new string[0]) + .Build(SqlServerTestHelpers.Instance.CreateContext()) .GetRequiredService(); Assert.Equal( diff --git a/test/EFCore.Design.Tests/TestUtilities/FakeScaffoldingModelFactory.cs b/test/EFCore.Design.Tests/TestUtilities/FakeScaffoldingModelFactory.cs index 7e8e9136405..a213a7ca5ab 100644 --- a/test/EFCore.Design.Tests/TestUtilities/FakeScaffoldingModelFactory.cs +++ b/test/EFCore.Design.Tests/TestUtilities/FakeScaffoldingModelFactory.cs @@ -6,6 +6,7 @@ using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Scaffolding; using Microsoft.EntityFrameworkCore.Scaffolding.Internal; @@ -21,8 +22,9 @@ public FakeScaffoldingModelFactory( IPluralizer pluralizer, ICSharpUtilities cSharpUtilities, IScaffoldingTypeMapper scaffoldingTypeMapper, - LoggingDefinitions loggingDefinitions) - : base(reporter, candidateNamingService, pluralizer, cSharpUtilities, scaffoldingTypeMapper, loggingDefinitions) + LoggingDefinitions loggingDefinitions, + IModelRuntimeInitializer modelRuntimeInitializer) + : base(reporter, candidateNamingService, pluralizer, cSharpUtilities, scaffoldingTypeMapper, loggingDefinitions, modelRuntimeInitializer) { } diff --git a/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs b/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs index edc09e93a78..86cc1c7b89b 100644 --- a/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs +++ b/test/EFCore.Relational.Tests/Metadata/DbFunctionMetadataTests.cs @@ -874,7 +874,6 @@ private ModelBuilder GetModelBuilder(DbContext dbContext = null) 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)); return new ModelBuilder(conventionSet); diff --git a/test/EFCore.Tests/DbContextServicesTest.cs b/test/EFCore.Tests/DbContextServicesTest.cs index 4e718e5eb5e..5090b5fb77a 100644 --- a/test/EFCore.Tests/DbContextServicesTest.cs +++ b/test/EFCore.Tests/DbContextServicesTest.cs @@ -2709,6 +2709,9 @@ public ParameterBinding Bind(IMutableEntityType entityType, Type parameterType, public ParameterBinding Bind(IConventionEntityType entityType, Type parameterType, string parameterName) => throw new NotImplementedException(); + + public ParameterBinding Bind([NotNull] IReadOnlyEntityType entityType, [NotNull] Type parameterType, [NotNull] string parameterName) + => throw new NotImplementedException(); } private class CustomParameterBindingFactory2 : IParameterBindingFactory @@ -2721,6 +2724,9 @@ public ParameterBinding Bind(IMutableEntityType entityType, Type parameterType, public ParameterBinding Bind(IConventionEntityType entityType, Type parameterType, string parameterName) => throw new NotImplementedException(); + + public ParameterBinding Bind([NotNull] IReadOnlyEntityType entityType, [NotNull] Type parameterType, [NotNull] string parameterName) + => throw new NotImplementedException(); } private class CustomModelCustomizer : ModelCustomizer diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs index bb79b430a3e..860d8806ec0 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs @@ -111,18 +111,18 @@ public virtual void Ignores_custom_converter_for_collection_type_with_comparer() private IMutableProperty CreateConvertedCollectionProperty() { - var model = CreateConventionlessModelBuilder().Model; - - var entityType = model.AddEntityType(typeof(WithCollectionConversion)); - entityType.SetPrimaryKey(entityType.AddProperty(nameof(WithCollectionConversion.Id), typeof(int))); - - var convertedProperty = entityType.AddProperty( - nameof(WithCollectionConversion.SomeStrings), typeof(string[])); + var modelBuilder = CreateConventionalModelBuilder(); - convertedProperty.SetValueConverter( + IMutableProperty convertedProperty = null; + modelBuilder.Entity(eb => + { + eb.Property(e => e.Id); + convertedProperty = eb.Property(e => e.SomeStrings).Metadata; + convertedProperty.SetValueConverter( new ValueConverter( v => string.Join(',', v), v => v.Split(',', StringSplitOptions.None))); + }); return convertedProperty; } diff --git a/test/EFCore.Tests/Metadata/Conventions/ConstructorBindingConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/ConstructorBindingConventionTest.cs index 9bd0fa4e0fe..3e64399978a 100644 --- a/test/EFCore.Tests/Metadata/Conventions/ConstructorBindingConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/ConstructorBindingConventionTest.cs @@ -387,7 +387,7 @@ public void Throws_if_two_constructors_with_same_number_of_parameters_could_be_u public void Does_not_throw_if_explicit_binding_has_been_set() { var constructorBinding = GetBinding( - e => e[CoreAnnotationNames.ConstructorBinding] = new ConstructorBinding( + e => ((EntityType)e).ConstructorBinding = new ConstructorBinding( typeof(BlogConflict).GetConstructor( new[] { typeof(string), typeof(int) }), new[] @@ -796,7 +796,7 @@ private ConstructorBinding GetBinding(Action setBin var convention = new ConstructorBindingConvention(CreateDependencies()); convention.ProcessModelFinalizing(model.Builder, context); - return (ConstructorBinding)entityType[CoreAnnotationNames.ConstructorBinding]; + return (ConstructorBinding)((EntityType)entityType).ConstructorBinding; } private ProviderConventionSetBuilderDependencies CreateDependencies() diff --git a/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs b/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs index 4c438ce2d99..ea3bb86d973 100644 --- a/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs @@ -61,7 +61,7 @@ public void Delegate_getter_is_returned_for_IProperty_property() { var modelBuilder = CreateModelBuilder(); var idProperty = modelBuilder.Entity().Property(e => e.Id).Metadata; - modelBuilder.FinalizeModel(); + InMemoryTestHelpers.Instance.Finalize(modelBuilder); Assert.Equal( 7, new ClrPropertyGetterFactory().Create((IPropertyBase)idProperty).GetClrValue( @@ -82,7 +82,7 @@ public void Delegate_getter_is_returned_for_IProperty_struct_property() var modelBuilder = CreateModelBuilder(); modelBuilder.Entity().Property(e => e.Id); var fuelProperty = modelBuilder.Entity().Property(e => e.Fuel).Metadata; - modelBuilder.FinalizeModel(); + InMemoryTestHelpers.Instance.Finalize(modelBuilder); Assert.Equal( new Fuel(1.0), @@ -106,7 +106,7 @@ public void Delegate_getter_is_returned_for_index_property() modelBuilder.Entity().Property(e => e.Id); var propertyA = modelBuilder.Entity().Metadata.AddIndexerProperty("PropertyA", typeof(string)); var propertyB = modelBuilder.Entity().Metadata.AddIndexerProperty("PropertyB", typeof(int)); - modelBuilder.FinalizeModel(); + InMemoryTestHelpers.Instance.Finalize(modelBuilder); Assert.Equal("ValueA", new ClrPropertyGetterFactory().Create((IPropertyBase)propertyA).GetClrValue(new IndexedClass { Id = 7 })); Assert.Equal(123, new ClrPropertyGetterFactory().Create((IPropertyBase)propertyB).GetClrValue(new IndexedClass { Id = 7 })); diff --git a/test/EFCore.Tests/Metadata/Internal/EntityMaterializerSourceTest.cs b/test/EFCore.Tests/Metadata/Internal/EntityMaterializerSourceTest.cs index 57f5578d5c1..77bdd34ddbf 100644 --- a/test/EFCore.Tests/Metadata/Internal/EntityMaterializerSourceTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/EntityMaterializerSourceTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -10,6 +11,7 @@ using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; // ReSharper disable UnusedMember.Local @@ -27,7 +29,7 @@ public class EntityMaterializerSourceTest [ConditionalFact] public void Throws_for_abstract_types() { - var entityType = ((IMutableModel)new Model()).AddEntityType(typeof(SomeAbstractEntity)); + var entityType = ((IMutableModel)CreateConventionalModelBuilder().Model).AddEntityType(typeof(SomeAbstractEntity)); var source = new EntityMaterializerSource(new EntityMaterializerSourceDependencies()); Assert.Equal( @@ -40,7 +42,7 @@ public void Can_create_materializer_for_entity_with_constructor_properties() { var entityType = CreateEntityType(); - entityType[CoreAnnotationNames.ConstructorBinding] + entityType.ConstructorBinding = new ConstructorBinding( typeof(SomeEntity).GetTypeInfo().DeclaredConstructors.Single(c => c.GetParameters().Length == 2), new List @@ -49,14 +51,15 @@ public void Can_create_materializer_for_entity_with_constructor_properties() new PropertyParameterBinding((IProperty)entityType.FindProperty(nameof(SomeEntity.Goo))) } ); - ((Model)entityType.Model).FinalizeModel(); + + entityType.Model.FinalizeModel(); var factory = GetMaterializer(new EntityMaterializerSource(new EntityMaterializerSourceDependencies()), entityType); var gu = Guid.NewGuid(); var entity = (SomeEntity)factory( new MaterializationContext( - new ValueBuffer(new object[] { SomeEnum.EnumValue, "Fu", gu, 77, SomeEnum.EnumValue }), + new ValueBuffer(new object[] { 77, SomeEnum.EnumValue, "Fu", gu, SomeEnum.EnumValue }), _fakeContext)); Assert.Equal(77, entity.Id); @@ -76,7 +79,7 @@ public void Can_create_materializer_for_entity_with_factory_method() { var entityType = CreateEntityType(); - entityType[CoreAnnotationNames.ConstructorBinding] + entityType.ConstructorBinding = new FactoryMethodBinding( typeof(SomeEntity).GetTypeInfo().GetDeclaredMethod(nameof(SomeEntity.Factory)), new List @@ -85,14 +88,15 @@ public void Can_create_materializer_for_entity_with_factory_method() new PropertyParameterBinding((IProperty)entityType.FindProperty(nameof(SomeEntity.Goo))) }, entityType.ClrType); - ((Model)entityType.Model).FinalizeModel(); + + entityType.Model.FinalizeModel(); var factory = GetMaterializer(new EntityMaterializerSource(new EntityMaterializerSourceDependencies()), entityType); var gu = Guid.NewGuid(); var entity = (SomeEntity)factory( new MaterializationContext( - new ValueBuffer(new object[] { SomeEnum.EnumValue, "Fu", gu, 77, SomeEnum.EnumValue }), + new ValueBuffer(new object[] { 77, SomeEnum.EnumValue, "Fu", gu, SomeEnum.EnumValue }), _fakeContext)); Assert.Equal(77, entity.Id); @@ -112,7 +116,7 @@ public void Can_create_materializer_for_entity_with_factory_method_with_object_a { var entityType = CreateEntityType(); - entityType[CoreAnnotationNames.ConstructorBinding] + entityType.ConstructorBinding = new FactoryMethodBinding( typeof(SomeEntity).GetTypeInfo().GetDeclaredMethod(nameof(SomeEntity.GeneralFactory)), new List @@ -125,14 +129,15 @@ public void Can_create_materializer_for_entity_with_factory_method_with_object_a }) }, entityType.ClrType); - ((Model)entityType.Model).FinalizeModel(); + + entityType.Model.FinalizeModel(); var factory = GetMaterializer(new EntityMaterializerSource(new EntityMaterializerSourceDependencies()), entityType); var gu = Guid.NewGuid(); var entity = (SomeEntity)factory( new MaterializationContext( - new ValueBuffer(new object[] { SomeEnum.EnumValue, "Fu", gu, 77, SomeEnum.EnumValue }), + new ValueBuffer(new object[] { 77, SomeEnum.EnumValue, "Fu", gu, SomeEnum.EnumValue }), _fakeContext)); Assert.Equal(77, entity.Id); @@ -152,20 +157,21 @@ public void Can_create_materializer_for_entity_with_instance_factory_method() { var entityType = CreateEntityType(); - entityType[CoreAnnotationNames.ConstructorBinding] + entityType.ConstructorBinding = new FactoryMethodBinding( TestProxyFactory.Instance, typeof(TestProxyFactory).GetTypeInfo().GetDeclaredMethod(nameof(TestProxyFactory.Create)), new List { new EntityTypeParameterBinding() }, entityType.ClrType); - ((Model)entityType.Model).FinalizeModel(); + + entityType.Model.FinalizeModel(); var factory = GetMaterializer(new EntityMaterializerSource(new EntityMaterializerSourceDependencies()), entityType); var gu = Guid.NewGuid(); var entity = (SomeEntity)factory( new MaterializationContext( - new ValueBuffer(new object[] { SomeEnum.EnumValue, "Fu", gu, 77, SomeEnum.EnumValue }), + new ValueBuffer(new object[] { 77, SomeEnum.EnumValue, "Fu", gu, SomeEnum.EnumValue }), _fakeContext)); Assert.Equal(77, entity.Id); @@ -188,29 +194,21 @@ public object Create(IEntityType entityType) => Activator.CreateInstance(entityType.ClrType); } - private static IMutableEntityType CreateEntityType() - { - var entityType = ((IMutableModel)new Model()).AddEntityType(typeof(SomeEntity)); - entityType.AddProperty(SomeEntity.EnumProperty); - entityType.AddProperty(SomeEntity.FooProperty); - entityType.AddProperty(SomeEntity.GooProperty); - entityType.AddProperty(SomeEntity.IdProperty); - entityType.AddProperty(SomeEntity.MaybeEnumProperty); - return entityType; - } + private EntityType CreateEntityType() + => (EntityType)CreateConventionalModelBuilder().Entity().Metadata; [ConditionalFact] public void Can_create_materializer_for_entity_with_auto_properties() { var entityType = CreateEntityType(); - ((Model)entityType.Model).FinalizeModel(); + entityType.Model.FinalizeModel(); var factory = GetMaterializer(new EntityMaterializerSource(new EntityMaterializerSourceDependencies()), entityType); var gu = Guid.NewGuid(); var entity = (SomeEntity)factory( new MaterializationContext( - new ValueBuffer(new object[] { SomeEnum.EnumValue, "Fu", gu, 77, SomeEnum.EnumValue }), + new ValueBuffer(new object[] { 77, SomeEnum.EnumValue, "Fu", gu, SomeEnum.EnumValue }), _fakeContext)); Assert.Equal(77, entity.Id); @@ -223,20 +221,26 @@ public void Can_create_materializer_for_entity_with_auto_properties() [ConditionalFact] public void Can_create_materializer_for_entity_with_fields() { - var entityType = ((IMutableModel)new Model()).AddEntityType(typeof(SomeEntityWithFields)); - entityType.AddProperty(SomeEntityWithFields.EnumProperty).SetField("_enum"); - entityType.AddProperty(SomeEntityWithFields.FooProperty).SetField("_foo"); - entityType.AddProperty(SomeEntityWithFields.GooProperty).SetField("_goo"); - entityType.AddProperty(SomeEntityWithFields.IdProperty).SetField("_id"); - entityType.AddProperty(SomeEntityWithFields.MaybeEnumProperty).SetField("_maybeEnum"); - ((Model)entityType.Model).FinalizeModel(); + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity(eb => + { + eb.UsePropertyAccessMode(PropertyAccessMode.Field); + + eb.Property(e => e.Enum).HasField("_enum"); + eb.Property(e => e.Foo).HasField("_foo"); + eb.Property(e => e.Goo).HasField("_goo"); + eb.Property(e => e.Id).HasField("_id"); + eb.Property(e => e.MaybeEnum).HasField("_maybeEnum"); + }); + + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(SomeEntityWithFields)); var factory = GetMaterializer(new EntityMaterializerSource(new EntityMaterializerSourceDependencies()), entityType); var gu = Guid.NewGuid(); var entity = (SomeEntityWithFields)factory( new MaterializationContext( - new ValueBuffer(new object[] { SomeEnum.EnumValue, "Fu", gu, 77, null }), + new ValueBuffer(new object[] { 77, SomeEnum.EnumValue, "Fu", gu, null }), _fakeContext)); Assert.Equal(77, entity.Id); @@ -249,17 +253,20 @@ public void Can_create_materializer_for_entity_with_fields() [ConditionalFact] public void Can_read_nulls() { - var entityType = ((IMutableModel)new Model()).AddEntityType(typeof(SomeEntity)); - entityType.AddProperty(SomeEntity.FooProperty); - entityType.AddProperty(SomeEntity.GooProperty); - entityType.AddProperty(SomeEntity.IdProperty); - ((Model)entityType.Model).FinalizeModel(); + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity(eb => + { + eb.Ignore(e => e.Enum); + eb.Ignore(e => e.MaybeEnum); + }); + + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(SomeEntity)); var factory = GetMaterializer(new EntityMaterializerSource(new EntityMaterializerSourceDependencies()), entityType); var entity = (SomeEntity)factory( new MaterializationContext( - new ValueBuffer(new object[] { null, null, 77 }), + new ValueBuffer(new object[] { 77, null, null}), _fakeContext)); Assert.Equal(77, entity.Id); @@ -270,21 +277,28 @@ public void Can_read_nulls() [ConditionalFact] public void Can_create_materializer_for_entity_ignoring_shadow_fields() { - var entityType = ((IMutableModel)new Model()).AddEntityType(typeof(SomeEntity)); - entityType.AddProperty(SomeEntity.IdProperty); - entityType.AddProperty("IdShadow", typeof(int)); - entityType.AddProperty(SomeEntity.FooProperty); - entityType.AddProperty("FooShadow", typeof(string)); - entityType.AddProperty(SomeEntity.GooProperty); - entityType.AddProperty("GooShadow", typeof(Guid)); - ((Model)entityType.Model).FinalizeModel(); + var modelBuilder = CreateConventionalModelBuilder(); + + modelBuilder.Entity(eb => + { + eb.UsePropertyAccessMode(PropertyAccessMode.Property); + + eb.Ignore(e => e.Enum); + eb.Ignore(e => e.MaybeEnum); + + eb.Property("IdShadow"); + eb.Property("FooShadow"); + eb.Property("GooShadow"); + }); + + var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(SomeEntity)); var factory = GetMaterializer(new EntityMaterializerSource(new EntityMaterializerSourceDependencies()), entityType); var gu = Guid.NewGuid(); var entity = (SomeEntity)factory( new MaterializationContext( - new ValueBuffer(new object[] { "Fu", "FuS", gu, Guid.NewGuid(), 77, 777 }), + new ValueBuffer(new object[] { 77, "Fu", "FuS", gu, Guid.NewGuid(), 777 }), _fakeContext)); Assert.Equal(77, entity.Id); @@ -295,15 +309,19 @@ public void Can_create_materializer_for_entity_ignoring_shadow_fields() [ConditionalFact] public void Throws_if_parameterless_constructor_is_not_defined_on_entity_type() { - var entityType = ((IMutableModel)new Model()).AddEntityType(typeof(EntityWithoutParameterlessConstructor)); - entityType.AddProperty(EntityWithoutParameterlessConstructor.IdProperty); + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity(); Assert.Equal( - CoreStrings.NoParameterlessConstructor(typeof(EntityWithoutParameterlessConstructor).Name), + CoreStrings.ConstructorNotFound( + typeof(EntityWithoutParameterlessConstructor).Name, "cannot bind 'value' in 'EntityWithoutParameterlessConstructor(int value)'"), Assert.Throws( - () => GetMaterializer(new EntityMaterializerSource(new EntityMaterializerSourceDependencies()), entityType)).Message); + () => modelBuilder.FinalizeModel()).Message); } + protected virtual ModelBuilder CreateConventionalModelBuilder(bool sensitiveDataLoggingEnabled = false) + => InMemoryTestHelpers.Instance.CreateConventionBuilder(); + private static readonly ParameterExpression _contextParameter = Expression.Parameter(typeof(MaterializationContext), "materializationContext"); @@ -345,9 +363,16 @@ public static SomeEntity GeneralFactory(object[] constructorArguments) return Factory((int)constructorArguments[0], (Guid?)constructorArguments[1]); } + [NotMapped] public bool FactoryUsed { get; set; } + + [NotMapped] public bool ParameterizedConstructorUsed { get; } + + [NotMapped] public bool IdSetterCalled { get; set; } + + [NotMapped] public bool GooSetterCalled { get; set; } public static readonly PropertyInfo IdProperty = typeof(SomeEntity).GetProperty("Id"); diff --git a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs index cff0cacc81e..ffabd212993 100644 --- a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs @@ -47,6 +47,8 @@ private class FakeEntityType : Annotatable, IReadOnlyEntityType public string DefiningNavigationName { get; } public IReadOnlyEntityType DefiningEntityType { get; } public LambdaExpression QueryFilter { get; } + public InstantiationBinding ConstructorBinding { get; } + public InstantiationBinding ServiceOnlyConstructorBinding { get; } public IReadOnlyKey FindPrimaryKey() => throw new NotImplementedException();