From d4bb806c9ef59d3e5d6062a04e5178a8d6e7b1e5 Mon Sep 17 00:00:00 2001 From: AndriySvyryd Date: Thu, 12 Aug 2021 18:34:43 -0700 Subject: [PATCH 1/3] Use the pre-convention type mapping configuration in query Part of #25084 --- .../CosmosServiceCollectionExtensions.cs | 8 +- ...mosQueryTranslationPostprocessorFactory.cs | 6 - ...smosQueryTranslationPreprocessorFactory.cs | 6 - ...thodTranslatingExpressionVisitorFactory.cs | 5 - .../Query/Internal/ISqlExpressionFactory.cs | 8 - .../Query/Internal/SqlExpressionFactory.cs | 25 +- .../DesignTimeServiceCollectionExtensions.cs | 2 +- .../Design/Internal/DatabaseOperations.cs | 3 +- ...emoryProjectionBindingExpressionVisitor.cs | 4 +- ...yableMethodTranslatingExpressionVisitor.cs | 16 +- ...rameworkRelationalDesignServicesBuilder.cs | 2 +- .../Extensions/RelationalModelExtensions.cs | 37 ++ ...ntityFrameworkRelationalServicesBuilder.cs | 36 +- .../RelationalRuntimeModelConvention.cs | 2 +- .../Metadata/Internal/CheckConstraint.cs | 2 +- .../Metadata/Internal/DbFunction.cs | 4 +- .../Metadata/Internal/DbFunctionParameter.cs | 2 +- .../Metadata/Internal/StoreFunction.cs | 2 +- .../Metadata/RuntimeDbFunction.cs | 2 +- .../Query/IMemberTranslatorPlugin.cs | 8 +- ...tionalParameterBasedSqlProcessorFactory.cs | 7 +- .../Query/IRelationalQueryStringFactory.cs | 7 + ...lSqlTranslatingExpressionVisitorFactory.cs | 7 +- .../Query/ISqlExpressionFactory.cs | 9 +- ...tionalParameterBasedSqlProcessorFactory.cs | 7 +- ...ionalProjectionBindingExpressionVisitor.cs | 4 +- ...onalQueryTranslationPreprocessorFactory.cs | 7 +- ...thodTranslatingExpressionVisitorFactory.cs | 7 +- ...dQueryCompilingExpressionVisitorFactory.cs | 7 +- ...lSqlTranslatingExpressionVisitorFactory.cs | 7 +- ...qlExpressionOptimizingExpressionVisitor.cs | 555 ------------------ ...lExpressionSimplifyingExpressionVisitor.cs | 2 - .../RelationalMemberTranslatorProvider.cs | 7 +- ...nalMemberTranslatorProviderDependencies.cs | 8 +- .../RelationalMethodCallTranslatorProvider.cs | 7 +- ...ethodCallTranslatorProviderDependencies.cs | 7 +- ...lParameterBasedSqlProcessorDependencies.cs | 7 +- ...eryTranslationPostprocessorDependencies.cs | 7 +- ...ueryTranslationPreprocessorDependencies.cs | 7 +- ...yableMethodTranslatingExpressionVisitor.cs | 20 +- ...ranslatingExpressionVisitorDependencies.cs | 7 +- ...lationalSqlTranslatingExpressionVisitor.cs | 2 +- ...ranslatingExpressionVisitorDependencies.cs | 18 +- .../Query/SqlExpressionFactory.cs | 27 +- .../Query/SqlExpressionFactoryDependencies.cs | 20 +- .../RelationalCommandParameterObject.cs | 4 +- .../Storage/RelationalTypeMappingSource.cs | 7 +- .../RelationalTypeMappingSourceExtensions.cs | 1 - ...opologySuiteServiceCollectionExtensions.cs | 7 +- ...rNetTopologySuiteMemberTranslatorPlugin.cs | 7 - ...opologySuiteServiceCollectionExtensions.cs | 7 +- ...eNetTopologySuiteMemberTranslatorPlugin.cs | 7 - .../EntityFrameworkServicesBuilder.cs | 14 +- .../PropertyDiscoveryConvention.cs | 4 +- .../ServicePropertyDiscoveryConvention.cs | 4 +- .../Metadata/Internal/IMemberClassifier.cs | 4 +- .../Metadata/Internal/MemberClassifier.cs | 28 +- .../IQueryTranslationPostprocessorFactory.cs | 7 +- .../IQueryTranslationPreprocessorFactory.cs | 7 +- ...dQueryCompilingExpressionVisitorFactory.cs | 7 +- ...eryTranslationPostprocessorDependencies.cs | 7 +- ...ueryTranslationPreprocessorDependencies.cs | 7 +- ...ranslatingExpressionVisitorDependencies.cs | 7 +- src/EFCore/Storage/TypeMappingSource.cs | 5 + .../Query/NorthwindWhereQueryCosmosTest.cs | 20 +- .../Design/DesignTimeServicesTest.cs | 7 +- .../Design/SnapshotModelProcessorTest.cs | 2 + .../CSharpRuntimeModelCodeGeneratorTest.cs | 21 +- .../Internal/ReverseEngineerScaffolderTest.cs | 14 +- .../ReverseEngineeringConfigurationTests.cs | 6 +- .../DesignTimeTestBase.cs | 8 +- .../EFCore.Specification.Tests/FixtureBase.cs | 6 +- .../TestUtilities/TestModelSource.cs | 22 +- .../ValueConvertersEndToEndTestBase.cs | 8 + .../Query/QueryBugsTest.cs | 2 +- .../ValueConvertersEndToEndSqlServerTest.cs | 39 ++ 76 files changed, 397 insertions(+), 849 deletions(-) delete mode 100644 src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs diff --git a/src/EFCore.Cosmos/Extensions/CosmosServiceCollectionExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosServiceCollectionExtensions.cs index c3112006beb..1791029d5aa 100644 --- a/src/EFCore.Cosmos/Extensions/CosmosServiceCollectionExtensions.cs +++ b/src/EFCore.Cosmos/Extensions/CosmosServiceCollectionExtensions.cs @@ -116,8 +116,6 @@ public static IServiceCollection AddEntityFrameworkCosmos(this IServiceCollectio .TryAdd() .TryAdd() .TryAdd() - - // New Query pipeline .TryAdd() .TryAdd() .TryAdd(p => p.GetRequiredService()) @@ -128,10 +126,10 @@ public static IServiceCollection AddEntityFrameworkCosmos(this IServiceCollectio b => b .TryAddSingleton() .TryAddSingleton() - .TryAddSingleton() .TryAddSingleton() - .TryAddSingleton() - .TryAddSingleton() + .TryAddScoped() + .TryAddScoped() + .TryAddScoped() .TryAddScoped() ); diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQueryTranslationPostprocessorFactory.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQueryTranslationPostprocessorFactory.cs index 513bdde60e4..b0e93f35230 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosQueryTranslationPostprocessorFactory.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosQueryTranslationPostprocessorFactory.cs @@ -3,7 +3,6 @@ using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Utilities; -using Microsoft.Extensions.DependencyInjection; namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal { @@ -14,11 +13,6 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . - /// /// public class CosmosQueryTranslationPostprocessorFactory : IQueryTranslationPostprocessorFactory { diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQueryTranslationPreprocessorFactory.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQueryTranslationPreprocessorFactory.cs index d4ee8173384..108a33dc955 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosQueryTranslationPreprocessorFactory.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosQueryTranslationPreprocessorFactory.cs @@ -3,7 +3,6 @@ using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Utilities; -using Microsoft.Extensions.DependencyInjection; namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal { @@ -14,11 +13,6 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . - /// /// public class CosmosQueryTranslationPreprocessorFactory : IQueryTranslationPreprocessorFactory { diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitorFactory.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitorFactory.cs index 438ad86317d..a48fa413e39 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitorFactory.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitorFactory.cs @@ -14,11 +14,6 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . - /// /// public class CosmosQueryableMethodTranslatingExpressionVisitorFactory : IQueryableMethodTranslatingExpressionVisitorFactory { diff --git a/src/EFCore.Cosmos/Query/Internal/ISqlExpressionFactory.cs b/src/EFCore.Cosmos/Query/Internal/ISqlExpressionFactory.cs index 9b5e762bbcb..90ff147664f 100644 --- a/src/EFCore.Cosmos/Query/Internal/ISqlExpressionFactory.cs +++ b/src/EFCore.Cosmos/Query/Internal/ISqlExpressionFactory.cs @@ -35,14 +35,6 @@ public interface ISqlExpressionFactory /// SqlExpression ApplyDefaultTypeMapping(SqlExpression? sqlExpression); - /// - /// 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. - /// - CoreTypeMapping FindMapping(Type type); - /// /// 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.Cosmos/Query/Internal/SqlExpressionFactory.cs b/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs index 335c2c8c380..31b934b7352 100644 --- a/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs +++ b/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs @@ -25,6 +25,7 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal public class SqlExpressionFactory : ISqlExpressionFactory { private readonly ITypeMappingSource _typeMappingSource; + private readonly IModel _model; private readonly CoreTypeMapping _boolTypeMapping; /// @@ -33,9 +34,10 @@ public class SqlExpressionFactory : ISqlExpressionFactory /// 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 SqlExpressionFactory(ITypeMappingSource typeMappingSource) + public SqlExpressionFactory(ITypeMappingSource typeMappingSource, IModel model) { _typeMappingSource = typeMappingSource; + _model = model; _boolTypeMapping = typeMappingSource.FindMapping(typeof(bool)); } @@ -46,12 +48,10 @@ public SqlExpressionFactory(ITypeMappingSource typeMappingSource) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual SqlExpression ApplyDefaultTypeMapping(SqlExpression sqlExpression) - { - return sqlExpression == null + => sqlExpression == null || sqlExpression.TypeMapping != null ? sqlExpression - : ApplyTypeMapping(sqlExpression, _typeMappingSource.FindMapping(sqlExpression.Type)); - } + : ApplyTypeMapping(sqlExpression, _model.FindMapping(sqlExpression.Type)); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -170,8 +170,8 @@ private SqlExpression ApplyTypeMappingOnSqlBinary( inferredTypeMapping = ExpressionExtensions.InferTypeMapping(left, right) // We avoid object here since the result does not get typeMapping from outside. ?? (left.Type != typeof(object) - ? _typeMappingSource.FindMapping(left.Type) - : _typeMappingSource.FindMapping(right.Type)); + ? _model.FindMapping(left.Type) + : _model.FindMapping(right.Type)); resultType = typeof(bool); resultTypeMapping = _boolTypeMapping; } @@ -216,15 +216,6 @@ private SqlExpression ApplyTypeMappingOnSqlBinary( resultTypeMapping); } - /// - /// 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 CoreTypeMapping FindMapping(Type type) - => _typeMappingSource.FindMapping(type); - /// /// 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 @@ -495,7 +486,7 @@ public virtual SqlConditionalExpression Condition(SqlExpression test, SqlExpress /// public virtual InExpression In(SqlExpression item, SqlExpression values, bool negated) { - var typeMapping = item.TypeMapping ?? _typeMappingSource.FindMapping(item.Type); + var typeMapping = item.TypeMapping ?? _model.FindMapping(item.Type); item = ApplyTypeMapping(item, typeMapping); values = ApplyTypeMapping(values, typeMapping); diff --git a/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs b/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs index 7a4f5411714..6da95d49211 100644 --- a/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs +++ b/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs @@ -64,11 +64,11 @@ public static IServiceCollection AddEntityFrameworkDesignTimeServices( .TryAddSingleton( new DesignTimeConnectionStringResolver(applicationServiceProviderAccessor)) .TryAddSingleton() - .TryAddSingleton() .TryAddSingleton() .TryAddSingleton() .TryAddSingleton() .TryAddSingleton() + .TryAddScoped() .TryAddScoped() .TryAddScoped() .TryAddScoped()); diff --git a/src/EFCore.Design/Design/Internal/DatabaseOperations.cs b/src/EFCore.Design/Design/Internal/DatabaseOperations.cs index 99f8b634db3..9d882296e59 100644 --- a/src/EFCore.Design/Design/Internal/DatabaseOperations.cs +++ b/src/EFCore.Design/Design/Internal/DatabaseOperations.cs @@ -93,8 +93,9 @@ public virtual SavedModelFiles ScaffoldContext( : outputDir; var services = _servicesBuilder.Build(provider); + using var scope = services.CreateScope(); - var scaffolder = services.GetRequiredService(); + var scaffolder = scope.ServiceProvider.GetRequiredService(); var finalModelNamespace = modelNamespace ?? GetNamespaceFromOutputPath(outputDir); var finalContextNamespace = diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs index 292ca9f910d..fdfb63b299d 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs @@ -63,7 +63,7 @@ public virtual Expression Translate(InMemoryQueryExpression queryExpression, Exp _projectionMembers.Push(new ProjectionMember()); - var expandedExpression = _queryableMethodTranslatingExpressionVisitor.ExpandWeakEntities(_queryExpression, expression); + var expandedExpression = _queryableMethodTranslatingExpressionVisitor.ExpandSharedTypeEntities(_queryExpression, expression); var result = Visit(expandedExpression); if (result == QueryCompilationContext.NotTranslatedExpression) @@ -73,7 +73,7 @@ public virtual Expression Translate(InMemoryQueryExpression queryExpression, Exp _entityProjectionCache = new(); _clientProjections = new(); - expandedExpression = _queryableMethodTranslatingExpressionVisitor.ExpandWeakEntities(_queryExpression, expression); + expandedExpression = _queryableMethodTranslatingExpressionVisitor.ExpandSharedTypeEntities(_queryExpression, expression); result = Visit(expandedExpression); _queryExpression.ReplaceProjection(_clientProjections); diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index c3de097e64b..c4c836ce7d2 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -24,7 +24,7 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal public class InMemoryQueryableMethodTranslatingExpressionVisitor : QueryableMethodTranslatingExpressionVisitor { private readonly InMemoryExpressionTranslatingExpressionVisitor _expressionTranslator; - private readonly WeakEntityExpandingExpressionVisitor _weakEntityExpandingExpressionVisitor; + private readonly SharedTypeEntityExpandingExpressionVisitor _weakEntityExpandingExpressionVisitor; private readonly InMemoryProjectionBindingExpressionVisitor _projectionBindingExpressionVisitor; private readonly IModel _model; @@ -40,7 +40,7 @@ public InMemoryQueryableMethodTranslatingExpressionVisitor( : base(dependencies, queryCompilationContext, subquery: false) { _expressionTranslator = new InMemoryExpressionTranslatingExpressionVisitor(queryCompilationContext, this); - _weakEntityExpandingExpressionVisitor = new WeakEntityExpandingExpressionVisitor(_expressionTranslator); + _weakEntityExpandingExpressionVisitor = new SharedTypeEntityExpandingExpressionVisitor(_expressionTranslator); _projectionBindingExpressionVisitor = new InMemoryProjectionBindingExpressionVisitor(this, _expressionTranslator); _model = queryCompilationContext.Model; } @@ -56,7 +56,7 @@ protected InMemoryQueryableMethodTranslatingExpressionVisitor( : base(parentVisitor.Dependencies, parentVisitor.QueryCompilationContext, subquery: true) { _expressionTranslator = new InMemoryExpressionTranslatingExpressionVisitor(QueryCompilationContext, parentVisitor); - _weakEntityExpandingExpressionVisitor = new WeakEntityExpandingExpressionVisitor(_expressionTranslator); + _weakEntityExpandingExpressionVisitor = new SharedTypeEntityExpandingExpressionVisitor(_expressionTranslator); _projectionBindingExpressionVisitor = new InMemoryProjectionBindingExpressionVisitor(this, _expressionTranslator); _model = parentVisitor._model; } @@ -441,7 +441,7 @@ private static ShapedQueryExpression CreateShapedQueryExpressionStatic(IEntityTy new Expression[] { original1, original2 }, new[] { groupByShaper.KeySelector, groupByShaper }).Visit(resultSelector.Body); - newResultSelectorBody = ExpandWeakEntities(inMemoryQueryExpression, newResultSelectorBody); + newResultSelectorBody = ExpandSharedTypeEntities(inMemoryQueryExpression, newResultSelectorBody); var newShaper = _projectionBindingExpressionVisitor.Translate(inMemoryQueryExpression, newResultSelectorBody); return source.UpdateShaperExpression(newShaper); @@ -1265,13 +1265,13 @@ private Expression RemapLambdaBody(ShapedQueryExpression shapedQueryExpression, var lambdaBody = ReplacingExpressionVisitor.Replace( lambdaExpression.Parameters.Single(), shapedQueryExpression.ShaperExpression, lambdaExpression.Body); - return ExpandWeakEntities((InMemoryQueryExpression)shapedQueryExpression.QueryExpression, lambdaBody); + return ExpandSharedTypeEntities((InMemoryQueryExpression)shapedQueryExpression.QueryExpression, lambdaBody); } - internal Expression ExpandWeakEntities(InMemoryQueryExpression queryExpression, Expression lambdaBody) + internal Expression ExpandSharedTypeEntities(InMemoryQueryExpression queryExpression, Expression lambdaBody) => _weakEntityExpandingExpressionVisitor.Expand(queryExpression, lambdaBody); - private sealed class WeakEntityExpandingExpressionVisitor : ExpressionVisitor + private sealed class SharedTypeEntityExpandingExpressionVisitor : ExpressionVisitor { private static readonly MethodInfo _objectEqualsMethodInfo = typeof(object).GetRequiredRuntimeMethod(nameof(object.Equals), new[] { typeof(object), typeof(object) }); @@ -1280,7 +1280,7 @@ private static readonly MethodInfo _objectEqualsMethodInfo private InMemoryQueryExpression _queryExpression; - public WeakEntityExpandingExpressionVisitor(InMemoryExpressionTranslatingExpressionVisitor expressionTranslator) + public SharedTypeEntityExpandingExpressionVisitor(InMemoryExpressionTranslatingExpressionVisitor expressionTranslator) { _expressionTranslator = expressionTranslator; _queryExpression = null!; diff --git a/src/EFCore.Relational/Design/EntityFrameworkRelationalDesignServicesBuilder.cs b/src/EFCore.Relational/Design/EntityFrameworkRelationalDesignServicesBuilder.cs index 00a135ffbad..6f8ac836426 100644 --- a/src/EFCore.Relational/Design/EntityFrameworkRelationalDesignServicesBuilder.cs +++ b/src/EFCore.Relational/Design/EntityFrameworkRelationalDesignServicesBuilder.cs @@ -45,7 +45,7 @@ public static readonly IDictionary RelationalServi { { typeof(IAnnotationCodeGenerator), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IProviderConfigurationCodeGenerator), new ServiceCharacteristics(ServiceLifetime.Singleton) }, - { typeof(IDatabaseModelFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) } + { typeof(IDatabaseModelFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) } }; /// diff --git a/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs b/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs index 80e4fc6581e..c181470fa9c 100644 --- a/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs @@ -6,8 +6,10 @@ using System.Linq; using System.Reflection; using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; // ReSharper disable once CheckNamespace @@ -519,5 +521,40 @@ public static void SetCollation(this IMutableModel model, string? value) /// The configuration source for the collation. public static ConfigurationSource? GetCollationConfigurationSource(this IConventionModel model) => model.FindAnnotation(RelationalAnnotationNames.Collation)?.GetConfigurationSource(); + + /// + /// Gets the relational database type for a given object, throwing if no mapping is found. + /// + /// The model. + /// The object to get the mapping for. + /// The type mapping to be used. + public static RelationalTypeMapping GetMappingForValue( + this IModel model, + object? value) + => value == null + || value == DBNull.Value + ? RelationalTypeMapping.NullMapping + : model.GetMapping(value.GetType()); + + /// + /// Gets the relational database type for a given .NET type, throwing if no mapping is found. + /// + /// The model. + /// The type to get the mapping for. + /// The type mapping to be used. + public static RelationalTypeMapping GetMapping( + this IModel model, + Type clrType) + { + Check.NotNull(clrType, nameof(clrType)); + + var mapping = model.FindMapping(clrType); + if (mapping != null) + { + return (RelationalTypeMapping)mapping; + } + + throw new InvalidOperationException(RelationalStrings.UnsupportedType(clrType.ShortDisplayName())); + } } } diff --git a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs index 99691c4d16a..237602fc1c5 100644 --- a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs +++ b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs @@ -68,14 +68,15 @@ public static readonly IDictionary RelationalServi { typeof(IRelationalCommandBuilderFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IRawSqlCommandBuilder), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IQuerySqlGeneratorFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, - { typeof(IRelationalSqlTranslatingExpressionVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, - { typeof(IMethodCallTranslatorProvider), new ServiceCharacteristics(ServiceLifetime.Singleton) }, - { typeof(IMemberTranslatorProvider), new ServiceCharacteristics(ServiceLifetime.Singleton) }, - { typeof(ISqlExpressionFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, - { typeof(IRelationalQueryStringFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, + { typeof(IModificationCommandFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(ICommandBatchPreparer), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IModificationCommandBatchFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, - { typeof(IModificationCommandFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, + { typeof(IRelationalSqlTranslatingExpressionVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, + { typeof(IMethodCallTranslatorProvider), new ServiceCharacteristics(ServiceLifetime.Scoped) }, + { typeof(IMemberTranslatorProvider), new ServiceCharacteristics(ServiceLifetime.Scoped) }, + { typeof(ISqlExpressionFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, + { typeof(IRelationalQueryStringFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, + { typeof(IRelationalParameterBasedSqlProcessorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IMigrationsModelDiffer), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IMigrationsSqlGenerator), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IMigrator), new ServiceCharacteristics(ServiceLifetime.Scoped) }, @@ -97,10 +98,9 @@ public static readonly IDictionary RelationalServi }, { typeof(IMethodCallTranslatorPlugin), - new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) + new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) }, - { typeof(IMemberTranslatorPlugin), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) }, - { typeof(IRelationalParameterBasedSqlProcessorFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) } + { typeof(IMemberTranslatorPlugin), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) } }; /// @@ -196,17 +196,8 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices() .AddDependencySingleton() .AddDependencySingleton() .AddDependencySingleton() - .AddDependencySingleton() - .AddDependencySingleton() - .AddDependencySingleton() - .AddDependencySingleton() - .AddDependencySingleton() .AddDependencySingleton() - .AddDependencySingleton() - .AddDependencySingleton() .AddDependencySingleton() - .AddDependencySingleton() - .AddDependencySingleton() .AddDependencySingleton() .AddDependencySingleton() .AddDependencyScoped() @@ -216,6 +207,15 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices() .AddDependencyScoped() .AddDependencyScoped() .AddDependencyScoped() + .AddDependencyScoped() + .AddDependencyScoped() + .AddDependencyScoped() + .AddDependencyScoped() + .AddDependencyScoped() + .AddDependencyScoped() + .AddDependencyScoped() + .AddDependencyScoped() + .AddDependencyScoped() .AddDependencyScoped() .AddDependencyScoped() .AddDependencyScoped() diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs index cab04fb522a..0a6cae53807 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs @@ -62,7 +62,7 @@ protected override void ProcessModelAnnotations( if (annotations.TryGetValue(RelationalAnnotationNames.DbFunctions, out var functions)) { - var runtimeFunctions = new SortedDictionary(); + var runtimeFunctions = new SortedDictionary(StringComparer.Ordinal); foreach (var functionPair in (SortedDictionary)functions!) { var runtimeFunction = Create(functionPair.Value, runtimeModel); diff --git a/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs b/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs index 3d568666f1c..765d6606b71 100644 --- a/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs +++ b/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs @@ -50,7 +50,7 @@ public CheckConstraint( var constraints = GetConstraintsDictionary(EntityType); if (constraints == null) { - constraints = new SortedDictionary(); + constraints = new SortedDictionary(StringComparer.Ordinal); ((IMutableEntityType)EntityType).SetOrRemoveAnnotation(RelationalAnnotationNames.CheckConstraints, constraints); } diff --git a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs index 06741f4927b..e216884a63c 100644 --- a/src/EFCore.Relational/Metadata/Internal/DbFunction.cs +++ b/src/EFCore.Relational/Metadata/Internal/DbFunction.cs @@ -261,7 +261,7 @@ public static DbFunction AddDbFunction( private static SortedDictionary GetOrCreateFunctions(IMutableModel model) => (SortedDictionary)( - model[RelationalAnnotationNames.DbFunctions] ??= new SortedDictionary()); + model[RelationalAnnotationNames.DbFunctions] ??= new SortedDictionary(StringComparer.Ordinal)); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -551,7 +551,7 @@ public virtual RelationalTypeMapping? TypeMapping (IRelationalTypeMappingSource)((IModel)dbFunction.Model).GetModelDependencies().TypeMappingSource; return !string.IsNullOrEmpty(dbFunction._storeType) ? relationalTypeMappingSource.FindMapping(dbFunction._storeType)! - : relationalTypeMappingSource.FindMapping(dbFunction.ReturnType)!; + : relationalTypeMappingSource.FindMapping(dbFunction.ReturnType, (IModel)dbFunction.Model)!; }) : _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 ac8960c5c17..654221de4c8 100644 --- a/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs +++ b/src/EFCore.Relational/Metadata/Internal/DbFunctionParameter.cs @@ -162,7 +162,7 @@ public virtual RelationalTypeMapping? TypeMapping (IRelationalTypeMappingSource)((IModel)parameter.Function.Model).GetModelDependencies().TypeMappingSource; return !string.IsNullOrEmpty(parameter._storeType) ? relationalTypeMappingSource.FindMapping(parameter._storeType)! - : relationalTypeMappingSource.FindMapping(parameter.ClrType)!; + : relationalTypeMappingSource.FindMapping(parameter.ClrType, (IModel)parameter.Function.Model)!; }) : _typeMapping; set => SetTypeMapping(value, ConfigurationSource.Explicit); diff --git a/src/EFCore.Relational/Metadata/Internal/StoreFunction.cs b/src/EFCore.Relational/Metadata/Internal/StoreFunction.cs index c9c7d79a06b..493b255f361 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoreFunction.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoreFunction.cs @@ -25,7 +25,7 @@ public class StoreFunction : TableBase, IStoreFunction public StoreFunction(IRuntimeDbFunction dbFunction, RelationalModel model) : base(dbFunction.Name, dbFunction.Schema, model) { - DbFunctions = new SortedDictionary { { dbFunction.ModelName, dbFunction } }; + DbFunctions = new SortedDictionary(StringComparer.Ordinal) { { dbFunction.ModelName, dbFunction } }; IsBuiltIn = dbFunction.IsBuiltIn; ReturnType = dbFunction.StoreType; diff --git a/src/EFCore.Relational/Metadata/RuntimeDbFunction.cs b/src/EFCore.Relational/Metadata/RuntimeDbFunction.cs index 288153d066e..6240b5d9d58 100644 --- a/src/EFCore.Relational/Metadata/RuntimeDbFunction.cs +++ b/src/EFCore.Relational/Metadata/RuntimeDbFunction.cs @@ -101,7 +101,7 @@ public virtual RelationalTypeMapping? TypeMapping (IRelationalTypeMappingSource)((IModel)dbFunction.Model).GetModelDependencies().TypeMappingSource; return !string.IsNullOrEmpty(dbFunction._storeType) ? relationalTypeMappingSource.FindMapping(dbFunction._storeType)! - : relationalTypeMappingSource.FindMapping(dbFunction._returnType)!; + : relationalTypeMappingSource.FindMapping(dbFunction._returnType, (IModel)dbFunction.Model)!; }) : _typeMapping; set => _typeMapping = value; diff --git a/src/EFCore.Relational/Query/IMemberTranslatorPlugin.cs b/src/EFCore.Relational/Query/IMemberTranslatorPlugin.cs index 0ca143ba5be..f65caf013f3 100644 --- a/src/EFCore.Relational/Query/IMemberTranslatorPlugin.cs +++ b/src/EFCore.Relational/Query/IMemberTranslatorPlugin.cs @@ -11,10 +11,10 @@ namespace Microsoft.EntityFrameworkCore.Query /// Represents plugin for . /// /// - /// The service lifetime is and multiple registrations - /// are allowed. This means a single instance of each service is used by many - /// instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public interface IMemberTranslatorPlugin diff --git a/src/EFCore.Relational/Query/IRelationalParameterBasedSqlProcessorFactory.cs b/src/EFCore.Relational/Query/IRelationalParameterBasedSqlProcessorFactory.cs index 72acb3751c4..840625c001a 100644 --- a/src/EFCore.Relational/Query/IRelationalParameterBasedSqlProcessorFactory.cs +++ b/src/EFCore.Relational/Query/IRelationalParameterBasedSqlProcessorFactory.cs @@ -10,9 +10,10 @@ namespace Microsoft.EntityFrameworkCore.Query /// A factory for creating instances. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public interface IRelationalParameterBasedSqlProcessorFactory diff --git a/src/EFCore.Relational/Query/IRelationalQueryStringFactory.cs b/src/EFCore.Relational/Query/IRelationalQueryStringFactory.cs index e296f906eab..4d783c6edef 100644 --- a/src/EFCore.Relational/Query/IRelationalQueryStringFactory.cs +++ b/src/EFCore.Relational/Query/IRelationalQueryStringFactory.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Data.Common; +using Microsoft.Extensions.DependencyInjection; namespace Microsoft.EntityFrameworkCore.Query { @@ -13,6 +14,12 @@ namespace Microsoft.EntityFrameworkCore.Query /// This interface is typically used by database providers (and other extensions). It is generally /// not used in application code. /// + /// + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. + /// /// public interface IRelationalQueryStringFactory { diff --git a/src/EFCore.Relational/Query/IRelationalSqlTranslatingExpressionVisitorFactory.cs b/src/EFCore.Relational/Query/IRelationalSqlTranslatingExpressionVisitorFactory.cs index fb67fa3bb02..a5d3b4f9475 100644 --- a/src/EFCore.Relational/Query/IRelationalSqlTranslatingExpressionVisitorFactory.cs +++ b/src/EFCore.Relational/Query/IRelationalSqlTranslatingExpressionVisitorFactory.cs @@ -10,9 +10,10 @@ namespace Microsoft.EntityFrameworkCore.Query /// A factory for creating instances. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public interface IRelationalSqlTranslatingExpressionVisitorFactory diff --git a/src/EFCore.Relational/Query/ISqlExpressionFactory.cs b/src/EFCore.Relational/Query/ISqlExpressionFactory.cs index 826ce21299e..d96b3dda0e0 100644 --- a/src/EFCore.Relational/Query/ISqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/ISqlExpressionFactory.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Metadata; @@ -17,9 +15,10 @@ namespace Microsoft.EntityFrameworkCore.Query /// A factory for creating instances. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public interface ISqlExpressionFactory diff --git a/src/EFCore.Relational/Query/Internal/RelationalParameterBasedSqlProcessorFactory.cs b/src/EFCore.Relational/Query/Internal/RelationalParameterBasedSqlProcessorFactory.cs index beb179f4ee9..2d18fa847c9 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalParameterBasedSqlProcessorFactory.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalParameterBasedSqlProcessorFactory.cs @@ -14,9 +14,10 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal /// doing so can result in application failures when updating to a new Entity Framework Core release. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public class RelationalParameterBasedSqlProcessorFactory : IRelationalParameterBasedSqlProcessorFactory diff --git a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs index bae6e2bd969..ca13fa8b03f 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs @@ -69,7 +69,7 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio _projectionMembers.Push(new ProjectionMember()); - var expandedExpression = _queryableMethodTranslatingExpressionVisitor.ExpandWeakEntities(_selectExpression, expression); + var expandedExpression = _queryableMethodTranslatingExpressionVisitor.ExpandSharedTypeEntities(_selectExpression, expression); var result = Visit(expandedExpression); if (result == QueryCompilationContext.NotTranslatedExpression) @@ -79,7 +79,7 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio _projectionMapping.Clear(); _clientProjections = new List(); - expandedExpression = _queryableMethodTranslatingExpressionVisitor.ExpandWeakEntities(_selectExpression, expression); + expandedExpression = _queryableMethodTranslatingExpressionVisitor.ExpandSharedTypeEntities(_selectExpression, expression); result = Visit(expandedExpression); _selectExpression.ReplaceProjection(_clientProjections); diff --git a/src/EFCore.Relational/Query/Internal/RelationalQueryTranslationPreprocessorFactory.cs b/src/EFCore.Relational/Query/Internal/RelationalQueryTranslationPreprocessorFactory.cs index cc189ff1595..25ad40d5005 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalQueryTranslationPreprocessorFactory.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalQueryTranslationPreprocessorFactory.cs @@ -14,9 +14,10 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal /// doing so can result in application failures when updating to a new Entity Framework Core release. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public class RelationalQueryTranslationPreprocessorFactory : IQueryTranslationPreprocessorFactory diff --git a/src/EFCore.Relational/Query/Internal/RelationalQueryableMethodTranslatingExpressionVisitorFactory.cs b/src/EFCore.Relational/Query/Internal/RelationalQueryableMethodTranslatingExpressionVisitorFactory.cs index 25162ce5f47..6d9610f5ece 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalQueryableMethodTranslatingExpressionVisitorFactory.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalQueryableMethodTranslatingExpressionVisitorFactory.cs @@ -14,9 +14,10 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal /// doing so can result in application failures when updating to a new Entity Framework Core release. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public class RelationalQueryableMethodTranslatingExpressionVisitorFactory : IQueryableMethodTranslatingExpressionVisitorFactory diff --git a/src/EFCore.Relational/Query/Internal/RelationalShapedQueryCompilingExpressionVisitorFactory.cs b/src/EFCore.Relational/Query/Internal/RelationalShapedQueryCompilingExpressionVisitorFactory.cs index 1bff29290ef..c0cf7fd5848 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalShapedQueryCompilingExpressionVisitorFactory.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalShapedQueryCompilingExpressionVisitorFactory.cs @@ -14,9 +14,10 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal /// doing so can result in application failures when updating to a new Entity Framework Core release. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public class RelationalShapedQueryCompilingExpressionVisitorFactory : IShapedQueryCompilingExpressionVisitorFactory diff --git a/src/EFCore.Relational/Query/Internal/RelationalSqlTranslatingExpressionVisitorFactory.cs b/src/EFCore.Relational/Query/Internal/RelationalSqlTranslatingExpressionVisitorFactory.cs index 3d16de73177..2bb24602ee9 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalSqlTranslatingExpressionVisitorFactory.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalSqlTranslatingExpressionVisitorFactory.cs @@ -14,9 +14,10 @@ namespace Microsoft.EntityFrameworkCore.Query /// doing so can result in application failures when updating to a new Entity Framework Core release. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public class RelationalSqlTranslatingExpressionVisitorFactory : IRelationalSqlTranslatingExpressionVisitorFactory diff --git a/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs deleted file mode 100644 index 5b71f4c5c37..00000000000 --- a/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs +++ /dev/null @@ -1,555 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Linq; -using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore.Query.SqlExpressions; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.EntityFrameworkCore.Utilities; - -namespace Microsoft.EntityFrameworkCore.Query.Internal -{ - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public class SqlExpressionOptimizingExpressionVisitor : ExpressionVisitor - { - private readonly bool _useRelationalNulls; - private readonly ISqlExpressionFactory _sqlExpressionFactory; - - private static bool TryNegate(ExpressionType expressionType, out ExpressionType result) - { - var negated = expressionType switch - { - ExpressionType.AndAlso => ExpressionType.OrElse, - ExpressionType.OrElse => ExpressionType.AndAlso, - ExpressionType.Equal => ExpressionType.NotEqual, - ExpressionType.NotEqual => ExpressionType.Equal, - ExpressionType.GreaterThan => ExpressionType.LessThanOrEqual, - ExpressionType.GreaterThanOrEqual => ExpressionType.LessThan, - ExpressionType.LessThan => ExpressionType.GreaterThanOrEqual, - ExpressionType.LessThanOrEqual => ExpressionType.GreaterThan, - _ => (ExpressionType?)null - }; - - result = negated ?? default; - - return negated.HasValue; - } - - /// - /// 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 SqlExpressionOptimizingExpressionVisitor(ISqlExpressionFactory sqlExpressionFactory, bool useRelationalNulls) - { - _sqlExpressionFactory = sqlExpressionFactory; - _useRelationalNulls = useRelationalNulls; - } - - /// - /// 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. - /// - protected override Expression VisitExtension(Expression extensionExpression) - { - Check.NotNull(extensionExpression, nameof(extensionExpression)); - -#pragma warning disable IDE0066 // Convert switch statement to expression - switch (extensionExpression) -#pragma warning restore IDE0066 // Convert switch statement to expression - { - case ShapedQueryExpression shapedQueryExpression: - return shapedQueryExpression.Update( - Visit(shapedQueryExpression.QueryExpression), shapedQueryExpression.ShaperExpression); - - case SqlUnaryExpression sqlUnaryExpression: - return VisitSqlUnary(sqlUnaryExpression); - - case SqlBinaryExpression sqlBinaryExpression: - return VisitSqlBinary(sqlBinaryExpression); - - case SelectExpression selectExpression: - return VisitSelect(selectExpression); - - default: - return base.VisitExtension(extensionExpression); - } - - ; - } - - private Expression VisitSelect(SelectExpression selectExpression) - { - var newExpression = base.VisitExtension(selectExpression); - - // if predicate is optimized to true, we can simply remove it - if (newExpression is SelectExpression newSelectExpression) - { - var changed = false; - var newPredicate = newSelectExpression.Predicate; - var newHaving = newSelectExpression.Having; - if (newSelectExpression.Predicate is SqlConstantExpression predicateConstantExpression - && predicateConstantExpression.Value is bool predicateBoolValue - && predicateBoolValue) - { - newPredicate = null; - changed = true; - } - - if (newSelectExpression.Having is SqlConstantExpression havingConstantExpression - && havingConstantExpression.Value is bool havingBoolValue - && havingBoolValue) - { - newHaving = null; - changed = true; - } - - return changed - ? newSelectExpression.Update( - newSelectExpression.Projection.ToList(), - newSelectExpression.Tables.ToList(), - newPredicate, - newSelectExpression.GroupBy.ToList(), - newHaving, - newSelectExpression.Orderings.ToList(), - newSelectExpression.Limit, - newSelectExpression.Offset) - : newSelectExpression; - } - - return newExpression; - } - - private Expression VisitSqlUnary(SqlUnaryExpression sqlUnaryExpression) - { - var newOperand = (SqlExpression)Visit(sqlUnaryExpression.Operand); - - return SimplifyUnaryExpression( - sqlUnaryExpression.OperatorType, - newOperand, - sqlUnaryExpression.Type, - sqlUnaryExpression.TypeMapping!); - } - - private SqlExpression SimplifyUnaryExpression( - ExpressionType operatorType, - SqlExpression operand, - Type type, - RelationalTypeMapping typeMapping) - { - switch (operatorType) - { - case ExpressionType.Not - when type == typeof(bool): - { - switch (operand) - { - // !(true) -> false - // !(false) -> true - case SqlConstantExpression constantOperand - when constantOperand.Value is bool value: - { - return _sqlExpressionFactory.Constant(!value, typeMapping); - } - - case InExpression inOperand: - return inOperand.Negate(); - - case SqlUnaryExpression unaryOperand: - switch (unaryOperand.OperatorType) - { - // !(!a) -> a - case ExpressionType.Not: - return unaryOperand.Operand; - - //!(a IS NULL) -> a IS NOT NULL - case ExpressionType.Equal: - return _sqlExpressionFactory.IsNotNull(unaryOperand.Operand); - - //!(a IS NOT NULL) -> a IS NULL - case ExpressionType.NotEqual: - return _sqlExpressionFactory.IsNull(unaryOperand.Operand); - } - - break; - - case SqlBinaryExpression binaryOperand: - { - // De Morgan's - if (binaryOperand.OperatorType == ExpressionType.AndAlso - || binaryOperand.OperatorType == ExpressionType.OrElse) - { - var newLeft = SimplifyUnaryExpression(ExpressionType.Not, binaryOperand.Left, type, typeMapping); - var newRight = SimplifyUnaryExpression(ExpressionType.Not, binaryOperand.Right, type, typeMapping); - - return SimplifyLogicalSqlBinaryExpression( - binaryOperand.OperatorType == ExpressionType.AndAlso - ? ExpressionType.OrElse - : ExpressionType.AndAlso, - newLeft, - newRight, - binaryOperand.TypeMapping!); - } - - // those optimizations are only valid in 2-value logic - // they are safe to do here because if we apply null semantics - // because null semantics removes possibility of nulls in the tree when the comparison is wrapped around NOT - if (!_useRelationalNulls - && TryNegate(binaryOperand.OperatorType, out var negated)) - { - return SimplifyBinaryExpression( - negated, - binaryOperand.Left, - binaryOperand.Right, - binaryOperand.TypeMapping!); - } - } - break; - } - - break; - } - - case ExpressionType.Equal: - case ExpressionType.NotEqual: - return SimplifyNullNotNullExpression( - operatorType, - operand, - type, - typeMapping); - } - - return _sqlExpressionFactory.MakeUnary(operatorType, operand, type, typeMapping)!; - } - - private SqlExpression SimplifyNullNotNullExpression( - ExpressionType operatorType, - SqlExpression operand, - Type type, - RelationalTypeMapping? typeMapping) - { - switch (operatorType) - { - case ExpressionType.Equal: - case ExpressionType.NotEqual: - switch (operand) - { - case SqlConstantExpression constantOperand: - return _sqlExpressionFactory.Constant( - operatorType == ExpressionType.Equal - ? constantOperand.Value == null - : constantOperand.Value != null, - typeMapping); - - case ColumnExpression columnOperand - when !columnOperand.IsNullable: - return _sqlExpressionFactory.Constant(operatorType == ExpressionType.NotEqual, typeMapping); - - case SqlUnaryExpression sqlUnaryOperand: - if (sqlUnaryOperand.OperatorType == ExpressionType.Convert - || sqlUnaryOperand.OperatorType == ExpressionType.Not - || sqlUnaryOperand.OperatorType == ExpressionType.Negate) - { - // op(a) is null -> a is null - // op(a) is not null -> a is not null - return SimplifyNullNotNullExpression(operatorType, sqlUnaryOperand.Operand, type, typeMapping); - } - - if (sqlUnaryOperand.OperatorType == ExpressionType.Equal - || sqlUnaryOperand.OperatorType == ExpressionType.NotEqual) - { - // (a is null) is null -> false - // (a is not null) is null -> false - // (a is null) is not null -> true - // (a is not null) is not null -> true - return _sqlExpressionFactory.Constant(operatorType == ExpressionType.NotEqual, typeMapping); - } - - break; - - case SqlBinaryExpression sqlBinaryOperand: - // in general: - // binaryOp(a, b) == null -> a == null || b == null - // binaryOp(a, b) != null -> a != null && b != null - // for AndAlso, OrElse we can't do this optimization - // we could do something like this, but it seems too complicated: - // (a && b) == null -> a == null && b != 0 || a != 0 && b == null - if (sqlBinaryOperand.OperatorType != ExpressionType.AndAlso - && sqlBinaryOperand.OperatorType != ExpressionType.OrElse) - { - var newLeft = SimplifyNullNotNullExpression(operatorType, sqlBinaryOperand.Left, typeof(bool), typeMapping); - var newRight = SimplifyNullNotNullExpression( - operatorType, sqlBinaryOperand.Right, typeof(bool), typeMapping); - - return SimplifyLogicalSqlBinaryExpression( - operatorType == ExpressionType.Equal - ? ExpressionType.OrElse - : ExpressionType.AndAlso, - newLeft, - newRight, - typeMapping); - } - - break; - - case SqlFunctionExpression sqlFunctionExpression - when sqlFunctionExpression.IsBuiltIn - && sqlFunctionExpression.Arguments != null - && string.Equals("COALESCE", sqlFunctionExpression.Name, StringComparison.OrdinalIgnoreCase): - // for coalesce: - // (a ?? b) == null -> a == null && b == null - // (a ?? b) != null -> a != null || b != null - var leftArgument = SimplifyNullNotNullExpression( - operatorType, sqlFunctionExpression.Arguments[0], typeof(bool), typeMapping); - var rightArgument = SimplifyNullNotNullExpression( - operatorType, sqlFunctionExpression.Arguments[1], typeof(bool), typeMapping); - - return SimplifyLogicalSqlBinaryExpression( - operatorType == ExpressionType.Equal - ? ExpressionType.AndAlso - : ExpressionType.OrElse, - leftArgument, - rightArgument, - typeMapping); - } - - break; - } - - return _sqlExpressionFactory.MakeUnary(operatorType, operand, type, typeMapping)!; - } - - private Expression VisitSqlBinary(SqlBinaryExpression sqlBinaryExpression) - { - var newLeft = (SqlExpression)Visit(sqlBinaryExpression.Left); - var newRight = (SqlExpression)Visit(sqlBinaryExpression.Right); - - return SimplifyBinaryExpression( - sqlBinaryExpression.OperatorType, - newLeft, - newRight, - sqlBinaryExpression.TypeMapping!); - } - - private SqlExpression SimplifyBinaryExpression( - ExpressionType operatorType, - SqlExpression left, - SqlExpression right, - RelationalTypeMapping typeMapping) - { - switch (operatorType) - { - case ExpressionType.AndAlso: - case ExpressionType.OrElse: - var leftUnary = left as SqlUnaryExpression; - var rightUnary = right as SqlUnaryExpression; - if (leftUnary != null - && rightUnary != null - && (leftUnary.OperatorType == ExpressionType.Equal || leftUnary.OperatorType == ExpressionType.NotEqual) - && (rightUnary.OperatorType == ExpressionType.Equal || rightUnary.OperatorType == ExpressionType.NotEqual) - && leftUnary.Operand.Equals(rightUnary.Operand)) - { - // a is null || a is null -> a is null - // a is not null || a is not null -> a is not null - // a is null && a is null -> a is null - // a is not null && a is not null -> a is not null - // a is null || a is not null -> true - // a is null && a is not null -> false - return leftUnary.OperatorType == rightUnary.OperatorType - ? (SqlExpression)leftUnary - : _sqlExpressionFactory.Constant(operatorType == ExpressionType.OrElse, typeMapping); - } - - return SimplifyLogicalSqlBinaryExpression( - operatorType, - left, - right, - typeMapping); - - case ExpressionType.Equal: - case ExpressionType.NotEqual: - var leftConstant = left as SqlConstantExpression; - var rightConstant = right as SqlConstantExpression; - var leftNullConstant = leftConstant != null && leftConstant.Value == null; - var rightNullConstant = rightConstant != null && rightConstant.Value == null; - if (leftNullConstant || rightNullConstant) - { - return SimplifyNullComparisonExpression( - operatorType, - left, - right, - leftNullConstant, - rightNullConstant, - typeMapping); - } - - var leftBoolValue = left.Type == typeof(bool) ? (bool?)leftConstant?.Value : null; - var rightBoolValue = right.Type == typeof(bool) ? (bool?)rightConstant?.Value : null; - if (leftBoolValue != null || rightBoolValue != null) - { - return SimplifyBoolConstantComparisonExpression( - operatorType, - left, - right, - leftBoolValue, - rightBoolValue, - typeMapping); - } - - // only works when a is not nullable - // a == a -> true - // a != a -> false - if ((left is LikeExpression - || left is ColumnExpression columnExpression && !columnExpression.IsNullable) - && left.Equals(right)) - { - return _sqlExpressionFactory.Constant(operatorType == ExpressionType.Equal, typeMapping); - } - - break; - } - - return _sqlExpressionFactory.MakeBinary(operatorType, left, right, typeMapping)!; - } - - private SqlExpression SimplifyNullComparisonExpression( - ExpressionType operatorType, - SqlExpression left, - SqlExpression right, - bool leftNull, - bool rightNull, - RelationalTypeMapping? typeMapping) - { - if ((operatorType == ExpressionType.Equal || operatorType == ExpressionType.NotEqual) - && (leftNull || rightNull)) - { - if (leftNull && rightNull) - { - return _sqlExpressionFactory.Constant(operatorType == ExpressionType.Equal, typeMapping); - } - - if (leftNull) - { - return SimplifyNullNotNullExpression(operatorType, right, typeof(bool), typeMapping); - } - - if (rightNull) - { - return SimplifyNullNotNullExpression(operatorType, left, typeof(bool), typeMapping); - } - } - - return _sqlExpressionFactory.MakeBinary(operatorType, left, right, typeMapping)!; - } - - private SqlExpression SimplifyBoolConstantComparisonExpression( - ExpressionType operatorType, - SqlExpression left, - SqlExpression right, - bool? leftBoolValue, - bool? rightBoolValue, - RelationalTypeMapping typeMapping) - { - if (leftBoolValue != null && rightBoolValue != null) - { - return operatorType == ExpressionType.Equal - ? _sqlExpressionFactory.Constant(leftBoolValue.Value == rightBoolValue.Value, typeMapping) - : _sqlExpressionFactory.Constant(leftBoolValue.Value != rightBoolValue.Value, typeMapping); - } - - if (rightBoolValue != null - && CanOptimize(left)) - { - // a == true -> a - // a == false -> !a - // a != true -> !a - // a != false -> a - // only correct when f(x) can't be null - return operatorType == ExpressionType.Equal - ? rightBoolValue.Value - ? left - : SimplifyUnaryExpression(ExpressionType.Not, left, typeof(bool), typeMapping) - : rightBoolValue.Value - ? SimplifyUnaryExpression(ExpressionType.Not, left, typeof(bool), typeMapping) - : left; - } - - if (leftBoolValue != null - && CanOptimize(right)) - { - // true == a -> a - // false == a -> !a - // true != a -> !a - // false != a -> a - // only correct when a can't be null - return operatorType == ExpressionType.Equal - ? leftBoolValue.Value - ? right - : SimplifyUnaryExpression(ExpressionType.Not, right, typeof(bool), typeMapping) - : leftBoolValue.Value - ? SimplifyUnaryExpression(ExpressionType.Not, right, typeof(bool), typeMapping) - : right; - } - - return _sqlExpressionFactory.MakeBinary(operatorType, left, right, typeMapping)!; - - static bool CanOptimize(SqlExpression operand) - => operand is LikeExpression - || (operand is SqlUnaryExpression sqlUnary - && (sqlUnary.OperatorType == ExpressionType.Equal - || sqlUnary.OperatorType == ExpressionType.NotEqual - // TODO: #18689 - /*|| sqlUnary.OperatorType == ExpressionType.Not*/)); - } - - private SqlExpression SimplifyLogicalSqlBinaryExpression( - ExpressionType operatorType, - SqlExpression left, - SqlExpression right, - RelationalTypeMapping? typeMapping) - { - // true && a -> a - // true || a -> true - // false && a -> false - // false || a -> a - if (left is SqlConstantExpression newLeftConstant - && newLeftConstant.Value is bool leftBoolValue) - { - return operatorType == ExpressionType.AndAlso - ? leftBoolValue - ? right - : newLeftConstant - : leftBoolValue - ? newLeftConstant - : right; - } - - if (right is SqlConstantExpression newRightConstant - && newRightConstant.Value is bool rightBoolValue) - { - // a && true -> a - // a || true -> true - // a && false -> false - // a || false -> a - return operatorType == ExpressionType.AndAlso - ? rightBoolValue - ? left - : newRightConstant - : rightBoolValue - ? newRightConstant - : left; - } - - return _sqlExpressionFactory.MakeBinary(operatorType, left, right, typeMapping)!; - } - } -} diff --git a/src/EFCore.Relational/Query/Internal/SqlExpressionSimplifyingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/SqlExpressionSimplifyingExpressionVisitor.cs index a7df7406f9e..3582153d42e 100644 --- a/src/EFCore.Relational/Query/Internal/SqlExpressionSimplifyingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/SqlExpressionSimplifyingExpressionVisitor.cs @@ -2,9 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; using Microsoft.EntityFrameworkCore.Storage; diff --git a/src/EFCore.Relational/Query/RelationalMemberTranslatorProvider.cs b/src/EFCore.Relational/Query/RelationalMemberTranslatorProvider.cs index 9cb88ffb0ee..a92731e1e0d 100644 --- a/src/EFCore.Relational/Query/RelationalMemberTranslatorProvider.cs +++ b/src/EFCore.Relational/Query/RelationalMemberTranslatorProvider.cs @@ -20,9 +20,10 @@ namespace Microsoft.EntityFrameworkCore.Query /// translators. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public class RelationalMemberTranslatorProvider : IMemberTranslatorProvider diff --git a/src/EFCore.Relational/Query/RelationalMemberTranslatorProviderDependencies.cs b/src/EFCore.Relational/Query/RelationalMemberTranslatorProviderDependencies.cs index 070edbfca39..7df68a8e877 100644 --- a/src/EFCore.Relational/Query/RelationalMemberTranslatorProviderDependencies.cs +++ b/src/EFCore.Relational/Query/RelationalMemberTranslatorProviderDependencies.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Generic; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.Extensions.DependencyInjection; @@ -25,9 +24,10 @@ namespace Microsoft.EntityFrameworkCore.Query /// services using the 'With...' methods. Do not call the constructor at any point in this process. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public sealed record RelationalMemberTranslatorProviderDependencies diff --git a/src/EFCore.Relational/Query/RelationalMethodCallTranslatorProvider.cs b/src/EFCore.Relational/Query/RelationalMethodCallTranslatorProvider.cs index 3a22754d114..421728e27da 100644 --- a/src/EFCore.Relational/Query/RelationalMethodCallTranslatorProvider.cs +++ b/src/EFCore.Relational/Query/RelationalMethodCallTranslatorProvider.cs @@ -21,9 +21,10 @@ namespace Microsoft.EntityFrameworkCore.Query /// method call translators. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public class RelationalMethodCallTranslatorProvider : IMethodCallTranslatorProvider diff --git a/src/EFCore.Relational/Query/RelationalMethodCallTranslatorProviderDependencies.cs b/src/EFCore.Relational/Query/RelationalMethodCallTranslatorProviderDependencies.cs index ee46b887c0e..e7fec1b389e 100644 --- a/src/EFCore.Relational/Query/RelationalMethodCallTranslatorProviderDependencies.cs +++ b/src/EFCore.Relational/Query/RelationalMethodCallTranslatorProviderDependencies.cs @@ -26,9 +26,10 @@ namespace Microsoft.EntityFrameworkCore.Query /// services using the 'With...' methods. Do not call the constructor at any point in this process. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public sealed record RelationalMethodCallTranslatorProviderDependencies diff --git a/src/EFCore.Relational/Query/RelationalParameterBasedSqlProcessorDependencies.cs b/src/EFCore.Relational/Query/RelationalParameterBasedSqlProcessorDependencies.cs index 6a59362de0c..2a80ffc2c57 100644 --- a/src/EFCore.Relational/Query/RelationalParameterBasedSqlProcessorDependencies.cs +++ b/src/EFCore.Relational/Query/RelationalParameterBasedSqlProcessorDependencies.cs @@ -26,9 +26,10 @@ namespace Microsoft.EntityFrameworkCore.Query /// services using the 'With...' methods. Do not call the constructor at any point in this process. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public sealed record RelationalParameterBasedSqlProcessorDependencies diff --git a/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessorDependencies.cs b/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessorDependencies.cs index 351a15802c6..2b6fc2e8e6d 100644 --- a/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessorDependencies.cs +++ b/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessorDependencies.cs @@ -24,9 +24,10 @@ namespace Microsoft.EntityFrameworkCore.Query /// services using the 'With...' methods. Do not call the constructor at any point in this process. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public sealed record RelationalQueryTranslationPostprocessorDependencies diff --git a/src/EFCore.Relational/Query/RelationalQueryTranslationPreprocessorDependencies.cs b/src/EFCore.Relational/Query/RelationalQueryTranslationPreprocessorDependencies.cs index b0b03a35695..2eabfd0ef8a 100644 --- a/src/EFCore.Relational/Query/RelationalQueryTranslationPreprocessorDependencies.cs +++ b/src/EFCore.Relational/Query/RelationalQueryTranslationPreprocessorDependencies.cs @@ -23,9 +23,10 @@ namespace Microsoft.EntityFrameworkCore.Query /// services using the 'With...' methods. Do not call the constructor at any point in this process. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public sealed record RelationalQueryTranslationPreprocessorDependencies diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 6ec15cc776b..e3e23e70aa4 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -21,7 +21,7 @@ namespace Microsoft.EntityFrameworkCore.Query public class RelationalQueryableMethodTranslatingExpressionVisitor : QueryableMethodTranslatingExpressionVisitor { private readonly RelationalSqlTranslatingExpressionVisitor _sqlTranslator; - private readonly WeakEntityExpandingExpressionVisitor _weakEntityExpandingExpressionVisitor; + private readonly SharedTypeEntityExpandingExpressionVisitor _sharedTypeEntityExpandingExpressionVisitor; private readonly RelationalProjectionBindingExpressionVisitor _projectionBindingExpressionVisitor; private readonly QueryCompilationContext _queryCompilationContext; private readonly ISqlExpressionFactory _sqlExpressionFactory; @@ -48,7 +48,7 @@ public RelationalQueryableMethodTranslatingExpressionVisitor( var sqlExpressionFactory = relationalDependencies.SqlExpressionFactory; _queryCompilationContext = queryCompilationContext; _sqlTranslator = relationalDependencies.RelationalSqlTranslatingExpressionVisitorFactory.Create(queryCompilationContext, this); - _weakEntityExpandingExpressionVisitor = new WeakEntityExpandingExpressionVisitor(_sqlTranslator, sqlExpressionFactory); + _sharedTypeEntityExpandingExpressionVisitor = new SharedTypeEntityExpandingExpressionVisitor(_sqlTranslator, sqlExpressionFactory); _projectionBindingExpressionVisitor = new RelationalProjectionBindingExpressionVisitor(this, _sqlTranslator); _sqlExpressionFactory = sqlExpressionFactory; _subquery = false; @@ -71,8 +71,8 @@ protected RelationalQueryableMethodTranslatingExpressionVisitor( _queryCompilationContext = parentVisitor._queryCompilationContext; _sqlTranslator = RelationalDependencies.RelationalSqlTranslatingExpressionVisitorFactory.Create( parentVisitor._queryCompilationContext, parentVisitor); - _weakEntityExpandingExpressionVisitor = - new WeakEntityExpandingExpressionVisitor(_sqlTranslator, parentVisitor._sqlExpressionFactory); + _sharedTypeEntityExpandingExpressionVisitor = + new SharedTypeEntityExpandingExpressionVisitor(_sqlTranslator, parentVisitor._sqlExpressionFactory); _projectionBindingExpressionVisitor = new RelationalProjectionBindingExpressionVisitor(this, _sqlTranslator); _sqlExpressionFactory = parentVisitor._sqlExpressionFactory; _subquery = true; @@ -489,7 +489,7 @@ private static ShapedQueryExpression CreateShapedQueryExpression(IEntityType ent new[] { translatedKey, groupByShaper }) .Visit(resultSelector.Body); - newResultSelectorBody = ExpandWeakEntities(selectExpression, newResultSelectorBody); + newResultSelectorBody = ExpandSharedTypeEntities(selectExpression, newResultSelectorBody); return source.UpdateShaperExpression( _projectionBindingExpressionVisitor.Translate(selectExpression, newResultSelectorBody)); @@ -1163,13 +1163,13 @@ private Expression RemapLambdaBody(ShapedQueryExpression shapedQueryExpression, var lambdaBody = ReplacingExpressionVisitor.Replace( lambdaExpression.Parameters.Single(), shapedQueryExpression.ShaperExpression, lambdaExpression.Body); - return ExpandWeakEntities((SelectExpression)shapedQueryExpression.QueryExpression, lambdaBody); + return ExpandSharedTypeEntities((SelectExpression)shapedQueryExpression.QueryExpression, lambdaBody); } - internal Expression ExpandWeakEntities(SelectExpression selectExpression, Expression lambdaBody) - => _weakEntityExpandingExpressionVisitor.Expand(selectExpression, lambdaBody); + internal Expression ExpandSharedTypeEntities(SelectExpression selectExpression, Expression lambdaBody) + => _sharedTypeEntityExpandingExpressionVisitor.Expand(selectExpression, lambdaBody); - private sealed class WeakEntityExpandingExpressionVisitor : ExpressionVisitor + private sealed class SharedTypeEntityExpandingExpressionVisitor : ExpressionVisitor { private static readonly MethodInfo _objectEqualsMethodInfo = typeof(object).GetRequiredRuntimeMethod(nameof(object.Equals), new[] { typeof(object), typeof(object) }); @@ -1179,7 +1179,7 @@ private static readonly MethodInfo _objectEqualsMethodInfo private SelectExpression _selectExpression; - public WeakEntityExpandingExpressionVisitor( + public SharedTypeEntityExpandingExpressionVisitor( RelationalSqlTranslatingExpressionVisitor sqlTranslator, ISqlExpressionFactory sqlExpressionFactory) { diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitorDependencies.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitorDependencies.cs index 8bfdecff6df..06ae3c4615c 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitorDependencies.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitorDependencies.cs @@ -24,9 +24,10 @@ namespace Microsoft.EntityFrameworkCore.Query /// services using the 'With...' methods. Do not call the constructor at any point in this process. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public sealed record RelationalQueryableMethodTranslatingExpressionVisitorDependencies diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index 7e94cd20ef5..5b55164b4c2 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -1050,7 +1050,7 @@ protected override Expression VisitUnary(UnaryExpression unaryExpression) // Introduce explicit cast only if the target type is mapped else we need to client eval if (unaryExpression.Type == typeof(object) - || Dependencies.TypeMappingSource.FindMapping(unaryExpression.Type) != null) + || Dependencies.Model.FindMapping(unaryExpression.Type) != null) { sqlOperand = _sqlExpressionFactory.ApplyDefaultTypeMapping(sqlOperand); diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitorDependencies.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitorDependencies.cs index 0a605952f28..747cff90098 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitorDependencies.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitorDependencies.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.Extensions.DependencyInjection; @@ -25,9 +26,10 @@ namespace Microsoft.EntityFrameworkCore.Query /// services using the 'With...' methods. Do not call the constructor at any point in this process. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public sealed record RelationalSqlTranslatingExpressionVisitorDependencies @@ -54,17 +56,22 @@ public sealed record RelationalSqlTranslatingExpressionVisitorDependencies [EntityFrameworkInternal] public RelationalSqlTranslatingExpressionVisitorDependencies( ISqlExpressionFactory sqlExpressionFactory, + IModel model, IRelationalTypeMappingSource typeMappingSource, IMemberTranslatorProvider memberTranslatorProvider, IMethodCallTranslatorProvider methodCallTranslatorProvider) { Check.NotNull(sqlExpressionFactory, nameof(sqlExpressionFactory)); + Check.NotNull(model, nameof(model)); Check.NotNull(typeMappingSource, nameof(typeMappingSource)); Check.NotNull(memberTranslatorProvider, nameof(memberTranslatorProvider)); Check.NotNull(methodCallTranslatorProvider, nameof(methodCallTranslatorProvider)); SqlExpressionFactory = sqlExpressionFactory; + Model = model; +#pragma warning disable CS0618 // Type or member is obsolete TypeMappingSource = typeMappingSource; +#pragma warning restore CS0618 // Type or member is obsolete MemberTranslatorProvider = memberTranslatorProvider; MethodCallTranslatorProvider = methodCallTranslatorProvider; } @@ -74,6 +81,11 @@ public RelationalSqlTranslatingExpressionVisitorDependencies( /// public ISqlExpressionFactory SqlExpressionFactory { get; init; } + /// + /// The expression factory. + /// + public IModel Model { get; init; } + /// /// The relational type mapping souce. /// diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs index 379cd0c01fe..a34a891a865 100644 --- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs @@ -1,10 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -19,7 +16,7 @@ namespace Microsoft.EntityFrameworkCore.Query /// public class SqlExpressionFactory : ISqlExpressionFactory { - private readonly IRelationalTypeMappingSource _typeMappingSource; + private readonly IModel _model; private readonly RelationalTypeMapping _boolTypeMapping; /// @@ -31,8 +28,8 @@ public SqlExpressionFactory(SqlExpressionFactoryDependencies dependencies) Check.NotNull(dependencies, nameof(dependencies)); Dependencies = dependencies; - _typeMappingSource = dependencies.TypeMappingSource; - _boolTypeMapping = _typeMappingSource.FindMapping(typeof(bool))!; + _model = dependencies.Model; + _boolTypeMapping = (RelationalTypeMapping)_model.FindMapping(typeof(bool))!; } /// @@ -51,7 +48,7 @@ public SqlExpressionFactory(SqlExpressionFactoryDependencies dependencies) && sqlUnaryExpression.OperatorType == ExpressionType.Convert && sqlUnaryExpression.Type == typeof(object) ? sqlUnaryExpression.Operand - : ApplyTypeMapping(sqlExpression, _typeMappingSource.FindMapping(sqlExpression.Type)); + : ApplyTypeMapping(sqlExpression, (RelationalTypeMapping?)_model.FindMapping(sqlExpression.Type)); } /// @@ -89,7 +86,7 @@ private SqlExpression ApplyTypeMappingOnLike(LikeExpression likeExpression) likeExpression.Match, likeExpression.Pattern) : ExpressionExtensions.InferTypeMapping( likeExpression.Match, likeExpression.Pattern, likeExpression.EscapeChar)) - ?? _typeMappingSource.FindMapping(likeExpression.Match.Type); + ?? (RelationalTypeMapping?)_model.FindMapping(likeExpression.Match.Type); return new LikeExpression( ApplyTypeMapping(likeExpression.Match, inferredTypeMapping), @@ -192,8 +189,8 @@ private SqlExpression ApplyTypeMappingOnSqlBinary( inferredTypeMapping = ExpressionExtensions.InferTypeMapping(left, right) // We avoid object here since the result does not get typeMapping from outside. ?? (left.Type != typeof(object) - ? _typeMappingSource.FindMapping(left.Type) - : _typeMappingSource.FindMapping(right.Type)); + ? (RelationalTypeMapping?)_model.FindMapping(left.Type) + : (RelationalTypeMapping?)_model.FindMapping(right.Type)); resultType = typeof(bool); resultTypeMapping = _boolTypeMapping; break; @@ -414,7 +411,7 @@ public virtual SqlFunctionExpression Coalesce(SqlExpression left, SqlExpression var resultType = right.Type; var inferredTypeMapping = typeMapping ?? ExpressionExtensions.InferTypeMapping(left, right) - ?? _typeMappingSource.FindMapping(resultType); + ?? (RelationalTypeMapping?)_model.FindMapping(resultType); var typeMappedArguments = new List { @@ -509,7 +506,7 @@ public virtual CaseExpression Case(SqlExpression? operand, IReadOnlyList wc.Test.Type)) - .Where(t => t != typeof(object)).Select(t => _typeMappingSource.FindMapping(t)).FirstOrDefault(); + .Where(t => t != typeof(object)).Select(t => (RelationalTypeMapping?)_model.FindMapping(t)).FirstOrDefault(); var resultTypeMapping = elseResult?.TypeMapping ?? whenClauses.Select(wc => wc.Result.TypeMapping).FirstOrDefault(t => t != null); @@ -751,7 +748,7 @@ public virtual InExpression In(SqlExpression item, SqlExpression values, bool ne Check.NotNull(item, nameof(item)); Check.NotNull(values, nameof(values)); - var typeMapping = item.TypeMapping ?? _typeMappingSource.FindMapping(item.Type); + var typeMapping = item.TypeMapping ?? (RelationalTypeMapping?)_model.FindMapping(item.Type); item = ApplyTypeMapping(item, typeMapping); values = ApplyTypeMapping(values, typeMapping); @@ -981,11 +978,11 @@ private SqlExpression IsNotNull(IProperty property, EntityProjectionExpression e /// [Obsolete("Use IRelationalTypeMappingSource directly.")] public virtual RelationalTypeMapping GetTypeMappingForValue(object? value) - => _typeMappingSource.GetMappingForValue(value); + => _model.GetMappingForValue(value); /// [Obsolete("Use IRelationalTypeMappingSource directly.")] public virtual RelationalTypeMapping? FindMapping(Type type) - => _typeMappingSource.FindMapping(Check.NotNull(type, nameof(type))); + => (RelationalTypeMapping?)_model.FindMapping(Check.NotNull(type, nameof(type))); } } diff --git a/src/EFCore.Relational/Query/SqlExpressionFactoryDependencies.cs b/src/EFCore.Relational/Query/SqlExpressionFactoryDependencies.cs index 0687705f193..10c7b381ede 100644 --- a/src/EFCore.Relational/Query/SqlExpressionFactoryDependencies.cs +++ b/src/EFCore.Relational/Query/SqlExpressionFactoryDependencies.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.Extensions.DependencyInjection; @@ -25,9 +26,10 @@ namespace Microsoft.EntityFrameworkCore.Query /// services using the 'With...' methods. Do not call the constructor at any point in this process. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public sealed record SqlExpressionFactoryDependencies @@ -52,16 +54,26 @@ public sealed record SqlExpressionFactoryDependencies /// /// [EntityFrameworkInternal] - public SqlExpressionFactoryDependencies(IRelationalTypeMappingSource typeMappingSource) + public SqlExpressionFactoryDependencies(IModel model, IRelationalTypeMappingSource typeMappingSource) { + Check.NotNull(model, nameof(model)); Check.NotNull(typeMappingSource, nameof(typeMappingSource)); + Model = model; +#pragma warning disable CS0618 // Type or member is obsolete TypeMappingSource = typeMappingSource; +#pragma warning restore CS0618 // Type or member is obsolete } /// /// The type mapping source. /// + [Obsolete("Use Model instead")] public IRelationalTypeMappingSource TypeMappingSource { get; init; } + + /// + /// The type mapping source. + /// + public IModel Model { get; init; } } } diff --git a/src/EFCore.Relational/Storage/RelationalCommandParameterObject.cs b/src/EFCore.Relational/Storage/RelationalCommandParameterObject.cs index 7104728c515..d7ef85e81f6 100644 --- a/src/EFCore.Relational/Storage/RelationalCommandParameterObject.cs +++ b/src/EFCore.Relational/Storage/RelationalCommandParameterObject.cs @@ -89,8 +89,8 @@ public RelationalCommandParameterObject( IReadOnlyList? readerColumns, DbContext? context, IRelationalCommandDiagnosticsLogger? logger, - bool detailedErrorsEnabled) : this(connection, parameterValues, readerColumns, context, - logger, detailedErrorsEnabled, CommandSource.Unknown) + bool detailedErrorsEnabled) + : this(connection, parameterValues, readerColumns, context, logger, detailedErrorsEnabled, CommandSource.Unknown) { } diff --git a/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs b/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs index 74cdd55d1a4..cf1dde32d31 100644 --- a/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs +++ b/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs @@ -357,6 +357,11 @@ protected override CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo) customConverter = firstConfiguration?.ClrType == type ? firstConfiguration.GetValueConverter() : null; + + if (customConverter != null) + { + mappingInfo = mappingInfo with { ClrType = customConverter.ProviderClrType }; + } } return FindMappingWithConversion(mappingInfo, providerClrType, customConverter); @@ -489,7 +494,7 @@ protected override CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo) /// RelationalTypeMapping? IRelationalTypeMappingSource.FindMapping(Type type, IModel model) - => (RelationalTypeMapping?)FindMapping(type); + => (RelationalTypeMapping?)FindMapping(type, model); /// RelationalTypeMapping? IRelationalTypeMappingSource.FindMapping(MemberInfo member) diff --git a/src/EFCore.Relational/Storage/RelationalTypeMappingSourceExtensions.cs b/src/EFCore.Relational/Storage/RelationalTypeMappingSourceExtensions.cs index 9cf0eb90579..5c103edc42b 100644 --- a/src/EFCore.Relational/Storage/RelationalTypeMappingSourceExtensions.cs +++ b/src/EFCore.Relational/Storage/RelationalTypeMappingSourceExtensions.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; diff --git a/src/EFCore.SqlServer.NTS/Extensions/SqlServerNetTopologySuiteServiceCollectionExtensions.cs b/src/EFCore.SqlServer.NTS/Extensions/SqlServerNetTopologySuiteServiceCollectionExtensions.cs index 726d631a1a5..620c2c195d9 100644 --- a/src/EFCore.SqlServer.NTS/Extensions/SqlServerNetTopologySuiteServiceCollectionExtensions.cs +++ b/src/EFCore.SqlServer.NTS/Extensions/SqlServerNetTopologySuiteServiceCollectionExtensions.cs @@ -30,10 +30,9 @@ public static IServiceCollection AddEntityFrameworkSqlServerNetTopologySuite( serviceCollection.TryAddSingleton(NtsGeometryServices.Instance); new EntityFrameworkRelationalServicesBuilder(serviceCollection) - .TryAddProviderSpecificServices( - x => x.TryAddSingletonEnumerable() - .TryAddSingletonEnumerable() - .TryAddSingletonEnumerable()); + .TryAdd() + .TryAdd() + .TryAdd(); return serviceCollection; } diff --git a/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerNetTopologySuiteMemberTranslatorPlugin.cs b/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerNetTopologySuiteMemberTranslatorPlugin.cs index db2e3395a15..50bb2c6001a 100644 --- a/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerNetTopologySuiteMemberTranslatorPlugin.cs +++ b/src/EFCore.SqlServer.NTS/Query/Internal/SqlServerNetTopologySuiteMemberTranslatorPlugin.cs @@ -1,10 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Generic; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.Extensions.DependencyInjection; namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal { @@ -15,11 +13,6 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . - /// /// public class SqlServerNetTopologySuiteMemberTranslatorPlugin : IMemberTranslatorPlugin { diff --git a/src/EFCore.Sqlite.NTS/Extensions/SqliteNetTopologySuiteServiceCollectionExtensions.cs b/src/EFCore.Sqlite.NTS/Extensions/SqliteNetTopologySuiteServiceCollectionExtensions.cs index bd6a8ab5d89..7e9b3f23692 100644 --- a/src/EFCore.Sqlite.NTS/Extensions/SqliteNetTopologySuiteServiceCollectionExtensions.cs +++ b/src/EFCore.Sqlite.NTS/Extensions/SqliteNetTopologySuiteServiceCollectionExtensions.cs @@ -31,10 +31,9 @@ public static IServiceCollection AddEntityFrameworkSqliteNetTopologySuite( serviceCollection.TryAddSingleton(NtsGeometryServices.Instance); new EntityFrameworkRelationalServicesBuilder(serviceCollection) - .TryAddProviderSpecificServices( - x => x.TryAddSingletonEnumerable() - .TryAddSingletonEnumerable() - .TryAddSingletonEnumerable()); + .TryAdd() + .TryAdd() + .TryAdd(); return serviceCollection; } diff --git a/src/EFCore.Sqlite.NTS/Query/Internal/SqliteNetTopologySuiteMemberTranslatorPlugin.cs b/src/EFCore.Sqlite.NTS/Query/Internal/SqliteNetTopologySuiteMemberTranslatorPlugin.cs index 0c1defb1ac2..f845dc838dc 100644 --- a/src/EFCore.Sqlite.NTS/Query/Internal/SqliteNetTopologySuiteMemberTranslatorPlugin.cs +++ b/src/EFCore.Sqlite.NTS/Query/Internal/SqliteNetTopologySuiteMemberTranslatorPlugin.cs @@ -1,9 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Generic; using Microsoft.EntityFrameworkCore.Query; -using Microsoft.Extensions.DependencyInjection; namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal { @@ -14,11 +12,6 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . - /// /// public class SqliteNetTopologySuiteMemberTranslatorPlugin : IMemberTranslatorPlugin { diff --git a/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs b/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs index 8651f54f4ce..7b7104cc5ec 100644 --- a/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs +++ b/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs @@ -94,10 +94,6 @@ public static readonly IDictionary CoreServices { typeof(IMemberClassifier), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IMemoryCache), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IEvaluatableExpressionFilter), new ServiceCharacteristics(ServiceLifetime.Singleton) }, - { typeof(IQueryTranslationPreprocessorFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, - { typeof(IQueryableMethodTranslatingExpressionVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, - { typeof(IQueryTranslationPostprocessorFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, - { typeof(IShapedQueryCompilingExpressionVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(INavigationExpansionExtensibilityHelper), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IProviderConventionSetBuilder), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IConventionSetBuilder), new ServiceCharacteristics(ServiceLifetime.Scoped) }, @@ -132,6 +128,10 @@ public static readonly IDictionary CoreServices { typeof(IDbContextTransactionManager), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IQueryContextFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IQueryCompilationContextFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, + { typeof(IQueryableMethodTranslatingExpressionVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, + { typeof(IQueryTranslationPreprocessorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, + { typeof(IQueryTranslationPostprocessorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, + { typeof(IShapedQueryCompilingExpressionVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IDbContextLogger), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(ILazyLoader), new ServiceCharacteristics(ServiceLifetime.Transient) }, { @@ -295,9 +295,6 @@ public virtual EntityFrameworkServicesBuilder TryAddCoreServices() .AddDependencySingleton() .AddDependencySingleton() .AddDependencySingleton() - .AddDependencySingleton() - .AddDependencySingleton() - .AddDependencySingleton() .AddDependencySingleton() .AddDependencySingleton() .AddDependencySingleton() @@ -308,6 +305,9 @@ public virtual EntityFrameworkServicesBuilder TryAddCoreServices() .AddDependencyScoped() .AddDependencyScoped() .AddDependencyScoped() + .AddDependencyScoped() + .AddDependencyScoped() + .AddDependencyScoped() .AddDependencyScoped() .AddDependencyScoped() .AddDependencyScoped() diff --git a/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs index 09452946f1b..e81995ef07d 100644 --- a/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs @@ -65,10 +65,10 @@ public virtual void ProcessEntityTypeBaseTypeChanged( private void Process(IConventionEntityTypeBuilder entityTypeBuilder) { var entityType = entityTypeBuilder.Metadata; - var configuration = ((Model)entityType.Model).Configuration; + var model = entityType.Model; foreach (var propertyInfo in entityType.GetRuntimeProperties().Values) { - if (!Dependencies.MemberClassifier.IsCandidatePrimitiveProperty(propertyInfo, configuration)) + if (!Dependencies.MemberClassifier.IsCandidatePrimitiveProperty(propertyInfo, model)) { continue; } diff --git a/src/EFCore/Metadata/Conventions/ServicePropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/ServicePropertyDiscoveryConvention.cs index 2c492258669..7ba1c10b6e0 100644 --- a/src/EFCore/Metadata/Conventions/ServicePropertyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/ServicePropertyDiscoveryConvention.cs @@ -61,7 +61,7 @@ public virtual void ProcessEntityTypeBaseTypeChanged( private void Process(IConventionEntityTypeBuilder entityTypeBuilder) { var entityType = entityTypeBuilder.Metadata; - var configuration = ((Model)entityType.Model).Configuration; + var model = entityType.Model; foreach (var propertyInfo in entityType.GetRuntimeProperties().Values) { if (!entityTypeBuilder.CanHaveServiceProperty(propertyInfo)) @@ -69,7 +69,7 @@ private void Process(IConventionEntityTypeBuilder entityTypeBuilder) continue; } - var factory = Dependencies.MemberClassifier.FindServicePropertyCandidateBindingFactory(propertyInfo, configuration); + var factory = Dependencies.MemberClassifier.FindServicePropertyCandidateBindingFactory(propertyInfo, model); if (factory == null) { continue; diff --git a/src/EFCore/Metadata/Internal/IMemberClassifier.cs b/src/EFCore/Metadata/Internal/IMemberClassifier.cs index 5acdaf3bd73..c191a32d584 100644 --- a/src/EFCore/Metadata/Internal/IMemberClassifier.cs +++ b/src/EFCore/Metadata/Internal/IMemberClassifier.cs @@ -45,7 +45,7 @@ public interface IMemberClassifier /// 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. /// - bool IsCandidatePrimitiveProperty(PropertyInfo propertyInfo, ModelConfiguration? configuration); + bool IsCandidatePrimitiveProperty(PropertyInfo propertyInfo, IConventionModel model); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -53,6 +53,6 @@ public interface IMemberClassifier /// 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. /// - IParameterBindingFactory? FindServicePropertyCandidateBindingFactory(PropertyInfo propertyInfo, ModelConfiguration? configuration); + IParameterBindingFactory? FindServicePropertyCandidateBindingFactory(PropertyInfo propertyInfo, IConventionModel model); } } diff --git a/src/EFCore/Metadata/Internal/MemberClassifier.cs b/src/EFCore/Metadata/Internal/MemberClassifier.cs index 85e569a2843..f2ac9959d8a 100644 --- a/src/EFCore/Metadata/Internal/MemberClassifier.cs +++ b/src/EFCore/Metadata/Internal/MemberClassifier.cs @@ -65,10 +65,9 @@ public MemberClassifier( var dictionaryBuilder = ImmutableSortedDictionary.CreateBuilder( MemberInfoNameComparer.Instance); - var configuration = ((Model)entityType.Model).Configuration; foreach (var propertyInfo in entityType.GetRuntimeProperties().Values) { - var targetType = FindCandidateNavigationPropertyType(propertyInfo, configuration, out var shouldBeOwned); + var targetType = FindCandidateNavigationPropertyType(propertyInfo, entityType.Model, out var shouldBeOwned); if (targetType != null) { dictionaryBuilder[propertyInfo] = (targetType, shouldBeOwned); @@ -94,11 +93,7 @@ public MemberClassifier( /// public virtual Type? FindCandidateNavigationPropertyType( MemberInfo memberInfo, IConventionModel model, out bool? shouldBeOwned) - => FindCandidateNavigationPropertyType(memberInfo, ((Model)model).Configuration, out shouldBeOwned); - - private Type? FindCandidateNavigationPropertyType( - MemberInfo memberInfo, ModelConfiguration? configuration, out bool? shouldBeOwned) - { + { shouldBeOwned = null; var propertyInfo = memberInfo as PropertyInfo; var targetType = memberInfo.GetMemberType(); @@ -106,19 +101,20 @@ public MemberClassifier( return targetSequenceType != null && (propertyInfo == null || propertyInfo.IsCandidateProperty(needsWrite: false)) - && IsCandidateNavigationPropertyType(targetSequenceType, memberInfo, configuration, out shouldBeOwned) + && IsCandidateNavigationPropertyType(targetSequenceType, memberInfo, (Model)model, out shouldBeOwned) ? targetSequenceType : (propertyInfo == null || propertyInfo.IsCandidateProperty(needsWrite: true)) - && IsCandidateNavigationPropertyType(targetType, memberInfo, configuration, out shouldBeOwned) + && IsCandidateNavigationPropertyType(targetType, memberInfo, (Model)model, out shouldBeOwned) ? targetType : null; } private bool IsCandidateNavigationPropertyType( - Type targetType, MemberInfo memberInfo, ModelConfiguration? configuration, out bool? shouldBeOwned) + Type targetType, MemberInfo memberInfo, Model model, out bool? shouldBeOwned) { shouldBeOwned = null; + var configuration = model.Configuration; var configurationType = configuration?.GetConfigurationType(targetType); var isConfiguredAsEntityType = configurationType.IsEntityType(); if (isConfiguredAsEntityType == false @@ -135,7 +131,7 @@ private bool IsCandidateNavigationPropertyType( return isConfiguredAsEntityType == true || (targetType != typeof(object) && _parameterBindingFactories.FindFactory(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName()) == null - && _typeMappingSource.FindMapping(targetType) == null); + && _typeMappingSource.FindMapping(targetType, model) == null); } /// @@ -144,14 +140,14 @@ private bool IsCandidateNavigationPropertyType( /// 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 IsCandidatePrimitiveProperty(PropertyInfo propertyInfo, ModelConfiguration? configuration) + public virtual bool IsCandidatePrimitiveProperty(PropertyInfo propertyInfo, IConventionModel model) { if (!propertyInfo.IsCandidateProperty()) { return false; } - var configurationType = configuration?.GetConfigurationType(propertyInfo.PropertyType); + var configurationType = ((Model)model).Configuration?.GetConfigurationType(propertyInfo.PropertyType); return configurationType == TypeConfigurationType.Property || (configurationType == null && _typeMappingSource.FindMapping(propertyInfo) != null); } @@ -163,7 +159,7 @@ public virtual bool IsCandidatePrimitiveProperty(PropertyInfo propertyInfo, Mode /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IParameterBindingFactory? FindServicePropertyCandidateBindingFactory( - PropertyInfo propertyInfo, ModelConfiguration? configuration) + PropertyInfo propertyInfo, IConventionModel model) { if (!propertyInfo.IsCandidateProperty(publicOnly: false)) { @@ -171,7 +167,7 @@ public virtual bool IsCandidatePrimitiveProperty(PropertyInfo propertyInfo, Mode } var type = propertyInfo.PropertyType; - var configurationType = configuration?.GetConfigurationType(type); + var configurationType = ((Model)model).Configuration?.GetConfigurationType(type); if (configurationType != TypeConfigurationType.ServiceProperty) { if (configurationType != null) @@ -180,7 +176,7 @@ public virtual bool IsCandidatePrimitiveProperty(PropertyInfo propertyInfo, Mode } if (propertyInfo.IsCandidateProperty() - && _typeMappingSource.FindMapping(propertyInfo) != null) + && _typeMappingSource.FindMapping(propertyInfo.GetMemberType(), (IModel)model) != null) { return null; } diff --git a/src/EFCore/Query/IQueryTranslationPostprocessorFactory.cs b/src/EFCore/Query/IQueryTranslationPostprocessorFactory.cs index a1a4514719a..839c241dd49 100644 --- a/src/EFCore/Query/IQueryTranslationPostprocessorFactory.cs +++ b/src/EFCore/Query/IQueryTranslationPostprocessorFactory.cs @@ -10,9 +10,10 @@ namespace Microsoft.EntityFrameworkCore.Query /// A factory for creating instances. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public interface IQueryTranslationPostprocessorFactory diff --git a/src/EFCore/Query/IQueryTranslationPreprocessorFactory.cs b/src/EFCore/Query/IQueryTranslationPreprocessorFactory.cs index a547ddf7a89..7e5ace75815 100644 --- a/src/EFCore/Query/IQueryTranslationPreprocessorFactory.cs +++ b/src/EFCore/Query/IQueryTranslationPreprocessorFactory.cs @@ -10,9 +10,10 @@ namespace Microsoft.EntityFrameworkCore.Query /// A factory for creating instances. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public interface IQueryTranslationPreprocessorFactory diff --git a/src/EFCore/Query/IShapedQueryCompilingExpressionVisitorFactory.cs b/src/EFCore/Query/IShapedQueryCompilingExpressionVisitorFactory.cs index c696f82d73c..71a63491525 100644 --- a/src/EFCore/Query/IShapedQueryCompilingExpressionVisitorFactory.cs +++ b/src/EFCore/Query/IShapedQueryCompilingExpressionVisitorFactory.cs @@ -10,9 +10,10 @@ namespace Microsoft.EntityFrameworkCore.Query /// A factory for creating instances. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public interface IShapedQueryCompilingExpressionVisitorFactory diff --git a/src/EFCore/Query/QueryTranslationPostprocessorDependencies.cs b/src/EFCore/Query/QueryTranslationPostprocessorDependencies.cs index 179da5cca4b..aecf2df8e5b 100644 --- a/src/EFCore/Query/QueryTranslationPostprocessorDependencies.cs +++ b/src/EFCore/Query/QueryTranslationPostprocessorDependencies.cs @@ -23,9 +23,10 @@ namespace Microsoft.EntityFrameworkCore.Query /// services using the 'With...' methods. Do not call the constructor at any point in this process. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public sealed record QueryTranslationPostprocessorDependencies diff --git a/src/EFCore/Query/QueryTranslationPreprocessorDependencies.cs b/src/EFCore/Query/QueryTranslationPreprocessorDependencies.cs index fc64be4cddf..2d14c7c4e13 100644 --- a/src/EFCore/Query/QueryTranslationPreprocessorDependencies.cs +++ b/src/EFCore/Query/QueryTranslationPreprocessorDependencies.cs @@ -24,9 +24,10 @@ namespace Microsoft.EntityFrameworkCore.Query /// services using the 'With...' methods. Do not call the constructor at any point in this process. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public sealed record QueryTranslationPreprocessorDependencies diff --git a/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitorDependencies.cs b/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitorDependencies.cs index 3789e64f9d6..db33336b0da 100644 --- a/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitorDependencies.cs +++ b/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitorDependencies.cs @@ -23,9 +23,10 @@ namespace Microsoft.EntityFrameworkCore.Query /// services using the 'With...' methods. Do not call the constructor at any point in this process. /// /// - /// The service lifetime is . This means a single instance - /// is used by many instances. The implementation must be thread-safe. - /// This service cannot depend on services registered as . + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. /// /// public sealed record QueryableMethodTranslatingExpressionVisitorDependencies diff --git a/src/EFCore/Storage/TypeMappingSource.cs b/src/EFCore/Storage/TypeMappingSource.cs index 8ea1c77f792..d368d5bb805 100644 --- a/src/EFCore/Storage/TypeMappingSource.cs +++ b/src/EFCore/Storage/TypeMappingSource.cs @@ -242,6 +242,11 @@ protected TypeMappingSource(TypeMappingSourceDependencies dependencies) customConverter = firstConfiguration?.ClrType == type ? firstConfiguration.GetValueConverter() : null; + + if (customConverter != null) + { + mappingInfo = mappingInfo with { ClrType = customConverter.ProviderClrType }; + } } return FindMappingWithConversion(mappingInfo, providerClrType, customConverter); diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs index 22d16479617..f00d8ee189d 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs @@ -875,6 +875,16 @@ FROM root c WHERE ((c[""Discriminator""] = ""Customer"") AND (c[""City""] = ""London""))"); } + public override async Task Where_equals_method_string_with_ignore_case(bool async) + { + await base.Where_equals_method_string_with_ignore_case(async); + + AssertSql( + @"SELECT c +FROM root c +WHERE ((c[""Discriminator""] = ""Customer"") AND STRINGEQUALS(c[""City""], ""London"", true))"); + } + public override async Task Where_equals_method_int(bool async) { await base.Where_equals_method_int(async); @@ -2173,16 +2183,6 @@ FROM root c WHERE ((c[""Discriminator""] = ""Order"") AND c[""OrderID""] IN (10248, 10249))"); } - public override async Task Where_equals_method_string_with_ignore_case(bool async) - { - await base.Where_equals_method_string_with_ignore_case(async); - - AssertSql( - @"SELECT c -FROM root c -WHERE ((c[""Discriminator""] = ""Customer"") AND STRINGEQUALS(c[""City""], ""London"", true))"); - } - public override async Task Filter_with_EF_Property_using_closure_for_property_name(bool async) { await base.Filter_with_EF_Property_using_closure_for_property_name(async); diff --git a/test/EFCore.Design.Tests/Design/DesignTimeServicesTest.cs b/test/EFCore.Design.Tests/Design/DesignTimeServicesTest.cs index 744b9e72738..133ba044bd2 100644 --- a/test/EFCore.Design.Tests/Design/DesignTimeServicesTest.cs +++ b/test/EFCore.Design.Tests/Design/DesignTimeServicesTest.cs @@ -1,13 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; using System.Data.Common; -using System.Linq; using System.Reflection; -using System.Threading; -using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Scaffolding; @@ -113,7 +108,7 @@ public class TryAddDesignTimeServices : IDesignTimeServices { public virtual void ConfigureDesignTimeServices(IServiceCollection serviceCollection) { - serviceCollection.TryAddSingleton(); + serviceCollection.TryAddScoped(); serviceCollection.TryAddSingleton(); serviceCollection.TryAddSingleton(); serviceCollection.TryAddSingleton(); diff --git a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs index 9ba042a255e..c4f6fb72df5 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs @@ -74,6 +74,8 @@ public void Can_resolve_ISnapshotModelProcessor_from_DI() var assembly = typeof(SnapshotModelProcessorTest).Assembly; var snapshotModelProcessor = new DesignTimeServicesBuilder(assembly, assembly, new TestOperationReporter(), new string[0]) .Build(SqlServerTestHelpers.Instance.CreateContext()) + .CreateScope() + .ServiceProvider .GetRequiredService(); Assert.NotNull(snapshotModelProcessor); diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index 65123ffbe52..4b399ad101c 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -1940,7 +1940,7 @@ partial void Initialize() ""condition"", typeof(string), false, - ""nvarchar(max)""); + ""nvarchar(256)""); functions[""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+DbFunctionContext.GetCount(System.Guid?,string)""] = getCount; @@ -2000,7 +2000,7 @@ partial void Initialize() ""date"", typeof(string), false, - ""nvarchar(max)""); + ""nvarchar(256)""); isDateStatic.AddAnnotation(""MyGuid"", new Guid(""00000000-0000-0000-0000-000000000000"")); functions[""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+DbFunctionContext.IsDateStatic(string)""] = isDateStatic; @@ -2147,12 +2147,12 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) var getCountParameter2 = getCount.Parameters[1]; Assert.Same(getCount, getCountParameter2.Function); Assert.Equal("condition", getCountParameter2.Name); - Assert.Equal("nvarchar(max)", getCountParameter2.StoreType); + Assert.Equal("nvarchar(256)", getCountParameter2.StoreType); Assert.False(getCountParameter2.PropagatesNullability); Assert.Equal(typeof(string), getCountParameter2.ClrType); - Assert.Equal("nvarchar(max)", getCountParameter2.TypeMapping.StoreType); + Assert.Equal("nvarchar(256)", getCountParameter2.TypeMapping.StoreType); Assert.Equal("condition", getCountParameter2.StoreFunctionParameter.Name); - Assert.Equal("nvarchar(max)", getCountParameter2.StoreFunctionParameter.Type); + Assert.Equal("nvarchar(256)", getCountParameter2.StoreFunctionParameter.Type); Assert.NotNull(getCountParameter2.ToString()); var isDate = model.FindDbFunction(typeof(DbFunctionContext).GetMethod("IsDateStatic")); @@ -2176,12 +2176,12 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) var isDateParameter = isDate.Parameters[0]; Assert.Same(isDate, isDateParameter.Function); Assert.Equal("date", isDateParameter.Name); - Assert.Equal("nvarchar(max)", isDateParameter.StoreType); + Assert.Equal("nvarchar(256)", isDateParameter.StoreType); Assert.False(isDateParameter.PropagatesNullability); Assert.Equal(typeof(string), isDateParameter.ClrType); - Assert.Equal("nvarchar(max)", isDateParameter.TypeMapping.StoreType); + Assert.Equal("nvarchar(256)", isDateParameter.TypeMapping.StoreType); Assert.Equal("date", isDateParameter.StoreFunctionParameter.Name); - Assert.Equal("nvarchar(max)", isDateParameter.StoreFunctionParameter.Type); + Assert.Equal("nvarchar(256)", isDateParameter.StoreFunctionParameter.Type); var getData = model.FindDbFunction(typeof(DbFunctionContext) .GetMethod("GetData", new Type[] { typeof(int) })); @@ -2298,6 +2298,11 @@ public IQueryable GetData() return FromExpression(() => GetData()); } + protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) + { + configurationBuilder.Properties().HaveMaxLength(256); + } + protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/ReverseEngineerScaffolderTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/ReverseEngineerScaffolderTest.cs index 08432202f6d..761c0a0403a 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/ReverseEngineerScaffolderTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/ReverseEngineerScaffolderTest.cs @@ -1,10 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Data.Common; using System.Globalization; -using System.IO; using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Internal; @@ -133,7 +131,9 @@ private static IReverseEngineerScaffolder CreateScaffolder() new TestOperationReporter(), new string[0]) .CreateServiceCollection("Microsoft.EntityFrameworkCore.SqlServer") - .BuildServiceProvider() // No scope validation; design services only resolved once + .BuildServiceProvider(validateScopes: true) + .CreateScope() + .ServiceProvider .GetRequiredService(); [ConditionalFact] @@ -148,8 +148,10 @@ public void ScaffoldModel_works_with_named_connection_string() new string[0]) .CreateServiceCollection("Microsoft.EntityFrameworkCore.SqlServer") .AddSingleton(resolver) - .AddSingleton(databaseModelFactory) + .AddScoped(p => databaseModelFactory) .BuildServiceProvider(validateScopes: true) + .CreateScope() + .ServiceProvider .GetRequiredService(); var result = scaffolder.ScaffoldModel( @@ -178,8 +180,10 @@ public void ScaffoldModel_works_with_overridden_connection_string() new string[0]) .CreateServiceCollection("Microsoft.EntityFrameworkCore.SqlServer") .AddSingleton(resolver) - .AddSingleton(databaseModelFactory) + .AddScoped(p => databaseModelFactory) .BuildServiceProvider(validateScopes: true) + .CreateScope() + .ServiceProvider .GetRequiredService(); var result = scaffolder.ScaffoldModel( diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/ReverseEngineeringConfigurationTests.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/ReverseEngineeringConfigurationTests.cs index d69652f7031..802d7c8d76e 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/ReverseEngineeringConfigurationTests.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/ReverseEngineeringConfigurationTests.cs @@ -1,12 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Design.Internal; -using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Internal; -using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -28,6 +24,8 @@ private void ValidateContextNameInReverseEngineerGenerator(string contextName) var assembly = typeof(ReverseEngineeringConfigurationTests).Assembly; var reverseEngineer = new DesignTimeServicesBuilder(assembly, assembly, new TestOperationReporter(), new string[0]) .Build("Microsoft.EntityFrameworkCore.SqlServer") + .CreateScope() + .ServiceProvider .GetRequiredService(); Assert.Equal( diff --git a/test/EFCore.Relational.Specification.Tests/DesignTimeTestBase.cs b/test/EFCore.Relational.Specification.Tests/DesignTimeTestBase.cs index 15a7de3735d..4a76ea4c85d 100644 --- a/test/EFCore.Relational.Specification.Tests/DesignTimeTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/DesignTimeTestBase.cs @@ -33,9 +33,9 @@ public void Can_get_reverse_engineering_services() ProviderAssembly.GetCustomAttribute().TypeName, throwOnError: true))!) .ConfigureDesignTimeServices(serviceCollection); - using var services = serviceCollection.BuildServiceProvider(); // No scope validation; design services only resolved once + using var services = serviceCollection.BuildServiceProvider(validateScopes: true); - var reverseEngineerScaffolder = services.GetService(); + var reverseEngineerScaffolder = services.CreateScope().ServiceProvider.GetService(); Assert.NotNull(reverseEngineerScaffolder); } @@ -52,9 +52,9 @@ public void Can_get_migrations_services() ProviderAssembly.GetCustomAttribute().TypeName, throwOnError: true))!) .ConfigureDesignTimeServices(serviceCollection); - using var services = serviceCollection.BuildServiceProvider(); // No scope validation; design services only resolved once + using var services = serviceCollection.BuildServiceProvider(validateScopes: true); - var migrationsScaffolder = services.GetService(); + var migrationsScaffolder = services.CreateScope().ServiceProvider.GetService(); Assert.NotNull(migrationsScaffolder); } diff --git a/test/EFCore.Specification.Tests/FixtureBase.cs b/test/EFCore.Specification.Tests/FixtureBase.cs index daecea03ab2..a05fddec6ef 100644 --- a/test/EFCore.Specification.Tests/FixtureBase.cs +++ b/test/EFCore.Specification.Tests/FixtureBase.cs @@ -10,7 +10,7 @@ namespace Microsoft.EntityFrameworkCore public abstract class FixtureBase { protected virtual IServiceCollection AddServices(IServiceCollection serviceCollection) - => serviceCollection.AddSingleton(TestModelSource.GetFactory(OnModelCreating)); + => serviceCollection.AddSingleton(TestModelSource.GetFactory(OnModelCreating, ConfigureConventions)); public virtual DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) => builder @@ -20,6 +20,10 @@ public virtual DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builde .Log(CoreEventId.SensitiveDataLoggingEnabledWarning) .Log(CoreEventId.PossibleUnintendedReferenceComparisonWarning)); + protected virtual void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) + { + } + protected virtual void OnModelCreating(ModelBuilder modelBuilder, DbContext context) { } diff --git a/test/EFCore.Specification.Tests/TestUtilities/TestModelSource.cs b/test/EFCore.Specification.Tests/TestUtilities/TestModelSource.cs index 4a09d2164f4..133a6a2b02c 100644 --- a/test/EFCore.Specification.Tests/TestUtilities/TestModelSource.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/TestModelSource.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; @@ -11,11 +10,16 @@ namespace Microsoft.EntityFrameworkCore.TestUtilities { public class TestModelSource : ModelSource { + private readonly Action _configureConventions; private readonly Action _onModelCreating; - private TestModelSource(Action onModelCreating, ModelSourceDependencies dependencies) + private TestModelSource( + Action configureConventions, + Action onModelCreating, + ModelSourceDependencies dependencies) : base(dependencies) { + _configureConventions = configureConventions; _onModelCreating = onModelCreating; } @@ -24,7 +28,9 @@ protected override IModel CreateModel( IConventionSetBuilder conventionSetBuilder, ModelDependencies modelDependencies) { - var modelBuilder = new ModelBuilder(conventionSetBuilder.CreateConventionSet(), modelDependencies); + var modelConfigurationBuilder = new ModelConfigurationBuilder(conventionSetBuilder.CreateConventionSet()); + _configureConventions?.Invoke(modelConfigurationBuilder); + var modelBuilder = modelConfigurationBuilder.CreateModelBuilder(modelDependencies); Dependencies.ModelCustomizer.Customize(modelBuilder, context); @@ -33,13 +39,19 @@ protected override IModel CreateModel( return modelBuilder.FinalizeModel(); } - public static Func GetFactory(Action onModelCreating) + public static Func GetFactory( + Action onModelCreating, + Action configureConventions = null) => p => new TestModelSource( + configureConventions, (mb, c) => onModelCreating(mb), p.GetRequiredService()); - public static Func GetFactory(Action onModelCreating) + public static Func GetFactory( + Action onModelCreating, + Action configureConventions = null) => p => new TestModelSource( + configureConventions, onModelCreating, p.GetRequiredService()); } diff --git a/test/EFCore.Specification.Tests/ValueConvertersEndToEndTestBase.cs b/test/EFCore.Specification.Tests/ValueConvertersEndToEndTestBase.cs index 7d1c6a96725..b43b86fe0a1 100644 --- a/test/EFCore.Specification.Tests/ValueConvertersEndToEndTestBase.cs +++ b/test/EFCore.Specification.Tests/ValueConvertersEndToEndTestBase.cs @@ -840,6 +840,14 @@ public NonNullIntToNullStringConverter() } } + protected class CustomValueComparer : ValueComparer + { + public CustomValueComparer() + : base(false) + { + } + } + protected enum TheExperience : ushort { Jimi, diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs index 22811390d3b..497796e64c8 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -9003,7 +9003,7 @@ public virtual async Task Method_call_translators_are_invoked_for_indexer_if_not { var contextFactory = await InitializeAsync(seed: c => c.Seed(), addServices: c => c.TryAddEnumerable(new ServiceDescriptor( - typeof(IMethodCallTranslatorPlugin), typeof(MyContext23410.JsonMethodCallTranslatorPlugin), ServiceLifetime.Singleton))); + typeof(IMethodCallTranslatorPlugin), typeof(MyContext23410.JsonMethodCallTranslatorPlugin), ServiceLifetime.Scoped))); using (var context = contextFactory.CreateContext()) { diff --git a/test/EFCore.SqlServer.FunctionalTests/ValueConvertersEndToEndSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/ValueConvertersEndToEndSqlServerTest.cs index a6a5fb2d50f..d412cb09e48 100644 --- a/test/EFCore.SqlServer.FunctionalTests/ValueConvertersEndToEndSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/ValueConvertersEndToEndSqlServerTest.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; @@ -160,10 +161,48 @@ public virtual void Properties_with_conversions_map_to_appropriately_null_column Assert.Equal(isNullable, property!.IsNullable); } + [ConditionalFact] + public virtual void Can_use_custom_converters_without_property() + { + Fixture.TestSqlLoggerFactory.Clear(); + + using (var context = CreateContext()) + { + Assert.Empty(context.Set().Where(e => EF.Functions.DataLength((string)(object)new WrappedString { Value = "" }) == 1).ToList()); + } + + Assert.Equal(@"SELECT [c].[Id], [c].[BoolAsChar], [c].[BoolAsInt], [c].[BoolAsNullableChar], [c].[BoolAsNullableInt], [c].[BoolAsNullableString], [c].[BoolAsString], [c].[BytesAsNullableString], [c].[BytesAsString], [c].[CharAsNullableString], [c].[CharAsString], [c].[DateTimeOffsetToBinary], [c].[DateTimeOffsetToNullableBinary], [c].[DateTimeOffsetToNullableString], [c].[DateTimeOffsetToString], [c].[DateTimeToBinary], [c].[DateTimeToNullableBinary], [c].[DateTimeToNullableString], [c].[DateTimeToString], [c].[EnumToNullableNumber], [c].[EnumToNullableString], [c].[EnumToNumber], [c].[EnumToString], [c].[GuidToBytes], [c].[GuidToNullableBytes], [c].[GuidToNullableString], [c].[GuidToString], [c].[IPAddressToBytes], [c].[IPAddressToNullableBytes], [c].[IPAddressToNullableString], [c].[IPAddressToString], [c].[IntAsLong], [c].[IntAsNullableLong], [c].[NonNullIntToNonNullString], [c].[NonNullIntToNullString], [c].[NonNullStringToNullString], [c].[NullIntToNonNullString], [c].[NullIntToNullString], [c].[NullStringToNonNullString], [c].[NullableBoolAsChar], [c].[NullableBoolAsInt], [c].[NullableBoolAsNullableChar], [c].[NullableBoolAsNullableInt], [c].[NullableBoolAsNullableString], [c].[NullableBoolAsString], [c].[NullableBytesAsNullableString], [c].[NullableBytesAsString], [c].[NullableCharAsNullableString], [c].[NullableCharAsString], [c].[NullableDateTimeOffsetToBinary], [c].[NullableDateTimeOffsetToNullableBinary], [c].[NullableDateTimeOffsetToNullableString], [c].[NullableDateTimeOffsetToString], [c].[NullableDateTimeToBinary], [c].[NullableDateTimeToNullableBinary], [c].[NullableDateTimeToNullableString], [c].[NullableDateTimeToString], [c].[NullableEnumToNullableNumber], [c].[NullableEnumToNullableString], [c].[NullableEnumToNumber], [c].[NullableEnumToString], [c].[NullableGuidToBytes], [c].[NullableGuidToNullableBytes], [c].[NullableGuidToNullableString], [c].[NullableGuidToString], [c].[NullableIPAddressToBytes], [c].[NullableIPAddressToNullableBytes], [c].[NullableIPAddressToNullableString], [c].[NullableIPAddressToString], [c].[NullableIntAsLong], [c].[NullableIntAsNullableLong], [c].[NullableNumberToBytes], [c].[NullableNumberToNullableBytes], [c].[NullableNumberToNullableString], [c].[NullableNumberToString], [c].[NullablePhysicalAddressToBytes], [c].[NullablePhysicalAddressToNullableBytes], [c].[NullablePhysicalAddressToNullableString], [c].[NullablePhysicalAddressToString], [c].[NullableStringToBool], [c].[NullableStringToBytes], [c].[NullableStringToChar], [c].[NullableStringToDateTime], [c].[NullableStringToDateTimeOffset], [c].[NullableStringToEnum], [c].[NullableStringToGuid], [c].[NullableStringToNullableBool], [c].[NullableStringToNullableBytes], [c].[NullableStringToNullableChar], [c].[NullableStringToNullableDateTime], [c].[NullableStringToNullableDateTimeOffset], [c].[NullableStringToNullableEnum], [c].[NullableStringToNullableGuid], [c].[NullableStringToNullableNumber], [c].[NullableStringToNullableTimeSpan], [c].[NullableStringToNumber], [c].[NullableStringToTimeSpan], [c].[NullableTimeSpanToNullableString], [c].[NullableTimeSpanToNullableTicks], [c].[NullableTimeSpanToString], [c].[NullableTimeSpanToTicks], [c].[NullableUriToNullableString], [c].[NullableUriToString], [c].[NumberToBytes], [c].[NumberToNullableBytes], [c].[NumberToNullableString], [c].[NumberToString], [c].[PhysicalAddressToBytes], [c].[PhysicalAddressToNullableBytes], [c].[PhysicalAddressToNullableString], [c].[PhysicalAddressToString], [c].[StringToBool], [c].[StringToBytes], [c].[StringToChar], [c].[StringToDateTime], [c].[StringToDateTimeOffset], [c].[StringToEnum], [c].[StringToGuid], [c].[StringToNullableBool], [c].[StringToNullableBytes], [c].[StringToNullableChar], [c].[StringToNullableDateTime], [c].[StringToNullableDateTimeOffset], [c].[StringToNullableEnum], [c].[StringToNullableGuid], [c].[StringToNullableNumber], [c].[StringToNullableTimeSpan], [c].[StringToNumber], [c].[StringToTimeSpan], [c].[TimeSpanToNullableString], [c].[TimeSpanToNullableTicks], [c].[TimeSpanToString], [c].[TimeSpanToTicks], [c].[UriToNullableString], [c].[UriToString] +FROM [ConvertingEntity] AS [c] +WHERE CAST(DATALENGTH(CAST(N'' AS nvarchar(max))) AS int) = 0", Fixture.TestSqlLoggerFactory.SqlStatements[0]); + } + + private struct WrappedString + { + public string Value { get; init; } + } + + private class WrappedStringToStringConverter : ValueConverter + { + public WrappedStringToStringConverter() + : base(v => v.Value, v => new WrappedString { Value = v }) + { + } + } + public class ValueConvertersEndToEndSqlServerFixture : ValueConvertersEndToEndFixtureBase { + protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) + { + base.ConfigureConventions(configurationBuilder); + + configurationBuilder.Properties().HaveConversion>(); + } + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; + + public TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; } } } From e2506493b80f03555be697824b4a9907b1d73c88 Mon Sep 17 00:00:00 2001 From: AndriySvyryd Date: Sat, 14 Aug 2021 11:48:37 -0700 Subject: [PATCH 2/3] Split out unmapped scalars configuration --- .../Query/Internal/ISqlExpressionFactory.cs | 11 +- .../Query/Internal/InExpression.cs | 3 +- .../Query/Internal/SqlExpressionFactory.cs | 58 +++--- .../Design/Internal/DatabaseOperations.cs | 5 +- ...ropertiesConfigurationBuilderExtensions.cs | 2 +- .../Extensions/RelationalModelExtensions.cs | 40 ---- .../ScalarConfigurationBuilderExtensions.cs | 77 ++++++++ ...lExpressionSimplifyingExpressionVisitor.cs | 2 + ...lationalSqlTranslatingExpressionVisitor.cs | 2 +- .../Query/SqlExpressionFactory.cs | 26 ++- .../Query/SqlExpressionFactoryDependencies.cs | 3 - .../Storage/RelationalTypeMappingSource.cs | 123 ++++-------- .../RelationalTypeMappingSourceExtensions.cs | 42 ++++- .../Builders/ScalarConfigurationBuilder.cs | 177 ++++++++++++++++++ .../Builders/ScalarConfigurationBuilder`.cs | 98 ++++++++++ .../Conventions/RuntimeModelConvention.cs | 17 +- src/EFCore/Metadata/IModel.cs | 14 +- ...uration.cs => IScalarTypeConfiguration.cs} | 4 +- src/EFCore/Metadata/Internal/Model.cs | 17 +- .../Metadata/Internal/ModelConfiguration.cs | 114 ++++++++--- .../Internal/PropertyConfiguration.cs | 2 +- src/EFCore/Metadata/RuntimeModel.cs | 22 +-- ...n.cs => RuntimeScalarTypeConfiguration.cs} | 16 +- src/EFCore/ModelConfigurationBuilder.cs | 68 ++++++- src/EFCore/Storage/TypeMappingSource.cs | 78 ++------ src/Shared/SharedTypeExtensions.cs | 10 +- .../CSharpRuntimeModelCodeGeneratorTest.cs | 13 +- .../Storage/RelationalTypeMapperTestBase.cs | 18 +- .../ValueConvertersEndToEndTestBase.cs | 8 - .../ValueConvertersEndToEndSqlServerTest.cs | 4 +- .../ModelValidatorTest.PropertyMapping.cs | 2 +- .../ModelBuilding/NonRelationshipTestBase.cs | 110 ++++++++--- 32 files changed, 782 insertions(+), 404 deletions(-) create mode 100644 src/EFCore.Relational/Extensions/ScalarConfigurationBuilderExtensions.cs create mode 100644 src/EFCore/Metadata/Builders/ScalarConfigurationBuilder.cs create mode 100644 src/EFCore/Metadata/Builders/ScalarConfigurationBuilder`.cs rename src/EFCore/Metadata/{IPropertyTypeConfiguration.cs => IScalarTypeConfiguration.cs} (94%) rename src/EFCore/Metadata/{RuntimePropertyTypeConfiguration.cs => RuntimeScalarTypeConfiguration.cs} (80%) diff --git a/src/EFCore.Cosmos/Query/Internal/ISqlExpressionFactory.cs b/src/EFCore.Cosmos/Query/Internal/ISqlExpressionFactory.cs index 90ff147664f..9652f0e02dd 100644 --- a/src/EFCore.Cosmos/Query/Internal/ISqlExpressionFactory.cs +++ b/src/EFCore.Cosmos/Query/Internal/ISqlExpressionFactory.cs @@ -1,14 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage; -#nullable disable warnings - namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal { /// @@ -25,7 +22,8 @@ public interface ISqlExpressionFactory /// 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. /// - SqlExpression ApplyTypeMapping(SqlExpression? sqlExpression, CoreTypeMapping typeMapping); + [return: NotNullIfNotNull("sqlExpression")] + SqlExpression? ApplyTypeMapping(SqlExpression? sqlExpression, CoreTypeMapping? typeMapping); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -33,7 +31,8 @@ public interface ISqlExpressionFactory /// 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. /// - SqlExpression ApplyDefaultTypeMapping(SqlExpression? sqlExpression); + [return: NotNullIfNotNull("sqlExpression")] + SqlExpression? ApplyDefaultTypeMapping(SqlExpression? sqlExpression); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Cosmos/Query/Internal/InExpression.cs b/src/EFCore.Cosmos/Query/Internal/InExpression.cs index 5cf2a6d1e7e..6912c28b612 100644 --- a/src/EFCore.Cosmos/Query/Internal/InExpression.cs +++ b/src/EFCore.Cosmos/Query/Internal/InExpression.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Storage; @@ -27,7 +26,7 @@ public InExpression( SqlExpression item, bool negated, SqlExpression values, - CoreTypeMapping typeMapping) + CoreTypeMapping? typeMapping) : base(typeof(bool), typeMapping) { Item = item; diff --git a/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs b/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs index 31b934b7352..b7420e92756 100644 --- a/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs +++ b/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs @@ -1,9 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Cosmos.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -12,8 +10,6 @@ using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Storage; -#nullable disable - namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal { /// @@ -38,7 +34,7 @@ public SqlExpressionFactory(ITypeMappingSource typeMappingSource, IModel model) { _typeMappingSource = typeMappingSource; _model = model; - _boolTypeMapping = typeMappingSource.FindMapping(typeof(bool)); + _boolTypeMapping = typeMappingSource.FindMapping(typeof(bool), model)!; } /// @@ -47,11 +43,12 @@ public SqlExpressionFactory(ITypeMappingSource typeMappingSource, IModel model) /// 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 SqlExpression ApplyDefaultTypeMapping(SqlExpression sqlExpression) + [return: NotNullIfNotNull("sqlExpression")] + public virtual SqlExpression? ApplyDefaultTypeMapping(SqlExpression? sqlExpression) => sqlExpression == null || sqlExpression.TypeMapping != null ? sqlExpression - : ApplyTypeMapping(sqlExpression, _model.FindMapping(sqlExpression.Type)); + : ApplyTypeMapping(sqlExpression, _typeMappingSource.FindMapping(sqlExpression.Type, _model)); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -59,7 +56,8 @@ public virtual SqlExpression ApplyDefaultTypeMapping(SqlExpression sqlExpression /// 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 SqlExpression ApplyTypeMapping(SqlExpression sqlExpression, CoreTypeMapping typeMapping) + [return: NotNullIfNotNull("sqlExpression")] + public virtual SqlExpression? ApplyTypeMapping(SqlExpression? sqlExpression, CoreTypeMapping? typeMapping) { if (sqlExpression == null || sqlExpression.TypeMapping != null) @@ -96,7 +94,7 @@ public virtual SqlExpression ApplyTypeMapping(SqlExpression sqlExpression, CoreT private SqlExpression ApplyTypeMappingOnSqlConditional( SqlConditionalExpression sqlConditionalExpression, - CoreTypeMapping typeMapping) + CoreTypeMapping? typeMapping) { return sqlConditionalExpression.Update( sqlConditionalExpression.Test, @@ -106,11 +104,11 @@ private SqlExpression ApplyTypeMappingOnSqlConditional( private SqlExpression ApplyTypeMappingOnSqlUnary( SqlUnaryExpression sqlUnaryExpression, - CoreTypeMapping typeMapping) + CoreTypeMapping? typeMapping) { SqlExpression operand; Type resultType; - CoreTypeMapping resultTypeMapping; + CoreTypeMapping? resultTypeMapping; switch (sqlUnaryExpression.OperatorType) { case ExpressionType.Equal: @@ -150,14 +148,14 @@ when sqlUnaryExpression.IsLogicalNot(): private SqlExpression ApplyTypeMappingOnSqlBinary( SqlBinaryExpression sqlBinaryExpression, - CoreTypeMapping typeMapping) + CoreTypeMapping? typeMapping) { var left = sqlBinaryExpression.Left; var right = sqlBinaryExpression.Right; Type resultType; - CoreTypeMapping resultTypeMapping; - CoreTypeMapping inferredTypeMapping; + CoreTypeMapping? resultTypeMapping; + CoreTypeMapping? inferredTypeMapping; switch (sqlBinaryExpression.OperatorType) { case ExpressionType.Equal: @@ -170,8 +168,8 @@ private SqlExpression ApplyTypeMappingOnSqlBinary( inferredTypeMapping = ExpressionExtensions.InferTypeMapping(left, right) // We avoid object here since the result does not get typeMapping from outside. ?? (left.Type != typeof(object) - ? _model.FindMapping(left.Type) - : _model.FindMapping(right.Type)); + ? _typeMappingSource.FindMapping(left.Type, _model) + : _typeMappingSource.FindMapping(right.Type, _model)); resultType = typeof(bool); resultTypeMapping = _boolTypeMapping; } @@ -226,7 +224,7 @@ public virtual SqlBinaryExpression MakeBinary( ExpressionType operatorType, SqlExpression left, SqlExpression right, - CoreTypeMapping typeMapping) + CoreTypeMapping? typeMapping) { var returnType = left.Type; switch (operatorType) @@ -325,7 +323,7 @@ public virtual SqlBinaryExpression OrElse(SqlExpression left, SqlExpression righ /// 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 SqlBinaryExpression Add(SqlExpression left, SqlExpression right, CoreTypeMapping typeMapping = null) + public virtual SqlBinaryExpression Add(SqlExpression left, SqlExpression right, CoreTypeMapping? typeMapping = null) => MakeBinary(ExpressionType.Add, left, right, typeMapping); /// @@ -334,7 +332,7 @@ public virtual SqlBinaryExpression Add(SqlExpression left, SqlExpression right, /// 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 SqlBinaryExpression Subtract(SqlExpression left, SqlExpression right, CoreTypeMapping typeMapping = null) + public virtual SqlBinaryExpression Subtract(SqlExpression left, SqlExpression right, CoreTypeMapping? typeMapping = null) => MakeBinary(ExpressionType.Subtract, left, right, typeMapping); /// @@ -343,7 +341,7 @@ public virtual SqlBinaryExpression Subtract(SqlExpression left, SqlExpression ri /// 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 SqlBinaryExpression Multiply(SqlExpression left, SqlExpression right, CoreTypeMapping typeMapping = null) + public virtual SqlBinaryExpression Multiply(SqlExpression left, SqlExpression right, CoreTypeMapping? typeMapping = null) => MakeBinary(ExpressionType.Multiply, left, right, typeMapping); /// @@ -352,7 +350,7 @@ public virtual SqlBinaryExpression Multiply(SqlExpression left, SqlExpression ri /// 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 SqlBinaryExpression Divide(SqlExpression left, SqlExpression right, CoreTypeMapping typeMapping = null) + public virtual SqlBinaryExpression Divide(SqlExpression left, SqlExpression right, CoreTypeMapping? typeMapping = null) => MakeBinary(ExpressionType.Divide, left, right, typeMapping); /// @@ -361,7 +359,7 @@ public virtual SqlBinaryExpression Divide(SqlExpression left, SqlExpression righ /// 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 SqlBinaryExpression Modulo(SqlExpression left, SqlExpression right, CoreTypeMapping typeMapping = null) + public virtual SqlBinaryExpression Modulo(SqlExpression left, SqlExpression right, CoreTypeMapping? typeMapping = null) => MakeBinary(ExpressionType.Modulo, left, right, typeMapping); /// @@ -370,7 +368,7 @@ public virtual SqlBinaryExpression Modulo(SqlExpression left, SqlExpression righ /// 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 SqlBinaryExpression And(SqlExpression left, SqlExpression right, CoreTypeMapping typeMapping = null) + public virtual SqlBinaryExpression And(SqlExpression left, SqlExpression right, CoreTypeMapping? typeMapping = null) => MakeBinary(ExpressionType.And, left, right, typeMapping); /// @@ -379,14 +377,14 @@ public virtual SqlBinaryExpression And(SqlExpression left, SqlExpression right, /// 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 SqlBinaryExpression Or(SqlExpression left, SqlExpression right, CoreTypeMapping typeMapping = null) + public virtual SqlBinaryExpression Or(SqlExpression left, SqlExpression right, CoreTypeMapping? typeMapping = null) => MakeBinary(ExpressionType.Or, left, right, typeMapping); private SqlUnaryExpression MakeUnary( ExpressionType operatorType, SqlExpression operand, Type type, - CoreTypeMapping typeMapping = null) + CoreTypeMapping? typeMapping = null) { return (SqlUnaryExpression)ApplyTypeMapping(new SqlUnaryExpression(operatorType, operand, type, null), typeMapping); } @@ -415,7 +413,7 @@ public virtual SqlBinaryExpression IsNotNull(SqlExpression operand) /// 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 SqlUnaryExpression Convert(SqlExpression operand, Type type, CoreTypeMapping typeMapping = null) + public virtual SqlUnaryExpression Convert(SqlExpression operand, Type type, CoreTypeMapping? typeMapping = null) => MakeUnary(ExpressionType.Convert, operand, type, typeMapping); /// @@ -446,7 +444,7 @@ public virtual SqlFunctionExpression Function( string functionName, IEnumerable arguments, Type returnType, - CoreTypeMapping typeMapping = null) + CoreTypeMapping? typeMapping = null) { var typeMappedArguments = new List(); @@ -486,7 +484,7 @@ public virtual SqlConditionalExpression Condition(SqlExpression test, SqlExpress /// public virtual InExpression In(SqlExpression item, SqlExpression values, bool negated) { - var typeMapping = item.TypeMapping ?? _model.FindMapping(item.Type); + var typeMapping = item.TypeMapping ?? _typeMappingSource.FindMapping(item.Type, _model); item = ApplyTypeMapping(item, typeMapping); values = ApplyTypeMapping(values, typeMapping); @@ -500,7 +498,7 @@ public virtual InExpression In(SqlExpression item, SqlExpression values, bool ne /// 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 SqlConstantExpression Constant(object value, CoreTypeMapping typeMapping = null) + public virtual SqlConstantExpression Constant(object? value, CoreTypeMapping? typeMapping = null) => new(Expression.Constant(value), typeMapping); /// diff --git a/src/EFCore.Design/Design/Internal/DatabaseOperations.cs b/src/EFCore.Design/Design/Internal/DatabaseOperations.cs index 9d882296e59..f6237286f96 100644 --- a/src/EFCore.Design/Design/Internal/DatabaseOperations.cs +++ b/src/EFCore.Design/Design/Internal/DatabaseOperations.cs @@ -1,9 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; -using System.IO; using System.Reflection; using Microsoft.EntityFrameworkCore.Scaffolding; using Microsoft.EntityFrameworkCore.Utilities; @@ -93,7 +90,7 @@ public virtual SavedModelFiles ScaffoldContext( : outputDir; var services = _servicesBuilder.Build(provider); - using var scope = services.CreateScope(); + using var scope = services.CreateScope(); var scaffolder = scope.ServiceProvider.GetRequiredService(); diff --git a/src/EFCore.Relational/Extensions/PropertiesConfigurationBuilderExtensions.cs b/src/EFCore.Relational/Extensions/PropertiesConfigurationBuilderExtensions.cs index d2d8f5165af..9fb0d029fe1 100644 --- a/src/EFCore.Relational/Extensions/PropertiesConfigurationBuilderExtensions.cs +++ b/src/EFCore.Relational/Extensions/PropertiesConfigurationBuilderExtensions.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore { /// - /// Relational database specific extension methods for . + /// Relational database specific extension methods for . /// public static class PropertiesConfigurationBuilderExtensions { diff --git a/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs b/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs index c181470fa9c..7754880519e 100644 --- a/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalModelExtensions.cs @@ -1,15 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; // ReSharper disable once CheckNamespace @@ -521,40 +516,5 @@ public static void SetCollation(this IMutableModel model, string? value) /// The configuration source for the collation. public static ConfigurationSource? GetCollationConfigurationSource(this IConventionModel model) => model.FindAnnotation(RelationalAnnotationNames.Collation)?.GetConfigurationSource(); - - /// - /// Gets the relational database type for a given object, throwing if no mapping is found. - /// - /// The model. - /// The object to get the mapping for. - /// The type mapping to be used. - public static RelationalTypeMapping GetMappingForValue( - this IModel model, - object? value) - => value == null - || value == DBNull.Value - ? RelationalTypeMapping.NullMapping - : model.GetMapping(value.GetType()); - - /// - /// Gets the relational database type for a given .NET type, throwing if no mapping is found. - /// - /// The model. - /// The type to get the mapping for. - /// The type mapping to be used. - public static RelationalTypeMapping GetMapping( - this IModel model, - Type clrType) - { - Check.NotNull(clrType, nameof(clrType)); - - var mapping = model.FindMapping(clrType); - if (mapping != null) - { - return (RelationalTypeMapping)mapping; - } - - throw new InvalidOperationException(RelationalStrings.UnsupportedType(clrType.ShortDisplayName())); - } } } diff --git a/src/EFCore.Relational/Extensions/ScalarConfigurationBuilderExtensions.cs b/src/EFCore.Relational/Extensions/ScalarConfigurationBuilderExtensions.cs new file mode 100644 index 00000000000..af0b4b87eaa --- /dev/null +++ b/src/EFCore.Relational/Extensions/ScalarConfigurationBuilderExtensions.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Utilities; + +// ReSharper disable once CheckNamespace +namespace Microsoft.EntityFrameworkCore +{ + /// + /// Relational database specific extension methods for . + /// + public static class ScalarConfigurationBuilderExtensions + { + /// + /// Configures the data type of the column that the scalar maps to when targeting a relational database. + /// This should be the complete type name, including precision, scale, length, etc. + /// + /// The builder for the scalar being configured. + /// The name of the data type of the column. + /// The same builder instance so that multiple calls can be chained. + public static ScalarConfigurationBuilder HaveColumnType( + this ScalarConfigurationBuilder scalarBuilder, + string typeName) + { + Check.NotNull(scalarBuilder, nameof(scalarBuilder)); + Check.NotEmpty(typeName, nameof(typeName)); + + scalarBuilder.HaveAnnotation(RelationalAnnotationNames.ColumnType, typeName); + + return scalarBuilder; + } + + /// + /// Configures the data type of the column that the scalar maps to when targeting a relational database. + /// This should be the complete type name, including precision, scale, length, etc. + /// + /// The type of the scalar being configured. + /// The builder for the scalar being configured. + /// The name of the data type of the column. + /// The same builder instance so that multiple calls can be chained. + public static ScalarConfigurationBuilder HaveColumnType( + this ScalarConfigurationBuilder scalarBuilder, + string typeName) + => (ScalarConfigurationBuilder)HaveColumnType((ScalarConfigurationBuilder)scalarBuilder, typeName); + + /// + /// Configures the scalar as capable of storing only fixed-length data, such as strings. + /// + /// The builder for the scalar being configured. + /// A value indicating whether the scalar is constrained to fixed length values. + /// The same builder instance so that multiple configuration calls can be chained. + public static ScalarConfigurationBuilder AreFixedLength( + this ScalarConfigurationBuilder scalarBuilder, + bool fixedLength = true) + { + Check.NotNull(scalarBuilder, nameof(scalarBuilder)); + + scalarBuilder.HaveAnnotation(RelationalAnnotationNames.IsFixedLength, fixedLength); + + return scalarBuilder; + } + + /// + /// Configures the scalar as capable of storing only fixed-length data, such as strings. + /// + /// The type of the scalar being configured. + /// The builder for the scalar being configured. + /// A value indicating whether the scalar is constrained to fixed length values. + /// The same builder instance so that multiple configuration calls can be chained. + public static ScalarConfigurationBuilder AreFixedLength( + this ScalarConfigurationBuilder scalarBuilder, + bool fixedLength = true) + => (ScalarConfigurationBuilder)AreFixedLength((ScalarConfigurationBuilder)scalarBuilder, fixedLength); + } +} diff --git a/src/EFCore.Relational/Query/Internal/SqlExpressionSimplifyingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/SqlExpressionSimplifyingExpressionVisitor.cs index 3582153d42e..a7df7406f9e 100644 --- a/src/EFCore.Relational/Query/Internal/SqlExpressionSimplifyingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/SqlExpressionSimplifyingExpressionVisitor.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; using Microsoft.EntityFrameworkCore.Storage; diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index 5b55164b4c2..e7a4ef1f0ab 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -1050,7 +1050,7 @@ protected override Expression VisitUnary(UnaryExpression unaryExpression) // Introduce explicit cast only if the target type is mapped else we need to client eval if (unaryExpression.Type == typeof(object) - || Dependencies.Model.FindMapping(unaryExpression.Type) != null) + || Dependencies.TypeMappingSource.FindMapping(unaryExpression.Type, Dependencies.Model) != null) { sqlOperand = _sqlExpressionFactory.ApplyDefaultTypeMapping(sqlOperand); diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs index a34a891a865..154bf12382e 100644 --- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs @@ -16,7 +16,6 @@ namespace Microsoft.EntityFrameworkCore.Query /// public class SqlExpressionFactory : ISqlExpressionFactory { - private readonly IModel _model; private readonly RelationalTypeMapping _boolTypeMapping; /// @@ -28,8 +27,7 @@ public SqlExpressionFactory(SqlExpressionFactoryDependencies dependencies) Check.NotNull(dependencies, nameof(dependencies)); Dependencies = dependencies; - _model = dependencies.Model; - _boolTypeMapping = (RelationalTypeMapping)_model.FindMapping(typeof(bool))!; + _boolTypeMapping = dependencies.TypeMappingSource.FindMapping(typeof(bool), dependencies.Model)!; } /// @@ -40,16 +38,14 @@ public SqlExpressionFactory(SqlExpressionFactoryDependencies dependencies) /// [return: NotNullIfNotNull("sqlExpression")] public virtual SqlExpression? ApplyDefaultTypeMapping(SqlExpression? sqlExpression) - { - return sqlExpression == null + => sqlExpression == null || sqlExpression.TypeMapping != null ? sqlExpression : sqlExpression is SqlUnaryExpression sqlUnaryExpression && sqlUnaryExpression.OperatorType == ExpressionType.Convert && sqlUnaryExpression.Type == typeof(object) ? sqlUnaryExpression.Operand - : ApplyTypeMapping(sqlExpression, (RelationalTypeMapping?)_model.FindMapping(sqlExpression.Type)); - } + : ApplyTypeMapping(sqlExpression, Dependencies.TypeMappingSource.FindMapping(sqlExpression.Type, Dependencies.Model)); /// [return: NotNullIfNotNull("sqlExpression")] @@ -86,7 +82,7 @@ private SqlExpression ApplyTypeMappingOnLike(LikeExpression likeExpression) likeExpression.Match, likeExpression.Pattern) : ExpressionExtensions.InferTypeMapping( likeExpression.Match, likeExpression.Pattern, likeExpression.EscapeChar)) - ?? (RelationalTypeMapping?)_model.FindMapping(likeExpression.Match.Type); + ?? Dependencies.TypeMappingSource.FindMapping(likeExpression.Match.Type, Dependencies.Model); return new LikeExpression( ApplyTypeMapping(likeExpression.Match, inferredTypeMapping), @@ -189,8 +185,8 @@ private SqlExpression ApplyTypeMappingOnSqlBinary( inferredTypeMapping = ExpressionExtensions.InferTypeMapping(left, right) // We avoid object here since the result does not get typeMapping from outside. ?? (left.Type != typeof(object) - ? (RelationalTypeMapping?)_model.FindMapping(left.Type) - : (RelationalTypeMapping?)_model.FindMapping(right.Type)); + ? Dependencies.TypeMappingSource.FindMapping(left.Type, Dependencies.Model) + : Dependencies.TypeMappingSource.FindMapping(right.Type, Dependencies.Model)); resultType = typeof(bool); resultTypeMapping = _boolTypeMapping; break; @@ -411,7 +407,7 @@ public virtual SqlFunctionExpression Coalesce(SqlExpression left, SqlExpression var resultType = right.Type; var inferredTypeMapping = typeMapping ?? ExpressionExtensions.InferTypeMapping(left, right) - ?? (RelationalTypeMapping?)_model.FindMapping(resultType); + ?? Dependencies.TypeMappingSource.FindMapping(resultType, Dependencies.Model); var typeMappedArguments = new List { @@ -506,7 +502,7 @@ public virtual CaseExpression Case(SqlExpression? operand, IReadOnlyList wc.Test.Type)) - .Where(t => t != typeof(object)).Select(t => (RelationalTypeMapping?)_model.FindMapping(t)).FirstOrDefault(); + .Where(t => t != typeof(object)).Select(t => Dependencies.TypeMappingSource.FindMapping(t, Dependencies.Model)).FirstOrDefault(); var resultTypeMapping = elseResult?.TypeMapping ?? whenClauses.Select(wc => wc.Result.TypeMapping).FirstOrDefault(t => t != null); @@ -748,7 +744,7 @@ public virtual InExpression In(SqlExpression item, SqlExpression values, bool ne Check.NotNull(item, nameof(item)); Check.NotNull(values, nameof(values)); - var typeMapping = item.TypeMapping ?? (RelationalTypeMapping?)_model.FindMapping(item.Type); + var typeMapping = item.TypeMapping ?? Dependencies.TypeMappingSource.FindMapping(item.Type, Dependencies.Model); item = ApplyTypeMapping(item, typeMapping); values = ApplyTypeMapping(values, typeMapping); @@ -978,11 +974,11 @@ private SqlExpression IsNotNull(IProperty property, EntityProjectionExpression e /// [Obsolete("Use IRelationalTypeMappingSource directly.")] public virtual RelationalTypeMapping GetTypeMappingForValue(object? value) - => _model.GetMappingForValue(value); + => Dependencies.TypeMappingSource.GetMappingForValue(value, Dependencies.Model); /// [Obsolete("Use IRelationalTypeMappingSource directly.")] public virtual RelationalTypeMapping? FindMapping(Type type) - => (RelationalTypeMapping?)_model.FindMapping(Check.NotNull(type, nameof(type))); + => Dependencies.TypeMappingSource.FindMapping(type, Dependencies.Model); } } diff --git a/src/EFCore.Relational/Query/SqlExpressionFactoryDependencies.cs b/src/EFCore.Relational/Query/SqlExpressionFactoryDependencies.cs index 10c7b381ede..adf6e1eacc8 100644 --- a/src/EFCore.Relational/Query/SqlExpressionFactoryDependencies.cs +++ b/src/EFCore.Relational/Query/SqlExpressionFactoryDependencies.cs @@ -60,15 +60,12 @@ public SqlExpressionFactoryDependencies(IModel model, IRelationalTypeMappingSour Check.NotNull(typeMappingSource, nameof(typeMappingSource)); Model = model; -#pragma warning disable CS0618 // Type or member is obsolete TypeMappingSource = typeMappingSource; -#pragma warning restore CS0618 // Type or member is obsolete } /// /// The type mapping source. /// - [Obsolete("Use Model instead")] public IRelationalTypeMappingSource TypeMappingSource { get; init; } /// diff --git a/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs b/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs index cf1dde32d31..6a3f222ea23 100644 --- a/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs +++ b/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs @@ -260,108 +260,65 @@ protected override CoreTypeMapping FindMapping(in TypeMappingInfo mappingInfo) /// The type mapping, or if none was found. public override CoreTypeMapping? FindMapping(Type type, IModel model) { - var typeConfigurations = model.FindPropertyTypeConfigurations(type); - var mappingInfo = new RelationalTypeMappingInfo(type); + type = type.UnwrapNullableType(); + var typeConfiguration = model.FindScalarTypeConfiguration(type); + RelationalTypeMappingInfo mappingInfo; Type? providerClrType = null; ValueConverter? customConverter = null; - if (typeConfigurations != null) + if (typeConfiguration == null) { - foreach (var typeConfiguration in typeConfigurations) - { - if (providerClrType == null) - { - var providerType = typeConfiguration.GetProviderClrType(); - if (providerType != null) - { - providerClrType = providerType.UnwrapNullableType(); - } - } - - var isUnicode = typeConfiguration.IsUnicode(); - var scale = typeConfiguration.GetScale(); - var precision = typeConfiguration.GetPrecision(); - var size = typeConfiguration.GetMaxLength(); - - if (mappingInfo.StoreTypeName == null) - { - var storeTypeName = (string?)typeConfiguration[RelationalAnnotationNames.ColumnType]; - if (storeTypeName != null) - { - var storeTypeBaseName = ParseStoreTypeName( - storeTypeName, out var parsedUnicode, out var parsedSize, out var parsedPrecision, out var parsedScale); - - mappingInfo = mappingInfo with { StoreTypeName = storeTypeName }; - - if (mappingInfo.StoreTypeNameBase == null) - { - mappingInfo = mappingInfo with { StoreTypeNameBase = storeTypeBaseName }; - } - - if (size == null) - { - size = parsedSize; - } - - if (precision == null) - { - precision = parsedPrecision; - } - - if (scale == null) - { - scale = parsedScale; - } + mappingInfo = new RelationalTypeMappingInfo(type); + } + else + { + providerClrType = typeConfiguration.GetProviderClrType()?.UnwrapNullableType(); + customConverter = typeConfiguration.GetValueConverter(); - if (isUnicode == null) - { - isUnicode = parsedUnicode; - } - } - } + var isUnicode = typeConfiguration.IsUnicode(); + var scale = typeConfiguration.GetScale(); + var precision = typeConfiguration.GetPrecision(); + var size = typeConfiguration.GetMaxLength(); - if (mappingInfo.IsUnicode == null - && isUnicode != null) - { - mappingInfo = mappingInfo with { IsUnicode = isUnicode }; - } + var storeTypeName = (string?)typeConfiguration[RelationalAnnotationNames.ColumnType]; + string? storeTypeBaseName = null; + if (storeTypeName != null) + { + storeTypeBaseName = ParseStoreTypeName( + storeTypeName, out var parsedUnicode, out var parsedSize, out var parsedPrecision, out var parsedScale); - if (mappingInfo.Scale == null - && scale != null) + if (size == null) { - mappingInfo = mappingInfo with { Scale = scale }; + size = parsedSize; } - if (mappingInfo.Precision == null - && precision != null) + if (precision == null) { - mappingInfo = mappingInfo with { Precision = precision }; + precision = parsedPrecision; } - if (mappingInfo.Size == null - && size != null) + if (scale == null) { - mappingInfo = mappingInfo with { Size = size }; + scale = parsedScale; } - if (mappingInfo.IsFixedLength == null) + if (isUnicode == null) { - var isFixedLength = (bool?)typeConfiguration[RelationalAnnotationNames.IsFixedLength]; - if (isFixedLength != null) - { - mappingInfo = mappingInfo with { IsFixedLength = isFixedLength }; - } + isUnicode = parsedUnicode; } } - var firstConfiguration = typeConfigurations.FirstOrDefault(); - customConverter = firstConfiguration?.ClrType == type - ? firstConfiguration.GetValueConverter() - : null; - - if (customConverter != null) - { - mappingInfo = mappingInfo with { ClrType = customConverter.ProviderClrType }; - } + var isFixedLength = (bool?)typeConfiguration[RelationalAnnotationNames.IsFixedLength]; + mappingInfo = new RelationalTypeMappingInfo( + customConverter?.ProviderClrType ?? type, + storeTypeName, + storeTypeBaseName, + keyOrIndex: false, + unicode: isUnicode, + size: size, + rowVersion: false, + fixedLength: isFixedLength, + precision: precision, + scale: scale); } return FindMappingWithConversion(mappingInfo, providerClrType, customConverter); diff --git a/src/EFCore.Relational/Storage/RelationalTypeMappingSourceExtensions.cs b/src/EFCore.Relational/Storage/RelationalTypeMappingSourceExtensions.cs index 5c103edc42b..47a79b05807 100644 --- a/src/EFCore.Relational/Storage/RelationalTypeMappingSourceExtensions.cs +++ b/src/EFCore.Relational/Storage/RelationalTypeMappingSourceExtensions.cs @@ -20,14 +20,29 @@ public static class RelationalTypeMappingSourceExtensions /// The object to get the mapping for. /// The type mapping to be used. public static RelationalTypeMapping GetMappingForValue( - this IRelationalTypeMappingSource? typeMappingSource, + this IRelationalTypeMappingSource typeMappingSource, object? value) => value == null || value == DBNull.Value - || typeMappingSource == null ? RelationalTypeMapping.NullMapping : typeMappingSource.GetMapping(value.GetType()); + /// + /// Gets the relational database type for a given object, throwing if no mapping is found. + /// + /// The type mapping source. + /// The object to get the mapping for. + /// The model. + /// The type mapping to be used. + public static RelationalTypeMapping GetMappingForValue( + this IRelationalTypeMappingSource typeMappingSource, + object? value, + IModel model) + => value == null + || value == DBNull.Value + ? RelationalTypeMapping.NullMapping + : typeMappingSource.GetMapping(value.GetType(), model); + /// /// Gets the relational database type for a given property, throwing if no mapping is found. /// @@ -77,6 +92,29 @@ public static RelationalTypeMapping GetMapping( throw new InvalidOperationException(RelationalStrings.UnsupportedType(clrType.ShortDisplayName())); } + /// + /// Gets the relational database type for a given .NET type, throwing if no mapping is found. + /// + /// The type mapping source. + /// The type to get the mapping for. + /// The model. + /// The type mapping to be used. + public static RelationalTypeMapping GetMapping( + this IRelationalTypeMappingSource typeMappingSource, + Type clrType, + IModel model) + { + Check.NotNull(clrType, nameof(clrType)); + + var mapping = typeMappingSource.FindMapping(clrType, model); + if (mapping != null) + { + return mapping; + } + + throw new InvalidOperationException(RelationalStrings.UnsupportedType(clrType.ShortDisplayName())); + } + /// /// /// Gets the mapping that represents the given database type, throwing if no mapping is found. diff --git a/src/EFCore/Metadata/Builders/ScalarConfigurationBuilder.cs b/src/EFCore/Metadata/Builders/ScalarConfigurationBuilder.cs new file mode 100644 index 00000000000..da0f8d9a537 --- /dev/null +++ b/src/EFCore/Metadata/Builders/ScalarConfigurationBuilder.cs @@ -0,0 +1,177 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.EntityFrameworkCore.Utilities; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders +{ + /// + /// + /// Provides a simple API surface for setting property defaults before conventions run. + /// + /// + /// Instances of this class are returned from methods when using the API + /// and it is not designed to be directly constructed in your application code. + /// + /// + public class ScalarConfigurationBuilder + { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public ScalarConfigurationBuilder(PropertyConfiguration scalar) + { + Check.NotNull(scalar, nameof(scalar)); + + Scalar = scalar; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + protected virtual PropertyConfiguration Scalar { get; } + + /// + /// Adds or updates an annotation on the property. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ScalarConfigurationBuilder HaveAnnotation(string annotation, object value) + { + Check.NotEmpty(annotation, nameof(annotation)); + + Scalar[annotation] = value; + + return this; + } + + /// + /// Configures the maximum length of data that can be stored in this property. + /// Maximum length can only be set on array properties (including properties). + /// + /// The maximum length of data allowed in the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ScalarConfigurationBuilder HaveMaxLength(int maxLength) + { + Scalar.SetMaxLength(maxLength); + + return this; + } + + /// + /// Configures the precision and scale of the property. + /// + /// The precision of the property. + /// The scale of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ScalarConfigurationBuilder HavePrecision(int precision, int scale) + { + Scalar.SetPrecision(precision); + Scalar.SetScale(scale); + + return this; + } + + /// + /// + /// Configures the precision of the property. + /// + /// + /// The precision of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ScalarConfigurationBuilder HavePrecision(int precision) + { + Scalar.SetPrecision(precision); + + return this; + } + + /// + /// Configures whether the property as capable of persisting unicode characters. + /// Can only be set on properties. + /// + /// A value indicating whether the property can contain unicode characters. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ScalarConfigurationBuilder AreUnicode(bool unicode = true) + { + Scalar.SetIsUnicode(unicode); + + return this; + } + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that derives from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ScalarConfigurationBuilder HaveConversion() + => HaveConversion(typeof(TConversion)); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that derives from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ScalarConfigurationBuilder HaveConversion(Type conversionType) + { + Check.NotNull(conversionType, nameof(conversionType)); + + if (typeof(ValueConverter).IsAssignableFrom(conversionType)) + { + Scalar.SetValueConverter(conversionType); + } + else + { + Scalar.SetProviderClrType(conversionType); + } + + return this; + } + + #region Hidden System.Object members + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() + => base.ToString(); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// if the specified object is equal to the current object; otherwise, . + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectEqualsIsObjectEquals + public override bool Equals(object? obj) + => base.Equals(obj); + + /// + /// Serves as the default hash function. + /// + /// A hash code for the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode + public override int GetHashCode() + => base.GetHashCode(); + + #endregion + } +} diff --git a/src/EFCore/Metadata/Builders/ScalarConfigurationBuilder`.cs b/src/EFCore/Metadata/Builders/ScalarConfigurationBuilder`.cs new file mode 100644 index 00000000000..5ae0a31e4f1 --- /dev/null +++ b/src/EFCore/Metadata/Builders/ScalarConfigurationBuilder`.cs @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders +{ + /// + /// + /// Provides a simple API surface for setting property defaults before conventions run. + /// + /// + /// Instances of this class are returned from methods when using the API + /// and it is not designed to be directly constructed in your application code. + /// + /// + public class ScalarConfigurationBuilder : ScalarConfigurationBuilder + { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public ScalarConfigurationBuilder(PropertyConfiguration scalar) + : base(scalar) + { + } + + /// + /// Adds or updates an annotation on the property. If an annotation with the key specified in + /// already exists its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ScalarConfigurationBuilder HaveAnnotation(string annotation, object value) + => (ScalarConfigurationBuilder)base.HaveAnnotation(annotation, value); + + /// + /// Configures the maximum length of data that can be stored in this property. + /// Maximum length can only be set on array properties (including properties). + /// + /// The maximum length of data allowed in the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ScalarConfigurationBuilder HaveMaxLength(int maxLength) + => (ScalarConfigurationBuilder)base.HaveMaxLength(maxLength); + + /// + /// Configures the precision and scale of the property. + /// + /// The precision of the property. + /// The scale of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ScalarConfigurationBuilder HavePrecision(int precision, int scale) + => (ScalarConfigurationBuilder)base.HavePrecision(precision, scale); + + /// + /// + /// Configures the precision of the property. + /// + /// + /// The precision of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ScalarConfigurationBuilder HavePrecision(int precision) + => (ScalarConfigurationBuilder)base.HavePrecision(precision); + + /// + /// Configures the property as capable of persisting unicode characters. + /// Can only be set on properties. + /// + /// A value indicating whether the property can contain unicode characters. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ScalarConfigurationBuilder AreUnicode(bool unicode = true) + => (ScalarConfigurationBuilder)base.AreUnicode(unicode); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that derives from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ScalarConfigurationBuilder HaveConversion() + => (ScalarConfigurationBuilder)base.HaveConversion(); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that derives from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ScalarConfigurationBuilder HaveConversion(Type conversionType) + => (ScalarConfigurationBuilder)base.HaveConversion(conversionType); + } +} diff --git a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs index 6587a58790b..7588208f1b0 100644 --- a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs +++ b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs @@ -1,9 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; @@ -150,11 +147,11 @@ protected virtual RuntimeModel Create(IModel model) convention.ProcessEntityTypeAnnotations(annotations, source, target, runtime)); } - foreach (var typeConfiguration in model.GetPropertyTypeConfigurations()) + foreach (var typeConfiguration in model.GetScalarTypeConfigurations()) { var runtimeTypeConfiguration = Create(typeConfiguration, runtimeModel); CreateAnnotations(typeConfiguration, runtimeTypeConfiguration, static (convention, annotations, source, target, runtime) => - convention.ProcessPropertyTypeConfigurationAnnotations(annotations, source, target, runtime)); + convention.ProcessScalarTypeConfigurationAnnotations(annotations, source, target, runtime)); } CreateAnnotations(model, runtimeModel, static (convention, annotations, source, target, runtime) => @@ -258,8 +255,8 @@ protected virtual void ProcessEntityTypeAnnotations( } } - private RuntimePropertyTypeConfiguration Create(IPropertyTypeConfiguration typeConfiguration, RuntimeModel model) - => model.AddPropertyTypeConfiguration( + private RuntimeScalarTypeConfiguration Create(IScalarTypeConfiguration typeConfiguration, RuntimeModel model) + => model.AddScalarTypeConfiguration( typeConfiguration.ClrType, typeConfiguration.GetMaxLength(), typeConfiguration.IsUnicode(), @@ -275,10 +272,10 @@ private RuntimePropertyTypeConfiguration Create(IPropertyTypeConfiguration typeC /// The source property. /// The target property that will contain the annotations. /// Indicates whether the given annotations are runtime annotations. - protected virtual void ProcessPropertyTypeConfigurationAnnotations( + protected virtual void ProcessScalarTypeConfigurationAnnotations( Dictionary annotations, - IPropertyTypeConfiguration typeConfiguration, - RuntimePropertyTypeConfiguration runtimeTypeConfiguration, + IScalarTypeConfiguration typeConfiguration, + RuntimeScalarTypeConfiguration runtimeTypeConfiguration, bool runtime) { if (!runtime) diff --git a/src/EFCore/Metadata/IModel.cs b/src/EFCore/Metadata/IModel.cs index 6607ca7fed2..bd10d44c43d 100644 --- a/src/EFCore/Metadata/IModel.cs +++ b/src/EFCore/Metadata/IModel.cs @@ -148,25 +148,17 @@ RuntimeModelDependencies GetModelDependencies() /// The to check. bool IsIndexerMethod(MethodInfo methodInfo); - /// - /// - /// Finds the type mapping for a given , taking pre-convention configuration into the account. - /// - /// - /// The CLR type. - CoreTypeMapping? FindMapping(Type type); - /// /// Gets all the pre-convention configurations. /// /// The pre-convention configurations. - IEnumerable GetPropertyTypeConfigurations(); + IEnumerable GetScalarTypeConfigurations(); /// /// Finds the pre-convention configurations for a given scalar . /// /// The CLR type. - /// The pre-convention configurations in order of most to least specific, or if none is found. - IEnumerable? FindPropertyTypeConfigurations(Type propertyType); + /// The pre-convention configuration or if none is found. + IScalarTypeConfiguration? FindScalarTypeConfiguration(Type propertyType); } } diff --git a/src/EFCore/Metadata/IPropertyTypeConfiguration.cs b/src/EFCore/Metadata/IScalarTypeConfiguration.cs similarity index 94% rename from src/EFCore/Metadata/IPropertyTypeConfiguration.cs rename to src/EFCore/Metadata/IScalarTypeConfiguration.cs index 1bdb905489e..7e60278040c 100644 --- a/src/EFCore/Metadata/IPropertyTypeConfiguration.cs +++ b/src/EFCore/Metadata/IScalarTypeConfiguration.cs @@ -7,9 +7,9 @@ namespace Microsoft.EntityFrameworkCore.Metadata { /// - /// Represents the configuration for a scalar property type. + /// Represents the configuration for a scalar type. /// - public interface IPropertyTypeConfiguration : IAnnotatable + public interface IScalarTypeConfiguration : IAnnotatable { /// /// Gets the type configured by this object. diff --git a/src/EFCore/Metadata/Internal/Model.cs b/src/EFCore/Metadata/Internal/Model.cs index 40ed614e20a..841359cd0e4 100644 --- a/src/EFCore/Metadata/Internal/Model.cs +++ b/src/EFCore/Metadata/Internal/Model.cs @@ -653,8 +653,8 @@ public virtual bool IsIgnoredType(Type type) /// 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 CoreTypeMapping? FindMapping(Type type) - => ((IModel)this).GetModelDependencies().TypeMappingSource.FindMapping(type, this); + public virtual IEnumerable GetScalarTypeConfigurations() + => Configuration?.GetScalarTypeConfigurations() ?? Enumerable.Empty(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -662,17 +662,8 @@ public virtual bool IsIgnoredType(Type type) /// 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 IEnumerable GetPropertyTypeConfigurations() - => Configuration?.GetPropertyTypeConfigurations() ?? Enumerable.Empty(); - - /// - /// 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 IEnumerable? FindPropertyTypeConfigurations(Type propertyType) - => Configuration?.FindPropertyTypeConfigurations(propertyType); + public virtual IScalarTypeConfiguration? FindScalarTypeConfiguration(Type propertyType) + => Configuration?.FindScalarTypeConfiguration(propertyType); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/ModelConfiguration.cs b/src/EFCore/Metadata/Internal/ModelConfiguration.cs index 0e09821e030..4b73c725808 100644 --- a/src/EFCore/Metadata/Internal/ModelConfiguration.cs +++ b/src/EFCore/Metadata/Internal/ModelConfiguration.cs @@ -1,9 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections; +using System.Dynamic; using Microsoft.EntityFrameworkCore.Diagnostics; namespace Microsoft.EntityFrameworkCore.Metadata.Internal @@ -17,6 +16,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal public partial class ModelConfiguration { private readonly Dictionary _properties = new(); + private readonly Dictionary _scalars = new(); private readonly HashSet _ignoredTypes = new(); private readonly Dictionary _configurationTypes = new(); @@ -37,7 +37,7 @@ public ModelConfiguration() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool IsEmpty() - => _properties.Count == 0 && _ignoredTypes.Count == 0; + => _properties.Count == 0 && _ignoredTypes.Count == 0 && _scalars.Count == 0; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -66,6 +66,13 @@ public virtual bool IsEmpty() } Type? configuredType = null; + + if (type.IsConstructedGenericType) + { + configurationType = GetConfigurationType( + type.GetGenericTypeDefinition(), configurationType, ref configuredType, getBaseTypes: false); + } + if (getBaseTypes) { if (type.BaseType != null) @@ -74,12 +81,6 @@ public virtual bool IsEmpty() type.BaseType, configurationType, ref configuredType); } - if (type.IsConstructedGenericType) - { - configurationType = GetConfigurationType( - type.GetGenericTypeDefinition(), configurationType, ref configuredType, getBaseTypes: false); - } - foreach (var @interface in type.GetDeclaredInterfaces()) { configurationType = GetConfigurationType( @@ -130,8 +131,8 @@ private static void EnsureCompatible( /// 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 IEnumerable GetPropertyTypeConfigurations() - => _properties.Values; + public virtual IEnumerable GetScalarTypeConfigurations() + => _scalars.Values; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -139,12 +140,10 @@ public virtual IEnumerable GetPropertyTypeConfigurat /// 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 IEnumerable? FindPropertyTypeConfigurations(Type propertyType) - => GetConfigurationType(propertyType) != TypeConfigurationType.Property + public virtual IScalarTypeConfiguration? FindScalarTypeConfiguration(Type scalarType) + => _scalars.Count == 0 ? null - : propertyType.GetBaseTypesAndInterfacesInclusive() - .Select(type => _properties.GetValueOrDefault(type)!) - .Where(type => type != null); + : _scalars.GetValueOrDefault(scalarType); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -174,16 +173,26 @@ public virtual void ConfigureProperty(IMutableProperty property) /// public virtual PropertyConfiguration GetOrAddProperty(Type type) { - if (type.UnwrapNullableType() == typeof(object) - || type == Model.DefaultPropertyBagType) - { - throw new InvalidOperationException( - CoreStrings.UnconfigurableType(type.DisplayName(fullName: false), TypeConfigurationType.Property)); - } - var property = FindProperty(type); if (property == null) { + if (type == typeof(object) + || type == typeof(ExpandoObject) + || type == typeof(SortedDictionary) + || type == typeof(Dictionary) + || type == typeof(IDictionary) + || type == typeof(IReadOnlyDictionary) + || type == typeof(IDictionary) + || type == typeof(ICollection>) + || type == typeof(IReadOnlyCollection>) + || type == typeof(ICollection) + || type == typeof(IEnumerable>) + || type == typeof(IEnumerable)) + { + throw new InvalidOperationException( + CoreStrings.UnconfigurableType(type.DisplayName(fullName: false), TypeConfigurationType.Property)); + } + RemoveIgnored(type); property = new PropertyConfiguration(type); @@ -213,6 +222,46 @@ public virtual PropertyConfiguration GetOrAddProperty(Type type) public virtual bool RemoveProperty(Type type) => _properties.Remove(type); + /// + /// 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 PropertyConfiguration GetOrAddScalar(Type type) + { + var scalar = FindScalar(type); + if (scalar == null) + { + if (type == typeof(object) + || type == typeof(ExpandoObject) + || type == typeof(SortedDictionary) + || type == typeof(Dictionary) + || type.IsNullableValueType() + || !type.IsInstantiable()) + { + throw new InvalidOperationException( + CoreStrings.UnconfigurableType(type.DisplayName(fullName: false), "Scalar")); + } + + scalar = new PropertyConfiguration(type); + _scalars.Add(type, scalar); + } + + return scalar; + } + + /// + /// 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 PropertyConfiguration? FindScalar(Type type) + => _scalars.TryGetValue(type, out var property) + ? property + : 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 @@ -222,10 +271,19 @@ public virtual bool RemoveProperty(Type type) public virtual void AddIgnored(Type type) { if (type.UnwrapNullableType() == typeof(int) - || type.UnwrapNullableType() == typeof(int?) - || type.UnwrapNullableType() == typeof(string) - || type.UnwrapNullableType() == typeof(object) - || type == Model.DefaultPropertyBagType) + || type == typeof(string) + || type == typeof(object) + || type == typeof(ExpandoObject) + || type == typeof(SortedDictionary) + || type == typeof(Dictionary) + || type == typeof(IDictionary) + || type == typeof(IReadOnlyDictionary) + || type == typeof(IDictionary) + || type == typeof(ICollection>) + || type == typeof(IReadOnlyCollection>) + || type == typeof(ICollection) + || type == typeof(IEnumerable>) + || type == typeof(IEnumerable)) { throw new InvalidOperationException( CoreStrings.UnconfigurableType(type.DisplayName(fullName: false), TypeConfigurationType.Ignored)); diff --git a/src/EFCore/Metadata/Internal/PropertyConfiguration.cs b/src/EFCore/Metadata/Internal/PropertyConfiguration.cs index e7198b4af19..64ada8b89c3 100644 --- a/src/EFCore/Metadata/Internal/PropertyConfiguration.cs +++ b/src/EFCore/Metadata/Internal/PropertyConfiguration.cs @@ -15,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public class PropertyConfiguration : AnnotatableBase, IPropertyTypeConfiguration + public class PropertyConfiguration : AnnotatableBase, IScalarTypeConfiguration { private ValueConverter? _valueConverter; diff --git a/src/EFCore/Metadata/RuntimeModel.cs b/src/EFCore/Metadata/RuntimeModel.cs index 5af3cf6b042..2911c083223 100644 --- a/src/EFCore/Metadata/RuntimeModel.cs +++ b/src/EFCore/Metadata/RuntimeModel.cs @@ -9,7 +9,6 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Microsoft.EntityFrameworkCore.Metadata @@ -29,12 +28,11 @@ public class RuntimeModel : AnnotatableBase, IRuntimeModel { private readonly SortedDictionary _entityTypes = new(StringComparer.Ordinal); private readonly Dictionary> _sharedTypes = new(); - private readonly Dictionary _typeConfigurations = new(); + private readonly Dictionary _typeConfigurations = new(); private bool _skipDetectChanges; private readonly ConcurrentDictionary _indexerPropertyInfoMap = new(); private readonly ConcurrentDictionary _clrTypeNameMap = new(); - private readonly ConcurrentDictionary _typeMappings = new(); /// /// Creates a new instance of @@ -139,7 +137,7 @@ private IEnumerable FindEntityTypes(Type type) } /// - /// Adds configuration for a scalar property type. + /// Adds configuration for a scalar type. /// /// The type of value the property will hold. /// The maximum length of data that is allowed in this property type. @@ -151,7 +149,7 @@ private IEnumerable FindEntityTypes(Type type) /// /// The type of a custom set for this property type. /// The newly created property. - public virtual RuntimePropertyTypeConfiguration AddPropertyTypeConfiguration( + public virtual RuntimeScalarTypeConfiguration AddScalarTypeConfiguration( Type clrType, int? maxLength = null, bool? unicode = null, @@ -160,7 +158,7 @@ public virtual RuntimePropertyTypeConfiguration AddPropertyTypeConfiguration( Type? providerPropertyType = null, Type? valueConverterType = null) { - var typeConfiguration = new RuntimePropertyTypeConfiguration( + var typeConfiguration = new RuntimeScalarTypeConfiguration( clrType, maxLength, unicode, @@ -294,19 +292,13 @@ bool IReadOnlyModel.IsShared(Type type) => _sharedTypes.ContainsKey(type); /// - IEnumerable IModel.GetPropertyTypeConfigurations() + IEnumerable IModel.GetScalarTypeConfigurations() => _typeConfigurations.Values; /// - IEnumerable? IModel.FindPropertyTypeConfigurations(Type propertyType) + IScalarTypeConfiguration? IModel.FindScalarTypeConfiguration(Type propertyType) => _typeConfigurations.Count == 0 ? null - : propertyType.GetBaseTypesAndInterfacesInclusive() - .Select(type => _typeConfigurations.GetValueOrDefault(type)!) - .Where(type => type != null); - - /// - CoreTypeMapping? IModel.FindMapping(Type type) - => _typeMappings.GetOrAdd(type, (type, model) => ((IModel)model).GetModelDependencies().TypeMappingSource.FindMapping(type, model), this); + : _typeConfigurations.GetValueOrDefault(propertyType); } } diff --git a/src/EFCore/Metadata/RuntimePropertyTypeConfiguration.cs b/src/EFCore/Metadata/RuntimeScalarTypeConfiguration.cs similarity index 80% rename from src/EFCore/Metadata/RuntimePropertyTypeConfiguration.cs rename to src/EFCore/Metadata/RuntimeScalarTypeConfiguration.cs index 73b23b7f0fd..ecdb828cc12 100644 --- a/src/EFCore/Metadata/RuntimePropertyTypeConfiguration.cs +++ b/src/EFCore/Metadata/RuntimeScalarTypeConfiguration.cs @@ -11,7 +11,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata /// /// Represents a scalar property type. /// - public sealed class RuntimePropertyTypeConfiguration : AnnotatableBase, IPropertyTypeConfiguration + public sealed class RuntimeScalarTypeConfiguration : AnnotatableBase, IScalarTypeConfiguration { private readonly ValueConverter? _valueConverter; @@ -22,7 +22,7 @@ public sealed class RuntimePropertyTypeConfiguration : AnnotatableBase, IPropert /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - public RuntimePropertyTypeConfiguration( + public RuntimeScalarTypeConfiguration( Type clrType, int? maxLength, bool? unicode, @@ -71,28 +71,28 @@ public RuntimePropertyTypeConfiguration( /// [DebuggerStepThrough] - int? IPropertyTypeConfiguration.GetMaxLength() => (int?)this[CoreAnnotationNames.MaxLength]; + int? IScalarTypeConfiguration.GetMaxLength() => (int?)this[CoreAnnotationNames.MaxLength]; /// [DebuggerStepThrough] - bool? IPropertyTypeConfiguration.IsUnicode() => (bool?)this[CoreAnnotationNames.Unicode]; + bool? IScalarTypeConfiguration.IsUnicode() => (bool?)this[CoreAnnotationNames.Unicode]; /// [DebuggerStepThrough] - int? IPropertyTypeConfiguration.GetPrecision() => (int?)this[CoreAnnotationNames.Precision]; + int? IScalarTypeConfiguration.GetPrecision() => (int?)this[CoreAnnotationNames.Precision]; /// [DebuggerStepThrough] - int? IPropertyTypeConfiguration.GetScale() => (int?)this[CoreAnnotationNames.Scale]; + int? IScalarTypeConfiguration.GetScale() => (int?)this[CoreAnnotationNames.Scale]; /// [DebuggerStepThrough] - ValueConverter? IPropertyTypeConfiguration.GetValueConverter() + ValueConverter? IScalarTypeConfiguration.GetValueConverter() => _valueConverter; /// [DebuggerStepThrough] - Type? IPropertyTypeConfiguration.GetProviderClrType() + Type? IScalarTypeConfiguration.GetProviderClrType() => (Type?)this[CoreAnnotationNames.ProviderClrType]; } } diff --git a/src/EFCore/ModelConfigurationBuilder.cs b/src/EFCore/ModelConfigurationBuilder.cs index 3c1fc6e1ce8..80ea23bba34 100644 --- a/src/EFCore/ModelConfigurationBuilder.cs +++ b/src/EFCore/ModelConfigurationBuilder.cs @@ -77,7 +77,7 @@ public virtual ModelConfigurationBuilder IgnoreAny(Type type) /// Marks the given and derived types as corresponding to entity type properties. /// /// The property type to be configured. - /// An object that can be used to provide the configuration defaults for the properties. + /// An object that can be used to configure the properties. public virtual PropertiesConfigurationBuilder Properties() { var property = _modelConfiguration.GetOrAddProperty(typeof(TProperty)); @@ -139,6 +139,72 @@ public virtual ModelConfigurationBuilder Properties( return this; } + /// + /// Marks the given type as a scalar, even when used outside of entity types. + /// + /// The scalar type to be configured. + /// An object that can be used to configure the scalars. + public virtual ScalarConfigurationBuilder Scalars() + { + var scalar = _modelConfiguration.GetOrAddScalar(typeof(TScalar)); + + return new ScalarConfigurationBuilder(scalar); + } + + /// + /// Marks the given type as a scalar, even when used outside of entity types. + /// + /// The scalar type to be configured. + /// An action that performs configuration for the scalars. + /// + /// The same instance so that additional configuration calls can be chained. + /// + public virtual ModelConfigurationBuilder Scalars( + Action> buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + var scalarBuilder = Scalars(); + buildAction(scalarBuilder); + + return this; + } + + /// + /// Marks the given type as a scalar, even when used outside of entity types. + /// + /// The scalar type to be configured. + /// An object that can be used to configure the scalars. + public virtual ScalarConfigurationBuilder Scalars(Type scalarType) + { + Check.NotNull(scalarType, nameof(scalarType)); + + var scalar = _modelConfiguration.GetOrAddScalar(scalarType); + + return new ScalarConfigurationBuilder(scalar); + } + + /// + /// Marks the given type as a scalar, even when used outside of entity types. + /// + /// The scalar type to be configured. + /// An action that performs configuration for the scalars. + /// + /// The same instance so that additional configuration calls can be chained. + /// + public virtual ModelConfigurationBuilder Scalars( + Type scalarType, + Action buildAction) + { + Check.NotNull(scalarType, nameof(scalarType)); + Check.NotNull(buildAction, nameof(buildAction)); + + var scalarBuilder = Scalars(scalarType); + buildAction(scalarBuilder); + + return this; + } + /// /// Creates the configured used to create the model. This is done automatically when using /// ; this method allows it to be run diff --git a/src/EFCore/Storage/TypeMappingSource.cs b/src/EFCore/Storage/TypeMappingSource.cs index d368d5bb805..dc8695ad974 100644 --- a/src/EFCore/Storage/TypeMappingSource.cs +++ b/src/EFCore/Storage/TypeMappingSource.cs @@ -1,10 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Concurrent; using System.Reflection; -using System.Security.Principal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.Extensions.DependencyInjection; @@ -184,69 +182,25 @@ protected TypeMappingSource(TypeMappingSourceDependencies dependencies) /// The type mapping, or if none was found. public override CoreTypeMapping? FindMapping(Type type, IModel model) { - var typeConfigurations = model.FindPropertyTypeConfigurations(type); - var mappingInfo = new TypeMappingInfo(type); + type = type.UnwrapNullableType(); + var typeConfiguration = model.FindScalarTypeConfiguration(type); + TypeMappingInfo mappingInfo; Type? providerClrType = null; ValueConverter? customConverter = null; - if (typeConfigurations != null) + if (typeConfiguration == null) { - foreach (var typeConfiguration in typeConfigurations) - { - if (providerClrType == null) - { - var providerType = typeConfiguration.GetProviderClrType(); - if (providerType != null) - { - providerClrType = providerType.UnwrapNullableType(); - } - } - - if (mappingInfo.IsUnicode == null) - { - var isUnicode = typeConfiguration.IsUnicode(); - if (isUnicode != null) - { - mappingInfo = mappingInfo with { IsUnicode = isUnicode }; - } - } - - if (mappingInfo.Scale == null) - { - var scale = typeConfiguration.GetScale(); - if (scale != null) - { - mappingInfo = mappingInfo with { Scale = scale }; - } - } - - if (mappingInfo.Precision == null) - { - var precision = typeConfiguration.GetPrecision(); - if (precision != null) - { - mappingInfo = mappingInfo with { Precision = precision }; - } - } - - if (mappingInfo.Size == null) - { - var size = typeConfiguration.GetMaxLength(); - if (size != null) - { - mappingInfo = mappingInfo with { Size = size }; - } - } - } - - var firstConfiguration = typeConfigurations.FirstOrDefault(); - customConverter = firstConfiguration?.ClrType == type - ? firstConfiguration.GetValueConverter() - : null; - - if (customConverter != null) - { - mappingInfo = mappingInfo with { ClrType = customConverter.ProviderClrType }; - } + mappingInfo = new TypeMappingInfo(type); + } + else + { + providerClrType = typeConfiguration.GetProviderClrType()?.UnwrapNullableType(); + customConverter = typeConfiguration.GetValueConverter(); + mappingInfo = new TypeMappingInfo( + customConverter?.ProviderClrType ?? type, + unicode: typeConfiguration.IsUnicode(), + size: typeConfiguration.GetMaxLength(), + precision: typeConfiguration.GetPrecision(), + scale: typeConfiguration.GetScale()); } return FindMappingWithConversion(mappingInfo, providerClrType, customConverter); diff --git a/src/Shared/SharedTypeExtensions.cs b/src/Shared/SharedTypeExtensions.cs index 4a59a0ee9b7..76cb5f45161 100644 --- a/src/Shared/SharedTypeExtensions.cs +++ b/src/Shared/SharedTypeExtensions.cs @@ -312,6 +312,11 @@ public static List GetBaseTypesAndInterfacesInclusive(this Type type) type = typesToProcess.Dequeue(); baseTypes.Add(type); + if (type.IsConstructedGenericType) + { + typesToProcess.Enqueue(type.GetGenericTypeDefinition()); + } + if (!type.IsGenericTypeDefinition && !type.IsInterface) { @@ -320,11 +325,6 @@ public static List GetBaseTypesAndInterfacesInclusive(this Type type) typesToProcess.Enqueue(type.BaseType); } - if (type.IsConstructedGenericType) - { - typesToProcess.Enqueue(type.GetGenericTypeDefinition()); - } - foreach (var @interface in GetDeclaredInterfaces(type)) { typesToProcess.Enqueue(@interface); diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index 4b399ad101c..cb7e8292486 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -1,10 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Linq; using System.Linq.Expressions; using System.Reflection; using Microsoft.EntityFrameworkCore.ChangeTracking; @@ -2289,18 +2286,14 @@ private int GetCount(Guid? id, string condition) => throw new NotImplementedException(); public IQueryable GetData(int id) - { - return FromExpression(() => GetData(id)); - } + => FromExpression(() => GetData(id)); public IQueryable GetData() - { - return FromExpression(() => GetData()); - } + => FromExpression(() => GetData()); protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) { - configurationBuilder.Properties().HaveMaxLength(256); + configurationBuilder.Scalars().HaveMaxLength(256); } protected override void OnModelCreating(ModelBuilder modelBuilder) diff --git a/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTestBase.cs b/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTestBase.cs index 6bc720c65a3..7e41a4620fc 100644 --- a/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTestBase.cs +++ b/test/EFCore.Relational.Tests/Storage/RelationalTypeMapperTestBase.cs @@ -44,47 +44,47 @@ protected RelationalTypeMapping GetTypeMapping( { var model = CreateModelBuilder(c => { - var properties = c.Properties(propertyType); + var scalarBuilder = c.Scalars(propertyType); if (maxLength.HasValue) { - properties.HaveMaxLength(maxLength.Value); + scalarBuilder.HaveMaxLength(maxLength.Value); } if (precision.HasValue) { if (scale.HasValue) { - properties.HavePrecision(precision.Value, scale.Value); + scalarBuilder.HavePrecision(precision.Value, scale.Value); } else { - properties.HavePrecision(precision.Value); + scalarBuilder.HavePrecision(precision.Value); } } if (providerType != null) { - properties.HaveConversion(providerType); + scalarBuilder.HaveConversion(providerType); } if (unicode.HasValue) { - properties.AreUnicode(unicode.Value); + scalarBuilder.AreUnicode(unicode.Value); } if (fixedLength.HasValue) { - properties.AreFixedLength(fixedLength.Value); + scalarBuilder.AreFixedLength(fixedLength.Value); } if (storeTypeName != null) { - properties.HaveColumnType(storeTypeName); + scalarBuilder.HaveColumnType(storeTypeName); } }).FinalizeModel(); - return (RelationalTypeMapping)model.FindMapping(propertyType); + return CreateRelationalTypeMappingSource().GetMapping(propertyType, model); } else { diff --git a/test/EFCore.Specification.Tests/ValueConvertersEndToEndTestBase.cs b/test/EFCore.Specification.Tests/ValueConvertersEndToEndTestBase.cs index b43b86fe0a1..7d1c6a96725 100644 --- a/test/EFCore.Specification.Tests/ValueConvertersEndToEndTestBase.cs +++ b/test/EFCore.Specification.Tests/ValueConvertersEndToEndTestBase.cs @@ -840,14 +840,6 @@ public NonNullIntToNullStringConverter() } } - protected class CustomValueComparer : ValueComparer - { - public CustomValueComparer() - : base(false) - { - } - } - protected enum TheExperience : ushort { Jimi, diff --git a/test/EFCore.SqlServer.FunctionalTests/ValueConvertersEndToEndSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/ValueConvertersEndToEndSqlServerTest.cs index d412cb09e48..0a94c3c6921 100644 --- a/test/EFCore.SqlServer.FunctionalTests/ValueConvertersEndToEndSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/ValueConvertersEndToEndSqlServerTest.cs @@ -173,7 +173,7 @@ public virtual void Can_use_custom_converters_without_property() Assert.Equal(@"SELECT [c].[Id], [c].[BoolAsChar], [c].[BoolAsInt], [c].[BoolAsNullableChar], [c].[BoolAsNullableInt], [c].[BoolAsNullableString], [c].[BoolAsString], [c].[BytesAsNullableString], [c].[BytesAsString], [c].[CharAsNullableString], [c].[CharAsString], [c].[DateTimeOffsetToBinary], [c].[DateTimeOffsetToNullableBinary], [c].[DateTimeOffsetToNullableString], [c].[DateTimeOffsetToString], [c].[DateTimeToBinary], [c].[DateTimeToNullableBinary], [c].[DateTimeToNullableString], [c].[DateTimeToString], [c].[EnumToNullableNumber], [c].[EnumToNullableString], [c].[EnumToNumber], [c].[EnumToString], [c].[GuidToBytes], [c].[GuidToNullableBytes], [c].[GuidToNullableString], [c].[GuidToString], [c].[IPAddressToBytes], [c].[IPAddressToNullableBytes], [c].[IPAddressToNullableString], [c].[IPAddressToString], [c].[IntAsLong], [c].[IntAsNullableLong], [c].[NonNullIntToNonNullString], [c].[NonNullIntToNullString], [c].[NonNullStringToNullString], [c].[NullIntToNonNullString], [c].[NullIntToNullString], [c].[NullStringToNonNullString], [c].[NullableBoolAsChar], [c].[NullableBoolAsInt], [c].[NullableBoolAsNullableChar], [c].[NullableBoolAsNullableInt], [c].[NullableBoolAsNullableString], [c].[NullableBoolAsString], [c].[NullableBytesAsNullableString], [c].[NullableBytesAsString], [c].[NullableCharAsNullableString], [c].[NullableCharAsString], [c].[NullableDateTimeOffsetToBinary], [c].[NullableDateTimeOffsetToNullableBinary], [c].[NullableDateTimeOffsetToNullableString], [c].[NullableDateTimeOffsetToString], [c].[NullableDateTimeToBinary], [c].[NullableDateTimeToNullableBinary], [c].[NullableDateTimeToNullableString], [c].[NullableDateTimeToString], [c].[NullableEnumToNullableNumber], [c].[NullableEnumToNullableString], [c].[NullableEnumToNumber], [c].[NullableEnumToString], [c].[NullableGuidToBytes], [c].[NullableGuidToNullableBytes], [c].[NullableGuidToNullableString], [c].[NullableGuidToString], [c].[NullableIPAddressToBytes], [c].[NullableIPAddressToNullableBytes], [c].[NullableIPAddressToNullableString], [c].[NullableIPAddressToString], [c].[NullableIntAsLong], [c].[NullableIntAsNullableLong], [c].[NullableNumberToBytes], [c].[NullableNumberToNullableBytes], [c].[NullableNumberToNullableString], [c].[NullableNumberToString], [c].[NullablePhysicalAddressToBytes], [c].[NullablePhysicalAddressToNullableBytes], [c].[NullablePhysicalAddressToNullableString], [c].[NullablePhysicalAddressToString], [c].[NullableStringToBool], [c].[NullableStringToBytes], [c].[NullableStringToChar], [c].[NullableStringToDateTime], [c].[NullableStringToDateTimeOffset], [c].[NullableStringToEnum], [c].[NullableStringToGuid], [c].[NullableStringToNullableBool], [c].[NullableStringToNullableBytes], [c].[NullableStringToNullableChar], [c].[NullableStringToNullableDateTime], [c].[NullableStringToNullableDateTimeOffset], [c].[NullableStringToNullableEnum], [c].[NullableStringToNullableGuid], [c].[NullableStringToNullableNumber], [c].[NullableStringToNullableTimeSpan], [c].[NullableStringToNumber], [c].[NullableStringToTimeSpan], [c].[NullableTimeSpanToNullableString], [c].[NullableTimeSpanToNullableTicks], [c].[NullableTimeSpanToString], [c].[NullableTimeSpanToTicks], [c].[NullableUriToNullableString], [c].[NullableUriToString], [c].[NumberToBytes], [c].[NumberToNullableBytes], [c].[NumberToNullableString], [c].[NumberToString], [c].[PhysicalAddressToBytes], [c].[PhysicalAddressToNullableBytes], [c].[PhysicalAddressToNullableString], [c].[PhysicalAddressToString], [c].[StringToBool], [c].[StringToBytes], [c].[StringToChar], [c].[StringToDateTime], [c].[StringToDateTimeOffset], [c].[StringToEnum], [c].[StringToGuid], [c].[StringToNullableBool], [c].[StringToNullableBytes], [c].[StringToNullableChar], [c].[StringToNullableDateTime], [c].[StringToNullableDateTimeOffset], [c].[StringToNullableEnum], [c].[StringToNullableGuid], [c].[StringToNullableNumber], [c].[StringToNullableTimeSpan], [c].[StringToNumber], [c].[StringToTimeSpan], [c].[TimeSpanToNullableString], [c].[TimeSpanToNullableTicks], [c].[TimeSpanToString], [c].[TimeSpanToTicks], [c].[UriToNullableString], [c].[UriToString] FROM [ConvertingEntity] AS [c] -WHERE CAST(DATALENGTH(CAST(N'' AS nvarchar(max))) AS int) = 0", Fixture.TestSqlLoggerFactory.SqlStatements[0]); +WHERE CAST(DATALENGTH(CAST(N'' AS nvarchar(max))) AS int) = 1", Fixture.TestSqlLoggerFactory.SqlStatements[0]); } private struct WrappedString @@ -195,7 +195,7 @@ protected override void ConfigureConventions(ModelConfigurationBuilder configura { base.ConfigureConventions(configurationBuilder); - configurationBuilder.Properties().HaveConversion>(); + configurationBuilder.Scalars().HaveConversion(); } protected override ITestStoreFactory TestStoreFactory diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.PropertyMapping.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.PropertyMapping.cs index a9e7876a427..9d2fc6f1cbe 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.PropertyMapping.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.PropertyMapping.cs @@ -241,7 +241,7 @@ public virtual void Does_not_throw_when_interface_generic_type_property_type_is_ public virtual void Does_not_throw_when_interface_base_type_property_type_is_ignored() { var modelBuilder = CreateConventionlessModelBuilder( - configurationBuilder => configurationBuilder.IgnoreAny()); + configurationBuilder => configurationBuilder.IgnoreAny>()); modelBuilder.Entity(typeof(InterfaceNavigationEntity)).HasNoKey(); Validate(modelBuilder); diff --git a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs index 8fb537fefd2..925c0042596 100644 --- a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs @@ -420,6 +420,13 @@ public virtual void Int32_cannot_be_ignored() Assert.Throws(() => CreateModelBuilder(c => c.IgnoreAny())).Message); } + [ConditionalFact] + public virtual void Object_cannot_be_ignored() + { + Assert.Equal(CoreStrings.UnconfigurableType("object", "Ignored"), + Assert.Throws(() => CreateModelBuilder(c => c.IgnoreAny())).Message); + } + [ConditionalFact] public virtual void Can_ignore_a_property_that_is_part_of_explicit_entity_key() { @@ -527,13 +534,6 @@ public virtual void Ignoring_a_base_type_removes_relationships() Assert.Empty(model.GetEntityTypes().Single().GetForeignKeys()); } - [ConditionalFact] - public virtual void Object_cannot_be_ignored() - { - Assert.Equal(CoreStrings.UnconfigurableType("object", "Ignored"), - Assert.Throws(() => CreateModelBuilder(c => c.IgnoreAny())).Message); - } - [ConditionalFact] public virtual void Properties_can_be_made_required() { @@ -938,24 +938,42 @@ private static ExpandoObject DeserializeExpandoObject(string value) return obj; } + private class ExpandoObjectConverter : ValueConverter + { + public ExpandoObjectConverter() + : base(v => (string)((IDictionary)v)["Value"], v => DeserializeExpandoObject(v)) + { + } + } + + private class ExpandoObjectComparer : ValueComparer + { + public ExpandoObjectComparer() + : base((v1, v2) => v1.SequenceEqual(v2), v => v.GetHashCode()) + { + } + } + [ConditionalFact] - public virtual void IEnumerable_properties_can_have_value_converter_configured_by_type() + public virtual void Properties_can_have_value_converter_configured_by_type() { var modelBuilder = CreateModelBuilder(c => { - c.Properties>().HaveMaxLength(20); - c.Properties().HaveConversion(typeof(ExpandoObjectConverter)); + c.Properties(typeof(IWrapped<>)).AreUnicode(false); + c.Properties().HaveMaxLength(20); + c.Properties().HaveConversion(typeof(WrappedStringToStringConverter)); }); - modelBuilder.Entity(); + modelBuilder.Entity(); var model = modelBuilder.FinalizeModel(); var entityType = (IReadOnlyEntityType)model.GetEntityTypes().Single(); - var expandoProperty = entityType.FindProperty(nameof(DynamicProperty.ExpandoObject)); - Assert.Equal(20, expandoProperty.GetMaxLength()); - Assert.IsType(expandoProperty.GetValueConverter()); - Assert.IsType>(expandoProperty.GetValueComparer()); + var wrappedProperty = entityType.FindProperty(nameof(WrappedStringEntity.WrappedString)); + Assert.False(wrappedProperty.IsUnicode()); + Assert.Equal(20, wrappedProperty.GetMaxLength()); + Assert.IsType(wrappedProperty.GetValueConverter()); + Assert.IsType>(wrappedProperty.GetValueComparer()); } [ConditionalFact] @@ -963,28 +981,41 @@ public virtual void Value_converter_configured_on_base_type_is_not_applied() { var modelBuilder = CreateModelBuilder(c => { - c.Properties>().HaveConversion(typeof(ExpandoObjectConverter), typeof(ExpandoObjectComparer)); + c.Properties().HaveConversion(typeof(WrappedStringToStringConverter)); }); - modelBuilder.Entity(); + modelBuilder.Entity(); Assert.Equal(CoreStrings.PropertyNotMapped( - nameof(DynamicProperty), nameof(DynamicProperty.ExpandoObject), nameof(ExpandoObject)), + nameof(WrappedStringEntity), nameof(WrappedStringEntity.WrappedString), nameof(WrappedString)), Assert.Throws(() => modelBuilder.FinalizeModel()).Message); } - private class ExpandoObjectConverter : ValueConverter + private interface IWrapped { - public ExpandoObjectConverter() - : base(v => (string)((IDictionary)v)["Value"], v => DeserializeExpandoObject(v)) - { - } + T Value { get; init; } } - private class ExpandoObjectComparer : ValueComparer + private abstract class WrappedStringBase : IWrapped { - public ExpandoObjectComparer() - : base((v1, v2) => v1.SequenceEqual(v2), v => v.GetHashCode()) + public abstract string Value { get; init; } + } + + private class WrappedString : WrappedStringBase + { + public override string Value { get; init; } + } + + private class WrappedStringEntity + { + public int Id { get; set; } + public WrappedString WrappedString { get; set; } + } + + private class WrappedStringToStringConverter : ValueConverter + { + public WrappedStringToStringConverter() + : base(v => v.Value, v => new WrappedString { Value = v }) { } } @@ -994,14 +1025,14 @@ public virtual void Throws_for_conflicting_base_configurations_by_type() { var modelBuilder = CreateModelBuilder(c => { - c.Properties(); - c.IgnoreAny(); + c.Properties(); + c.IgnoreAny>(); }); Assert.Equal(CoreStrings.TypeConfigurationConflict( - nameof(INotifyPropertyChanged), "Ignored", - nameof(IEnumerable), "Property"), - Assert.Throws(() => modelBuilder.Entity()).Message); + nameof(WrappedString), "Property", + "IWrapped", "Ignored"), + Assert.Throws(() => modelBuilder.Entity()).Message); } [ConditionalFact] @@ -1373,6 +1404,23 @@ protected class StringCollectionEntity public ICollection Property { get; set; } } + [ConditionalFact] + public virtual void Object_cannot_be_configured_as_property() + { + Assert.Equal(CoreStrings.UnconfigurableType("object", "Property"), + Assert.Throws(() => CreateModelBuilder(c => c.Properties())).Message); + } + + [ConditionalFact] + public virtual void Property_bag_cannot_be_configured_as_property() + { + Assert.Equal(CoreStrings.UnconfigurableType("Dictionary", "Property"), + Assert.Throws(() => CreateModelBuilder(c => c.Properties>())).Message); + + Assert.Equal(CoreStrings.UnconfigurableType("IDictionary", "Property"), + Assert.Throws(() => CreateModelBuilder(c => c.Properties>())).Message); + } + [ConditionalFact] protected virtual void Mapping_throws_for_non_ignored_array() { From a3a06cf3ee984bdbb2c9a7e0f3a943c7c57cc9a0 Mon Sep 17 00:00:00 2001 From: AndriySvyryd Date: Sun, 15 Aug 2021 09:17:51 -0700 Subject: [PATCH 3/3] React to PR feedback --- .../Query/Internal/InExpression.cs | 2 +- .../Query/SqlExpressions/InExpression.cs | 4 +- src/EFCore/ModelConfigurationBuilder.cs | 82 +++++++++++++++++-- 3 files changed, 77 insertions(+), 11 deletions(-) diff --git a/src/EFCore.Cosmos/Query/Internal/InExpression.cs b/src/EFCore.Cosmos/Query/Internal/InExpression.cs index 6912c28b612..8808a75ae1a 100644 --- a/src/EFCore.Cosmos/Query/Internal/InExpression.cs +++ b/src/EFCore.Cosmos/Query/Internal/InExpression.cs @@ -26,7 +26,7 @@ public InExpression( SqlExpression item, bool negated, SqlExpression values, - CoreTypeMapping? typeMapping) + CoreTypeMapping typeMapping) : base(typeof(bool), typeMapping) { Item = item; diff --git a/src/EFCore.Relational/Query/SqlExpressions/InExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/InExpression.cs index c864161fde4..1be63a46b80 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/InExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/InExpression.cs @@ -66,7 +66,7 @@ public InExpression( SqlExpression item, SelectExpression subquery, bool negated, - RelationalTypeMapping? typeMapping) + RelationalTypeMapping typeMapping) : this(Check.NotNull(item, nameof(item)), null, Check.NotNull(subquery, nameof(subquery)), negated, typeMapping) { } @@ -82,7 +82,7 @@ public InExpression( SqlExpression item, SqlExpression values, bool negated, - RelationalTypeMapping? typeMapping) + RelationalTypeMapping typeMapping) : this(Check.NotNull(item, nameof(item)), Check.NotNull(values, nameof(values)), null, negated, typeMapping) { } diff --git a/src/EFCore/ModelConfigurationBuilder.cs b/src/EFCore/ModelConfigurationBuilder.cs index 80ea23bba34..91000a8c312 100644 --- a/src/EFCore/ModelConfigurationBuilder.cs +++ b/src/EFCore/ModelConfigurationBuilder.cs @@ -74,7 +74,12 @@ public virtual ModelConfigurationBuilder IgnoreAny(Type type) } /// - /// Marks the given and derived types as corresponding to entity type properties. + /// + /// Marks the given and derived types as corresponding to entity type properties. + /// + /// + /// This can also be called on an interface to apply the configuration to all properties of implementing types. + /// /// /// The property type to be configured. /// An object that can be used to configure the properties. @@ -86,7 +91,12 @@ public virtual PropertiesConfigurationBuilder Properties() } /// - /// Marks the given and derived types as corresponding to entity type properties. + /// + /// Marks the given and derived types as corresponding to entity type properties. + /// + /// + /// This can also be called on an interface to apply the configuration to all properties of implementing types. + /// /// /// The property type to be configured. /// An action that performs configuration of the property. @@ -105,7 +115,13 @@ public virtual ModelConfigurationBuilder Properties( } /// - /// Marks the given and derived types as corresponding to entity type properties. + /// + /// Marks the given and derived types as corresponding to entity type properties. + /// + /// + /// This can also be called on an interface or an unbound generic type to apply the configuration to all + /// properties of implementing and constructed types. + /// /// /// The property type to be configured. /// An object that can be used to configure the property. @@ -119,7 +135,13 @@ public virtual PropertiesConfigurationBuilder Properties(Type propertyType) } /// - /// Marks the given and derived types as corresponding to entity type properties. + /// + /// Marks the given and derived types as corresponding to entity type properties. + /// + /// + /// This can also be called on an interface or an unbound generic type to apply the configuration to all + /// properties of implementing and constructed types. + /// /// /// The property type to be configured. /// An action that performs configuration of the property. @@ -140,7 +162,18 @@ public virtual ModelConfigurationBuilder Properties( } /// - /// Marks the given type as a scalar, even when used outside of entity types. + /// + /// Marks the given type as a scalar, even when used outside of entity types. This allows values of this type + /// to be used in queries that are not referencing property of this type. + /// + /// + /// Unlike this method should only be called on a non-nullable concrete type. + /// Calling it on a base type will not apply the configuration to the derived types. + /// + /// + /// Calling this is rarely needed. If there are properties of the given type calling + /// should be enough in most cases. + /// /// /// The scalar type to be configured. /// An object that can be used to configure the scalars. @@ -152,7 +185,18 @@ public virtual ScalarConfigurationBuilder Scalars() } /// - /// Marks the given type as a scalar, even when used outside of entity types. + /// + /// Marks the given type as a scalar, even when used outside of entity types. This allows values of this type + /// to be used in queries that are not referencing property of this type. + /// + /// + /// Unlike this method should only be called on a non-nullable concrete type. + /// Calling it on a base type will not apply the configuration to the derived types. + /// + /// + /// Calling this is rarely needed. If there are properties of the given type calling + /// should be enough in most cases. + /// /// /// The scalar type to be configured. /// An action that performs configuration for the scalars. @@ -171,7 +215,18 @@ public virtual ModelConfigurationBuilder Scalars( } /// - /// Marks the given type as a scalar, even when used outside of entity types. + /// + /// Marks the given type as a scalar, even when used outside of entity types. This allows values of this type + /// to be used in queries that are not referencing property of this type. + /// + /// + /// Unlike this method should only be called on a non-nullable concrete type. + /// Calling it on a base type will not apply the configuration to the derived types. + /// + /// + /// Calling this is rarely needed. If there are properties of the given type calling + /// should be enough in most cases. + /// /// /// The scalar type to be configured. /// An object that can be used to configure the scalars. @@ -185,7 +240,18 @@ public virtual ScalarConfigurationBuilder Scalars(Type scalarType) } /// - /// Marks the given type as a scalar, even when used outside of entity types. + /// + /// Marks the given type as a scalar, even when used outside of entity types. This allows values of this type + /// to be used in queries that are not referencing property of this type. + /// + /// + /// Unlike this method should only be called on a non-nullable concrete type. + /// Calling it on a base type will not apply the configuration to the derived types. + /// + /// + /// Calling this is rarely needed. If there are properties of the given type calling + /// should be enough in most cases. + /// /// /// The scalar type to be configured. /// An action that performs configuration for the scalars.