From a424b3eabc584556ae0af1e9ee4671cbbb4a273b Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 4 Jan 2024 10:09:33 -0800 Subject: [PATCH] Introduce liftable constants to shaper to prepare for precompilation Part of #25009 remove unnecessary test changes cleanup --- EFCore.sln.DotSettings | 1 + .../Query/Internal/InMemoryQueryExpression.cs | 1 - .../Internal/ProxyBindingInterceptor.cs | 2 + ...ntityFrameworkRelationalServicesBuilder.cs | 5 + .../IRelationalLiftableConstantFactory.cs | 28 + .../GroupBySingleQueryingEnumerable.cs | 12 +- .../GroupBySplitQueryingEnumerable.cs | 12 +- ...lLiftableConstantExpressionDependencies.cs | 32 + ...ionalProjectionBindingExpressionVisitor.cs | 8 +- .../Internal/SingleQueryingEnumerable.cs | 34 + .../RelationalLiftableConstantFactory.cs | 54 ++ .../RelationalLiftableConstantProcessor.cs | 44 ++ ...onalMaterializerLiftableConstantContext.cs | 18 + .../RelationalQueryCompilationContext.cs | 3 + ...ocessingExpressionVisitor.ClientMethods.cs | 212 +++++- ...sitor.ShaperProcessingExpressionVisitor.cs | 657 +++++++++++++----- ...alShapedQueryCompilingExpressionVisitor.cs | 154 ++-- ...yCompilingExpressionVisitorDependencies.cs | 9 +- ...lationalSqlTranslatingExpressionVisitor.cs | 20 +- .../Query/SqlExpressions/SelectExpression.cs | 94 +-- src/EFCore.Relational/Storage/ReaderColumn.cs | 20 +- .../Storage/ReaderColumn`.cs | 8 +- .../SqlServerJsonHierarchyIdReaderWriter.cs | 5 + ...SqlServerJsonSqlHierarchyIdReaderWriter.cs | 5 + .../SqlServerJsonGeometryWktReaderWriter.cs | 5 + ...yableMethodTranslatingExpressionVisitor.cs | 1 - ...qlServerSqlTranslatingExpressionVisitor.cs | 38 +- ...yableMethodTranslatingExpressionVisitor.cs | 1 - .../SqliteJsonByteArrayReaderWriter.cs | 5 + .../SqliteJsonDateTimeOffsetReaderWriter.cs | 5 + .../SqliteJsonDateTimeReaderWriter.cs | 5 + .../Internal/SqliteJsonDecimalReaderWriter.cs | 5 + .../Internal/SqliteJsonGuidReaderWriter.cs | 5 + .../Json/SqliteJsonGeometryWktReaderWriter.cs | 5 + src/EFCore/ChangeTracking/ListComparer.cs | 9 +- .../NullableValueTypeListComparer.cs | 9 +- src/EFCore/ChangeTracking/ValueComparer.cs | 5 + src/EFCore/ChangeTracking/ValueComparer`.cs | 29 + src/EFCore/EFCore.csproj | 1 + .../EntityFrameworkServicesBuilder.cs | 7 +- .../Infrastructure/Internal/LazyLoader.cs | 6 +- src/EFCore/Internal/IInjectableService.cs | 2 +- .../Metadata/ContextParameterBinding.cs | 18 + .../DependencyInjectionParameterBinding.cs | 17 + .../Metadata/EntityTypeParameterBinding.cs | 16 + src/EFCore/Metadata/FactoryMethodBinding.cs | 28 +- .../Metadata/ServiceParameterBinding.cs | 13 +- src/EFCore/Query/ILiftableConstantFactory.cs | 28 + .../Query/ILiftableConstantProcessor.cs | 54 ++ .../Internal/EntityMaterializerSource.cs | 38 +- .../LiftableConstantExpressionDependencies.cs | 32 + ...ingExpressionVisitor.ExpressionVisitors.cs | 19 +- .../Query/LiftableConstantExpression.cs | 104 +++ .../LiftableConstantExpressionHelpers.cs | 285 ++++++++ src/EFCore/Query/LiftableConstantFactory.cs | 35 + src/EFCore/Query/LiftableConstantProcessor.cs | 489 +++++++++++++ .../MaterializerLiftableConstantContext.cs | 15 + src/EFCore/Query/QueryCompilationContext.cs | 102 ++- .../QueryCompilationContextDependencies.cs | 14 + .../Query/ReplacingExpressionVisitor.cs | 12 +- .../ShapedQueryCompilingExpressionVisitor.cs | 268 +++++-- ...yCompilingExpressionVisitorDependencies.cs | 38 +- .../Query/StructuralTypeShaperExpression.cs | 33 +- .../Storage/Json/JsonBoolReaderWriter.cs | 13 +- .../Storage/Json/JsonByteArrayReaderWriter.cs | 5 + .../Storage/Json/JsonByteReaderWriter.cs | 5 + .../Storage/Json/JsonCastValueReaderWriter.cs | 6 + .../Storage/Json/JsonCharReaderWriter.cs | 5 + .../Json/JsonCollectionReaderWriter.cs | 6 + .../Json/JsonConvertedValueReaderWriter.cs | 9 + .../Storage/Json/JsonDateOnlyReaderWriter.cs | 5 + .../Json/JsonDateTimeOffsetReaderWriter.cs | 5 + .../Storage/Json/JsonDateTimeReaderWriter.cs | 5 + .../Storage/Json/JsonDecimalReaderWriter.cs | 5 + .../Storage/Json/JsonDoubleReaderWriter.cs | 5 + .../Storage/Json/JsonFloatReaderWriter.cs | 5 + .../Storage/Json/JsonGuidReaderWriter.cs | 5 + .../Storage/Json/JsonInt16ReaderWriter.cs | 5 + .../Storage/Json/JsonInt32ReaderWriter.cs | 5 + .../Storage/Json/JsonInt64ReaderWriter.cs | 5 + .../Storage/Json/JsonNullReaderWriter.cs | 5 + ...sonNullableStructCollectionReaderWriter.cs | 7 + .../Storage/Json/JsonSByteReaderWriter.cs | 5 + .../Json/JsonSignedEnumReaderWriter.cs | 5 + .../Storage/Json/JsonStringReaderWriter.cs | 5 + .../Storage/Json/JsonTimeOnlyReaderWriter.cs | 5 + .../Storage/Json/JsonTimeSpanReaderWriter.cs | 5 + .../Storage/Json/JsonUInt16ReaderWriter.cs | 5 + .../Storage/Json/JsonUInt32ReaderWriter.cs | 5 + .../Storage/Json/JsonUInt64ReaderWriter.cs | 5 + .../Json/JsonUnsignedEnumReaderWriter.cs | 5 + .../Storage/Json/JsonValueReaderWriter.cs | 5 + .../Json/JsonWarningEnumReaderWriter.cs | 5 + .../ValueConversion/BoolToStringConverter.cs | 32 +- .../ValueConversion/StringToBytesConverter.cs | 29 +- .../Storage/ValueConversion/ValueConverter.cs | 5 + .../ValueConversion/ValueConverter`.cs | 16 + src/Shared/ExpressionExtensions.cs | 12 +- ...AdvancedMappingsQueryRelationalTestBase.cs | 3 +- .../Query/AdHocJsonQueryTestBase.cs | 3 +- .../Query/AdHocQuerySplittingQueryTestBase.cs | 3 +- .../Query/JsonQueryTestBase.cs | 6 +- .../OwnedEntityQueryRelationalTestBase.cs | 6 +- .../Query/UdfDbFunctionTestBase.cs | 12 +- .../Query/Internal/BufferedDataReaderTest.cs | 9 +- .../RelationalApiConsistencyTest.cs | 20 +- .../ApiConsistencyTestBase.cs | 10 + .../JsonTypesTestBase.cs | 5 + .../MaterializationInterceptionTestBase.cs | 1 + .../Query/AdHocMiscellaneousQueryTestBase.cs | 18 +- .../Query/ComplexNavigationsQueryTestBase.cs | 12 +- .../Query/GearsOfWarQueryTestBase.cs | 17 +- ...SharedPrimitiveCollectionsQueryTestBase.cs | 2 +- .../Query/NorthwindGroupByQueryTestBase.cs | 6 +- .../NorthwindMiscellaneousQueryTestBase.cs | 3 +- .../Query/OwnedEntityQueryTestBase.cs | 6 +- .../Query/OwnedQueryTestBase.cs | 3 +- .../Query/SpatialQueryTestBase.cs | 58 +- .../Scaffolding/CompiledModelTestBase.cs | 5 + .../SpatialTestBase.cs | 6 +- .../AdHocMiscellaneousQuerySqlServerTest.cs | 2 +- ...dPrimitiveCollectionsQuerySqlServerTest.cs | 9 +- .../Query/QueryLoggingSqlServerTest.cs | 2 +- .../Query/RawSqlServerTest.cs | 35 +- .../SpatialQuerySqlServerGeographyTest.cs | 2 +- .../SpatialQuerySqlServerGeometryTest.cs | 2 +- .../Baselines/BigModel/ManyTypesEntityType.cs | 4 +- .../ManyTypesEntityType.cs | 4 +- ...lServerValueGenerationScenariosTestBase.cs | 2 +- .../Baselines/BigModel/ManyTypesEntityType.cs | 4 +- .../ManyTypesEntityType.cs | 4 +- .../Metadata/Internal/PropertyTest.cs | 33 +- .../Query/EntityMaterializerSourceTest.cs | 4 +- ...vigationExpandingExpressionVisitorTests.cs | 2 + 134 files changed, 3288 insertions(+), 534 deletions(-) create mode 100644 src/EFCore.Relational/Query/IRelationalLiftableConstantFactory.cs create mode 100644 src/EFCore.Relational/Query/Internal/RelationalLiftableConstantExpressionDependencies.cs create mode 100644 src/EFCore.Relational/Query/RelationalLiftableConstantFactory.cs create mode 100644 src/EFCore.Relational/Query/RelationalLiftableConstantProcessor.cs create mode 100644 src/EFCore.Relational/Query/RelationalMaterializerLiftableConstantContext.cs create mode 100644 src/EFCore/Query/ILiftableConstantFactory.cs create mode 100644 src/EFCore/Query/ILiftableConstantProcessor.cs create mode 100644 src/EFCore/Query/Internal/LiftableConstantExpressionDependencies.cs create mode 100644 src/EFCore/Query/LiftableConstantExpression.cs create mode 100644 src/EFCore/Query/LiftableConstantExpressionHelpers.cs create mode 100644 src/EFCore/Query/LiftableConstantFactory.cs create mode 100644 src/EFCore/Query/LiftableConstantProcessor.cs create mode 100644 src/EFCore/Query/MaterializerLiftableConstantContext.cs diff --git a/EFCore.sln.DotSettings b/EFCore.sln.DotSettings index d1ddc281f56..6881961da98 100644 --- a/EFCore.sln.DotSettings +++ b/EFCore.sln.DotSettings @@ -309,6 +309,7 @@ The .NET Foundation licenses this file to you under the MIT license. True True True + True True True True diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs index 5d833b29426..791e04c0593 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.EntityFrameworkCore.InMemory.Internal; -using Microsoft.EntityFrameworkCore.Internal; using ExpressionExtensions = Microsoft.EntityFrameworkCore.Infrastructure.ExpressionExtensions; namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal; diff --git a/src/EFCore.Proxies/Proxies/Internal/ProxyBindingInterceptor.cs b/src/EFCore.Proxies/Proxies/Internal/ProxyBindingInterceptor.cs index ad8acaeceb4..ca081b841de 100644 --- a/src/EFCore.Proxies/Proxies/Internal/ProxyBindingInterceptor.cs +++ b/src/EFCore.Proxies/Proxies/Internal/ProxyBindingInterceptor.cs @@ -52,6 +52,7 @@ public virtual InstantiationBinding ModifyBinding(InstantiationBindingIntercepti return new FactoryMethodBinding( _proxyFactory, + Expression.Constant(_proxyFactory, typeof(IProxyFactory)), CreateLazyLoadingProxyMethod, new List { @@ -67,6 +68,7 @@ public virtual InstantiationBinding ModifyBinding(InstantiationBindingIntercepti { return new FactoryMethodBinding( _proxyFactory, + Expression.Constant(_proxyFactory, typeof(IProxyFactory)), CreateProxyMethod, new List { diff --git a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs index 93cd941c2f5..6d4ad282930 100644 --- a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs +++ b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs @@ -63,6 +63,7 @@ public static readonly IDictionary RelationalServi { typeof(IQuerySqlGeneratorFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IModificationCommandFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(ISqlAliasManagerFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, + { typeof(IRelationalLiftableConstantFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(ICommandBatchPreparer), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IModificationCommandBatchFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IRelationalSqlTranslatingExpressionVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, @@ -189,6 +190,9 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices() TryAdd(); TryAdd(); TryAdd(); + TryAdd(p => p.GetRequiredService()); + TryAdd(); + TryAdd(); ServiceCollectionMap.GetInfrastructure() .AddDependencySingleton() @@ -204,6 +208,7 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices() .AddDependencySingleton() .AddDependencySingleton() .AddDependencySingleton() + .AddDependencySingleton() .AddDependencyScoped() .AddDependencyScoped() .AddDependencyScoped() diff --git a/src/EFCore.Relational/Query/IRelationalLiftableConstantFactory.cs b/src/EFCore.Relational/Query/IRelationalLiftableConstantFactory.cs new file mode 100644 index 00000000000..2c6da7cbebc --- /dev/null +++ b/src/EFCore.Relational/Query/IRelationalLiftableConstantFactory.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.EntityFrameworkCore.Query; + +/// +/// This is an experimental API used by the Entity Framework Core feature and it is 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. +/// +[Experimental(EFDiagnostics.PrecompiledQueryExperimental)] +public interface IRelationalLiftableConstantFactory : ILiftableConstantFactory +{ + /// + /// This is an experimental API used by the Entity Framework Core feature and it is 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. + /// + LiftableConstantExpression CreateLiftableConstant( + ConstantExpression originalExpression, + Expression> resolverExpression, + string variableName, + Type type); +} diff --git a/src/EFCore.Relational/Query/Internal/GroupBySingleQueryingEnumerable.cs b/src/EFCore.Relational/Query/Internal/GroupBySingleQueryingEnumerable.cs index b53b4dc5d7c..73d4960150f 100644 --- a/src/EFCore.Relational/Query/Internal/GroupBySingleQueryingEnumerable.cs +++ b/src/EFCore.Relational/Query/Internal/GroupBySingleQueryingEnumerable.cs @@ -20,7 +20,7 @@ public class GroupBySingleQueryingEnumerable private readonly IReadOnlyList? _readerColumns; private readonly Func _keySelector; private readonly Func _keyIdentifier; - private readonly IReadOnlyList _keyIdentifierValueComparers; + private readonly IReadOnlyList> _keyIdentifierValueComparers; private readonly Func _elementSelector; private readonly Type _contextType; private readonly IDiagnosticsLogger _queryLogger; @@ -40,7 +40,7 @@ public GroupBySingleQueryingEnumerable( IReadOnlyList? readerColumns, Func keySelector, Func keyIdentifier, - IReadOnlyList keyIdentifierValueComparers, + IReadOnlyList> keyIdentifierValueComparers, Func elementSelector, Type contextType, bool standAloneStateManager, @@ -139,12 +139,12 @@ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } - private static bool CompareIdentifiers(IReadOnlyList valueComparers, object[] left, object[] right) + private static bool CompareIdentifiers(IReadOnlyList> valueComparers, object[] left, object[] right) { // Ignoring size check on all for perf as they should be same unless bug in code. for (var i = 0; i < left.Length; i++) { - if (!valueComparers[i].Equals(left[i], right[i])) + if (!valueComparers[i](left[i], right[i])) { return false; } @@ -160,7 +160,7 @@ private sealed class Enumerator : IEnumerator> private readonly IReadOnlyList? _readerColumns; private readonly Func _keySelector; private readonly Func _keyIdentifier; - private readonly IReadOnlyList _keyIdentifierValueComparers; + private readonly IReadOnlyList> _keyIdentifierValueComparers; private readonly Func _elementSelector; private readonly Type _contextType; private readonly IDiagnosticsLogger _queryLogger; @@ -344,7 +344,7 @@ private sealed class AsyncEnumerator : IAsyncEnumerator? _readerColumns; private readonly Func _keySelector; private readonly Func _keyIdentifier; - private readonly IReadOnlyList _keyIdentifierValueComparers; + private readonly IReadOnlyList> _keyIdentifierValueComparers; private readonly Func _elementSelector; private readonly Type _contextType; private readonly IDiagnosticsLogger _queryLogger; diff --git a/src/EFCore.Relational/Query/Internal/GroupBySplitQueryingEnumerable.cs b/src/EFCore.Relational/Query/Internal/GroupBySplitQueryingEnumerable.cs index c8e21e4f499..7c610c47b3a 100644 --- a/src/EFCore.Relational/Query/Internal/GroupBySplitQueryingEnumerable.cs +++ b/src/EFCore.Relational/Query/Internal/GroupBySplitQueryingEnumerable.cs @@ -20,7 +20,7 @@ public class GroupBySplitQueryingEnumerable private readonly IReadOnlyList? _readerColumns; private readonly Func _keySelector; private readonly Func _keyIdentifier; - private readonly IReadOnlyList _keyIdentifierValueComparers; + private readonly IReadOnlyList> _keyIdentifierValueComparers; private readonly Func _elementSelector; private readonly Action? _relatedDataLoaders; private readonly Func? _relatedDataLoadersAsync; @@ -42,7 +42,7 @@ public GroupBySplitQueryingEnumerable( IReadOnlyList? readerColumns, Func keySelector, Func keyIdentifier, - IReadOnlyList keyIdentifierValueComparers, + IReadOnlyList> keyIdentifierValueComparers, Func elementSelector, Action? relatedDataLoaders, Func? relatedDataLoadersAsync, @@ -145,12 +145,12 @@ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } - private static bool CompareIdentifiers(IReadOnlyList valueComparers, object[] left, object[] right) + private static bool CompareIdentifiers(IReadOnlyList> valueComparers, object[] left, object[] right) { // Ignoring size check on all for perf as they should be same unless bug in code. for (var i = 0; i < left.Length; i++) { - if (!valueComparers[i].Equals(left[i], right[i])) + if (!valueComparers[i](left[i], right[i])) { return false; } @@ -166,7 +166,7 @@ private sealed class Enumerator : IEnumerator> private readonly IReadOnlyList? _readerColumns; private readonly Func _keySelector; private readonly Func _keyIdentifier; - private readonly IReadOnlyList _keyIdentifierValueComparers; + private readonly IReadOnlyList> _keyIdentifierValueComparers; private readonly Func _elementSelector; private readonly Action? _relatedDataLoaders; private readonly Type _contextType; @@ -340,7 +340,7 @@ private sealed class AsyncEnumerator : IAsyncEnumerator? _readerColumns; private readonly Func _keySelector; private readonly Func _keyIdentifier; - private readonly IReadOnlyList _keyIdentifierValueComparers; + private readonly IReadOnlyList> _keyIdentifierValueComparers; private readonly Func _elementSelector; private readonly Func? _relatedDataLoaders; private readonly Type _contextType; diff --git a/src/EFCore.Relational/Query/Internal/RelationalLiftableConstantExpressionDependencies.cs b/src/EFCore.Relational/Query/Internal/RelationalLiftableConstantExpressionDependencies.cs new file mode 100644 index 00000000000..a4babebadaf --- /dev/null +++ b/src/EFCore.Relational/Query/Internal/RelationalLiftableConstantExpressionDependencies.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Internal; + +/// +/// +/// Service dependencies parameter class for +/// +/// +/// This type is typically used by database providers (and other extensions). It is generally +/// not used in application code. +/// +/// +/// +/// +/// Do not construct instances of this class directly from either provider or application code as the +/// constructor signature may change as new dependencies are added. Instead, use this type in +/// your constructor so that an instance will be created and injected automatically by the +/// dependency injection container. To create an instance with some dependent services replaced, +/// first resolve the object from the dependency injection container, then replace selected +/// services using the C# 'with' operator. 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 . +/// +/// +public sealed record RelationalLiftableConstantExpressionDependencies +{ +} diff --git a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs index 2d9ef438560..42e34d8c4c5 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs @@ -653,7 +653,13 @@ private ProjectionBindingExpression AddClientProjection(Expression expression, T return new ProjectionBindingExpression(_selectExpression, existingIndex, type); } - private static T GetParameterValue(QueryContext queryContext, string parameterName) + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static T GetParameterValue(QueryContext queryContext, string parameterName) #pragma warning restore IDE0052 // Remove unread private members => (T)queryContext.ParameterValues[parameterName]!; diff --git a/src/EFCore.Relational/Query/Internal/SingleQueryingEnumerable.cs b/src/EFCore.Relational/Query/Internal/SingleQueryingEnumerable.cs index 53a3c372294..560179079fe 100644 --- a/src/EFCore.Relational/Query/Internal/SingleQueryingEnumerable.cs +++ b/src/EFCore.Relational/Query/Internal/SingleQueryingEnumerable.cs @@ -6,6 +6,40 @@ 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 static class SingleQueryingEnumerable +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static SingleQueryingEnumerable Create( + RelationalQueryContext relationalQueryContext, + RelationalCommandCache relationalCommandCache, + IReadOnlyList? readerColumns, + Func shaper, + Type contextType, + bool standAloneStateManager, + bool detailedErrorsEnabled, + bool threadSafetyChecksEnabled) + => new( + relationalQueryContext, + relationalCommandCache, + readerColumns, + shaper, + contextType, + standAloneStateManager, + detailedErrorsEnabled, + threadSafetyChecksEnabled); +} + /// /// 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.Relational/Query/RelationalLiftableConstantFactory.cs b/src/EFCore.Relational/Query/RelationalLiftableConstantFactory.cs new file mode 100644 index 00000000000..17b22c4b223 --- /dev/null +++ b/src/EFCore.Relational/Query/RelationalLiftableConstantFactory.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Query.Internal; + +namespace Microsoft.EntityFrameworkCore.Query; + +/// +/// This is an experimental API used by the Entity Framework Core feature and it is 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. +/// +[Experimental(EFDiagnostics.PrecompiledQueryExperimental)] +public class RelationalLiftableConstantFactory : LiftableConstantFactory, IRelationalLiftableConstantFactory +{ + /// + /// This is an experimental API used by the Entity Framework Core feature and it is 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 RelationalLiftableConstantFactory( +#pragma warning disable EF1001 // Internal EF Core API usage. + LiftableConstantExpressionDependencies dependencies, +#pragma warning restore EF1001 // Internal EF Core API usage. + RelationalLiftableConstantExpressionDependencies relationalDependencies) + : base(dependencies) + { + RelationalDependencies = relationalDependencies; + } + + /// + /// This is an experimental API used by the Entity Framework Core feature and it is 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 RelationalLiftableConstantExpressionDependencies RelationalDependencies { get; } + + /// + /// This is an experimental API used by the Entity Framework Core feature and it is 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 LiftableConstantExpression CreateLiftableConstant( + ConstantExpression originalExpression, + Expression> resolverExpression, + string variableName, + Type type) + => new(originalExpression, resolverExpression, variableName, type); +} diff --git a/src/EFCore.Relational/Query/RelationalLiftableConstantProcessor.cs b/src/EFCore.Relational/Query/RelationalLiftableConstantProcessor.cs new file mode 100644 index 00000000000..1e942372ab0 --- /dev/null +++ b/src/EFCore.Relational/Query/RelationalLiftableConstantProcessor.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.EntityFrameworkCore.Query; + +/// +/// This is an experimental API used by the Entity Framework Core feature and it is 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. +/// +[Experimental(EFDiagnostics.PrecompiledQueryExperimental)] +public class RelationalLiftableConstantProcessor : LiftableConstantProcessor +{ + private readonly RelationalMaterializerLiftableConstantContext _relationalMaterializerLiftableConstantContext; + + /// + /// This is an experimental API used by the Entity Framework Core feature and it is 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 RelationalLiftableConstantProcessor( + ShapedQueryCompilingExpressionVisitorDependencies dependencies, + RelationalShapedQueryCompilingExpressionVisitorDependencies relationalDependencies) + : base(dependencies) + => _relationalMaterializerLiftableConstantContext = new(dependencies, relationalDependencies); + + /// + protected override ConstantExpression InlineConstant(LiftableConstantExpression liftableConstant) + { + if (liftableConstant.ResolverExpression is Expression> + resolverExpression) + { + var resolver = resolverExpression.Compile(preferInterpretation: true); + var value = resolver(_relationalMaterializerLiftableConstantContext); + return Expression.Constant(value, liftableConstant.Type); + } + + return base.InlineConstant(liftableConstant); + } +} diff --git a/src/EFCore.Relational/Query/RelationalMaterializerLiftableConstantContext.cs b/src/EFCore.Relational/Query/RelationalMaterializerLiftableConstantContext.cs new file mode 100644 index 00000000000..e54e2b507a2 --- /dev/null +++ b/src/EFCore.Relational/Query/RelationalMaterializerLiftableConstantContext.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.EntityFrameworkCore.Query; + +/// +/// This is an experimental API used by the Entity Framework Core feature and it is 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. +/// +[Experimental(EFDiagnostics.PrecompiledQueryExperimental)] +public record RelationalMaterializerLiftableConstantContext( + ShapedQueryCompilingExpressionVisitorDependencies Dependencies, + RelationalShapedQueryCompilingExpressionVisitorDependencies RelationalDependencies) + : MaterializerLiftableConstantContext(Dependencies); diff --git a/src/EFCore.Relational/Query/RelationalQueryCompilationContext.cs b/src/EFCore.Relational/Query/RelationalQueryCompilationContext.cs index 970c8eda4b2..113868cc4d0 100644 --- a/src/EFCore.Relational/Query/RelationalQueryCompilationContext.cs +++ b/src/EFCore.Relational/Query/RelationalQueryCompilationContext.cs @@ -49,4 +49,7 @@ public RelationalQueryCompilationContext( /// A manager for SQL aliases, capable of generate uniquified table aliases. /// public virtual SqlAliasManager SqlAliasManager { get; } + + /// + public override bool SupportsPrecompiledQuery => true; } diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ClientMethods.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ClientMethods.cs index 33b01fd885a..07df5d5c415 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ClientMethods.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ClientMethods.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 System.Collections; using System.Runtime.CompilerServices; using System.Text.Json; using Microsoft.EntityFrameworkCore.Internal; @@ -11,7 +12,14 @@ namespace Microsoft.EntityFrameworkCore.Query; public partial class RelationalShapedQueryCompilingExpressionVisitor { - private sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisitor + /// + /// 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 sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisitor { private static readonly MethodInfo ThrowReadValueExceptionMethod = typeof(ShaperProcessingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(ThrowReadValueException))!; @@ -71,8 +79,15 @@ private static readonly MethodInfo MaterializeJsonEntityCollectionMethodInfo private static readonly MethodInfo InverseCollectionFixupMethod = typeof(ShaperProcessingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(InverseCollectionFixup))!; + /// + /// 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. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static TValue ThrowReadValueException( + [EntityFrameworkInternal] + public static TValue ThrowReadValueException( Exception exception, object? value, Type expectedType, @@ -122,7 +137,14 @@ private static TValue ThrowExtractJsonPropertyException(Exception except exception); } - private static void IncludeReference( + /// + /// 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 static void IncludeReference( QueryContext queryContext, TEntity entity, TIncludedEntity? relatedEntity, @@ -160,7 +182,14 @@ private static void IncludeReference } } - private static void InitializeIncludeCollection( + /// + /// 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 static void InitializeIncludeCollection( int collectionId, QueryContext queryContext, DbDataReader dbDataReader, @@ -201,7 +230,14 @@ private static void InitializeIncludeCollection( resultCoordinator.SetSingleQueryCollectionContext(collectionId, collectionMaterializationContext); } - private static void PopulateIncludeCollection( + /// + /// 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 static void PopulateIncludeCollection( int collectionId, QueryContext queryContext, DbDataReader dbDataReader, @@ -209,9 +245,9 @@ private static void PopulateIncludeCollection Func parentIdentifier, Func outerIdentifier, Func selfIdentifier, - IReadOnlyList parentIdentifierValueComparers, - IReadOnlyList outerIdentifierValueComparers, - IReadOnlyList selfIdentifierValueComparers, + IReadOnlyList> parentIdentifierValueComparers, + IReadOnlyList> outerIdentifierValueComparers, + IReadOnlyList> selfIdentifierValueComparers, Func innerShaper, INavigationBase? inverseNavigation, Action fixup, @@ -319,7 +355,14 @@ void GenerateCurrentElementIfPending() } } - private static void InitializeSplitIncludeCollection( + /// + /// 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 static void InitializeSplitIncludeCollection( int collectionId, QueryContext queryContext, DbDataReader parentDataReader, @@ -358,7 +401,14 @@ private static void InitializeSplitIncludeCollection resultCoordinator.SetSplitQueryCollectionContext(collectionId, splitQueryCollectionContext); } - private static void PopulateSplitIncludeCollection( + /// + /// 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 static void PopulateSplitIncludeCollection( int collectionId, RelationalQueryContext queryContext, IExecutionStrategy executionStrategy, @@ -367,7 +417,7 @@ private static void PopulateSplitIncludeCollection childIdentifier, - IReadOnlyList identifierValueComparers, + IReadOnlyList> identifierValueComparers, Func innerShaper, Action? relatedDataLoaders, INavigationBase? inverseNavigation, @@ -442,7 +492,14 @@ static RelationalDataReader InitializeReader( } } - private static async Task PopulateSplitIncludeCollectionAsync( + /// + /// 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 static async Task PopulateSplitIncludeCollectionAsync( int collectionId, RelationalQueryContext queryContext, IExecutionStrategy executionStrategy, @@ -451,7 +508,7 @@ private static async Task PopulateSplitIncludeCollectionAsync childIdentifier, - IReadOnlyList identifierValueComparers, + IReadOnlyList> identifierValueComparers, Func innerShaper, Func? relatedDataLoaders, INavigationBase? inverseNavigation, @@ -538,7 +595,14 @@ static async Task InitializeReaderAsync( } } - private static TCollection InitializeCollection( + /// + /// 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 static TCollection InitializeCollection( int collectionId, QueryContext queryContext, DbDataReader dbDataReader, @@ -560,7 +624,14 @@ private static TCollection InitializeCollection( return (TCollection)collection; } - private static void PopulateCollection( + /// + /// 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 static void PopulateCollection( int collectionId, QueryContext queryContext, DbDataReader dbDataReader, @@ -568,9 +639,9 @@ private static void PopulateCollection( Func parentIdentifier, Func outerIdentifier, Func selfIdentifier, - IReadOnlyList parentIdentifierValueComparers, - IReadOnlyList outerIdentifierValueComparers, - IReadOnlyList selfIdentifierValueComparers, + IReadOnlyList> parentIdentifierValueComparers, + IReadOnlyList> outerIdentifierValueComparers, + IReadOnlyList> selfIdentifierValueComparers, Func innerShaper) where TRelatedEntity : TElement where TCollection : class, ICollection @@ -590,8 +661,8 @@ private static void PopulateCollection( } if (!CompareIdentifiers( - outerIdentifierValueComparers, - outerIdentifier(queryContext, dbDataReader), collectionMaterializationContext.OuterIdentifier)) + outerIdentifierValueComparers, + outerIdentifier(queryContext, dbDataReader), collectionMaterializationContext.OuterIdentifier)) { // Outer changed so collection has ended. Materialize last element. GenerateCurrentElementIfPending(); @@ -616,8 +687,8 @@ private static void PopulateCollection( if (collectionMaterializationContext.SelfIdentifier != null) { if (CompareIdentifiers( - selfIdentifierValueComparers, - innerKey, collectionMaterializationContext.SelfIdentifier)) + selfIdentifierValueComparers, + innerKey, collectionMaterializationContext.SelfIdentifier)) { // repeated row for current element // If it is pending materialization then it may have nested elements @@ -673,7 +744,14 @@ void GenerateCurrentElementIfPending() } } - private static TCollection InitializeSplitCollection( + /// + /// 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 static TCollection InitializeSplitCollection( int collectionId, QueryContext queryContext, DbDataReader parentDataReader, @@ -691,7 +769,14 @@ private static TCollection InitializeSplitCollection( return (TCollection)collection; } - private static void PopulateSplitCollection( + /// + /// 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 static void PopulateSplitCollection( int collectionId, RelationalQueryContext queryContext, IExecutionStrategy executionStrategy, @@ -700,7 +785,7 @@ private static void PopulateSplitCollection childIdentifier, - IReadOnlyList identifierValueComparers, + IReadOnlyList> identifierValueComparers, Func innerShaper, Action? relatedDataLoaders) where TRelatedEntity : TElement @@ -770,7 +855,14 @@ static RelationalDataReader InitializeReader( dataReaderContext.HasNext = false; } - private static async Task PopulateSplitCollectionAsync( + /// + /// 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 static async Task PopulateSplitCollectionAsync( int collectionId, RelationalQueryContext queryContext, IExecutionStrategy executionStrategy, @@ -779,7 +871,7 @@ private static async Task PopulateSplitCollectionAsync childIdentifier, - IReadOnlyList identifierValueComparers, + IReadOnlyList> identifierValueComparers, Func innerShaper, Func? relatedDataLoaders) where TRelatedEntity : TElement @@ -861,7 +953,14 @@ static async Task InitializeReaderAsync( dataReaderContext.HasNext = false; } - private static TEntity? MaterializeJsonEntity( + /// + /// 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 static TEntity? MaterializeJsonEntity( QueryContext queryContext, object[] keyPropertyValues, JsonReaderData? jsonReaderData, @@ -900,7 +999,14 @@ static async Task InitializeReaderAsync( return result; } - private static TResult? MaterializeJsonEntityCollection( + /// + /// 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 static TResult? MaterializeJsonEntityCollection( QueryContext queryContext, object[] keyPropertyValues, JsonReaderData? jsonReaderData, @@ -967,7 +1073,14 @@ static async Task InitializeReaderAsync( return result; } - private static void IncludeJsonEntityReference( + /// + /// 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 static void IncludeJsonEntityReference( QueryContext queryContext, object[] keyPropertyValues, JsonReaderData? jsonReaderData, @@ -991,7 +1104,14 @@ private static void IncludeJsonEntityReference( + /// + /// 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 static void IncludeJsonEntityCollection( QueryContext queryContext, object[] keyPropertyValues, JsonReaderData? jsonReaderData, @@ -1058,7 +1178,31 @@ private static void IncludeJsonEntityCollection[] taskFactories) + /// + /// 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 static bool Any(IEnumerable source) + { + foreach (var _ in source) + { + return true; + } + + return false; + } + + /// + /// 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 static async Task TaskAwaiter(Func[] taskFactories) { for (var i = 0; i < taskFactories.Length; i++) { @@ -1066,12 +1210,12 @@ private static async Task TaskAwaiter(Func[] taskFactories) } } - private static bool CompareIdentifiers(IReadOnlyList valueComparers, object[] left, object[] right) + private static bool CompareIdentifiers(IReadOnlyList> valueComparers, object[] left, object[] right) { // Ignoring size check on all for perf as they should be same unless bug in code. for (var i = 0; i < left.Length; i++) { - if (!valueComparers[i].Equals(left[i], right[i])) + if (!valueComparers[i](left[i], right[i])) { return false; } diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index b6f5aa95364..8ab925ed0db 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -2,11 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using System.Text.Json; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.Storage.Internal; using Microsoft.EntityFrameworkCore.Storage.Json; using static System.Linq.Expressions.Expression; @@ -14,7 +16,13 @@ namespace Microsoft.EntityFrameworkCore.Query; public partial class RelationalShapedQueryCompilingExpressionVisitor { - private sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisitor + /// + /// 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 sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisitor { /// /// Reading database values @@ -22,6 +30,12 @@ private sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisit private static readonly MethodInfo IsDbNullMethod = typeof(DbDataReader).GetRuntimeMethod(nameof(DbDataReader.IsDBNull), [typeof(int)])!; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public static readonly MethodInfo GetFieldValueMethod = typeof(DbDataReader).GetRuntimeMethod(nameof(DbDataReader.GetFieldValue), [typeof(int)])!; @@ -77,6 +91,15 @@ private static readonly MethodInfo Utf8JsonReaderGetStringMethod private static readonly MethodInfo EnumParseMethodInfo = typeof(Enum).GetMethod(nameof(Enum.Parse), [typeof(Type), typeof(string)])!; + private static readonly MethodInfo ReadColumnCreateMethod + = typeof(ReaderColumn).GetMethod(nameof(ReaderColumn.Create))!; + + private readonly static MethodInfo PropertyGetJsonValueReaderWriterMethod = + typeof(IReadOnlyProperty).GetMethod(nameof(IReadOnlyProperty.GetJsonValueReaderWriter), [])!; + + private readonly static MethodInfo PropertyGetTypeMappingMethod = + typeof(IReadOnlyProperty).GetMethod(nameof(IReadOnlyProperty.GetTypeMapping), [])!; + private readonly RelationalShapedQueryCompilingExpressionVisitor _parentVisitor; private readonly ISet? _tags; private readonly bool _isTracking; @@ -160,6 +183,14 @@ private readonly Dictionary /// private readonly Dictionary _jsonArrayNonConstantElementAccessMap = new(); + private readonly DisplayClassConstantFixer _displayClassConstantFixer = new(); + + /// + /// 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 ShaperProcessingExpressionVisitor( RelationalShapedQueryCompilingExpressionVisitor parentVisitor, SelectExpression selectExpression, @@ -172,7 +203,6 @@ public ShaperProcessingExpressionVisitor( _resultCoordinatorParameter = Parameter( splitQuery ? typeof(SplitQueryResultCoordinator) : typeof(SingleQueryResultCoordinator), "resultCoordinator"); _executionStrategyParameter = splitQuery ? Parameter(typeof(IExecutionStrategy), "executionStrategy") : null; - _selectExpression = selectExpression; _tags = tags; _dataReaderParameter = Parameter(typeof(DbDataReader), "dataReader"); @@ -247,10 +277,16 @@ private ShaperProcessingExpressionVisitor( _selectExpression.ApplyTags(_tags); } + /// + /// 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 LambdaExpression ProcessRelationalGroupingResult( RelationalGroupByResultExpression relationalGroupByResultExpression, - out RelationalCommandCache relationalCommandCache, - out IReadOnlyList? readerColumns, + out Expression relationalCommandCache, + out Func readerColumns, out LambdaExpression keySelector, out LambdaExpression keyIdentifier, out LambdaExpression? relatedDataLoaders, @@ -277,10 +313,16 @@ public LambdaExpression ProcessRelationalGroupingResult( ref collectionId); } + /// + /// 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 LambdaExpression ProcessShaper( Expression shaperExpression, - out RelationalCommandCache? relationalCommandCache, - out IReadOnlyList? readerColumns, + out Expression relationalCommandCache, + out Func readerColumns, out LambdaExpression? relatedDataLoaders, ref int collectionId) { @@ -293,13 +335,8 @@ public LambdaExpression ProcessShaper( _expressions.Add(result); result = Block(_variables, _expressions); - relationalCommandCache = new RelationalCommandCache( - _parentVisitor.Dependencies.MemoryCache, - _parentVisitor.RelationalDependencies.QuerySqlGeneratorFactory, - _parentVisitor.RelationalDependencies.RelationalParameterBasedSqlProcessorFactory, - _selectExpression, - _parentVisitor._useRelationalNulls); - readerColumns = _readerColumns; + relationalCommandCache = _parentVisitor.CreateRelationalCommandCacheExpression(_selectExpression); + readerColumns = CreateReaderColumnsExpression(); return Lambda( result, @@ -320,14 +357,9 @@ public LambdaExpression ProcessShaper( result = Block(_variables, _expressions); relationalCommandCache = _generateCommandCache - ? new RelationalCommandCache( - _parentVisitor.Dependencies.MemoryCache, - _parentVisitor.RelationalDependencies.QuerySqlGeneratorFactory, - _parentVisitor.RelationalDependencies.RelationalParameterBasedSqlProcessorFactory, - _selectExpression, - _parentVisitor._useRelationalNulls) - : null; - readerColumns = _readerColumns; + ? _parentVisitor.CreateRelationalCommandCacheExpression(_selectExpression) + : Expression.Constant(null, typeof(RelationalCommandCache)); + readerColumns = CreateReaderColumnsExpression(); return Lambda( result, @@ -408,14 +440,9 @@ public LambdaExpression ProcessShaper( } relationalCommandCache = _generateCommandCache - ? new RelationalCommandCache( - _parentVisitor.Dependencies.MemoryCache, - _parentVisitor.RelationalDependencies.QuerySqlGeneratorFactory, - _parentVisitor.RelationalDependencies.RelationalParameterBasedSqlProcessorFactory, - _selectExpression, - _parentVisitor._useRelationalNulls) - : null; - readerColumns = _readerColumns; + ? _parentVisitor.CreateRelationalCommandCacheExpression(_selectExpression) + : Expression.Constant(null, typeof(RelationalCommandCache));; + readerColumns = CreateReaderColumnsExpression(); collectionId = _collectionId; @@ -428,6 +455,12 @@ public LambdaExpression ProcessShaper( } } + /// + /// 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 VisitBinary(BinaryExpression binaryExpression) { switch (binaryExpression) @@ -452,8 +485,18 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) ? value : propertyMap.Values.Max() + 1; - var updatedExpression = newExpression.Update( - new[] { Constant(ValueBuffer.Empty), newExpression.Arguments[1] }); + var updatedExpression = newExpression.Update( + new[] + { + _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery + ? _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + Constant(ValueBuffer.Empty), + _ => ValueBuffer.Empty, + "emptyValueBuffer", + typeof(ValueBuffer)) + : Constant(ValueBuffer.Empty), + newExpression.Arguments[1] + }); return Assign(binaryExpression.Left, updatedExpression); } @@ -465,7 +508,17 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) _jsonMaterializationContextToJsonReaderDataAndKeyValuesParameterMapping[parameterExpression] = mappedParameter; var updatedExpression = newExpression.Update( - new[] { Constant(ValueBuffer.Empty), newExpression.Arguments[1] }); + new[] + { + _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery + ? _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + Constant(ValueBuffer.Empty), + _ => ValueBuffer.Empty, + "emptyValueBuffer", + typeof(ValueBuffer)) + : Constant(ValueBuffer.Empty), + newExpression.Arguments[1] + }); return Assign(binaryExpression.Left, updatedExpression); } @@ -497,6 +550,12 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) return base.VisitBinary(binaryExpression); } + /// + /// 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) { switch (extensionExpression) @@ -597,7 +656,7 @@ protected override Expression VisitExtension(Expression extensionExpression) } else { - var entityParameter = Parameter(shaper.Type); + var entityParameter = Parameter(shaper.Type, "entity"); _variables.Add(entityParameter); if (shaper.StructuralType is IEntityType entityType && entityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy) @@ -718,7 +777,7 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) var projection = _selectExpression.Projection[projectionIndex]; var nullable = IsNullableProjection(projection); - var valueParameter = Parameter(projectionBindingExpression.Type); + var valueParameter = Parameter(projectionBindingExpression.Type, "value" + (_variables.Count + 1)); _variables.Add(valueParameter); _expressions.Add( @@ -769,13 +828,13 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) _readerColumns) .ProcessShaper(relationalCollectionShaperExpression.InnerShaper, out _, out _, out _, ref _collectionId); - var entityType = entity.Type; + var entityClrType = entity.Type; var navigation = includeExpression.Navigation; - var includingEntityType = navigation.DeclaringEntityType.ClrType; - if (includingEntityType != entityType - && includingEntityType.IsAssignableFrom(entityType)) + var includingEntityClrType = navigation.DeclaringEntityType.ClrType; + if (includingEntityClrType != entityClrType + && includingEntityClrType.IsAssignableFrom(entityClrType)) { - includingEntityType = entityType; + includingEntityClrType = entityClrType; } _inline = true; @@ -799,51 +858,68 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) _includeExpressions.Add( Call( - InitializeIncludeCollectionMethodInfo.MakeGenericMethod(entityType, includingEntityType), + InitializeIncludeCollectionMethodInfo.MakeGenericMethod(entityClrType, includingEntityClrType), collectionIdConstant, QueryCompilationContext.QueryContextParameter, _dataReaderParameter, _resultCoordinatorParameter, entity, - Constant(parentIdentifierLambda.Compile()), - Constant(outerIdentifierLambda.Compile()), - Constant(navigation), - Constant( - navigation.IsShadowProperty() - ? null - : navigation.GetCollectionAccessor(), typeof(IClrCollectionAccessor)), + parentIdentifierLambda, + outerIdentifierLambda, + _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery + ? _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + Constant(navigation), + LiftableConstantExpressionHelpers.BuildNavigationAccessLambda(navigation), + navigation.Name + "Navigation", + typeof(INavigationBase)) + : Constant(navigation), + navigation.IsShadowProperty() + ? Constant(null, typeof(IClrCollectionAccessor)) + : _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery + ? _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + Constant(navigation.GetCollectionAccessor(), typeof(IClrCollectionAccessor)), + LiftableConstantExpressionHelpers.BuildClrCollectionAccessorLambda(navigation), + navigation.Name + "NavigationCollectionAccessor", + typeof(IClrCollectionAccessor)) + : Constant(navigation.GetCollectionAccessor(), typeof(IClrCollectionAccessor)), Constant(_isTracking), #pragma warning disable EF1001 // Internal EF Core API usage. Constant(includeExpression.SetLoaded))); #pragma warning restore EF1001 // Internal EF Core API usage. - var relatedEntityType = innerShaper.ReturnType; + var relatedEntityClrType = innerShaper.ReturnType; var inverseNavigation = navigation.Inverse; _collectionPopulatingExpressions!.Add( Call( - PopulateIncludeCollectionMethodInfo.MakeGenericMethod(includingEntityType, relatedEntityType), + PopulateIncludeCollectionMethodInfo.MakeGenericMethod(includingEntityClrType, relatedEntityClrType), collectionIdConstant, QueryCompilationContext.QueryContextParameter, _dataReaderParameter, _resultCoordinatorParameter, - Constant(parentIdentifierLambda.Compile()), - Constant(outerIdentifierLambda.Compile()), - Constant(selfIdentifierLambda.Compile()), - Constant( - relationalCollectionShaperExpression.ParentIdentifierValueComparers, - typeof(IReadOnlyList)), - Constant( - relationalCollectionShaperExpression.OuterIdentifierValueComparers, - typeof(IReadOnlyList)), - Constant( - relationalCollectionShaperExpression.SelfIdentifierValueComparers, - typeof(IReadOnlyList)), - Constant(innerShaper.Compile()), - Constant(inverseNavigation, typeof(INavigationBase)), - Constant( - GenerateFixup( - includingEntityType, relatedEntityType, navigation, inverseNavigation).Compile()), + parentIdentifierLambda, + outerIdentifierLambda, + selfIdentifierLambda, + NewArrayInit( + typeof(Func), + relationalCollectionShaperExpression.ParentIdentifierValueComparers.Select(vc => vc.ObjectEqualsExpression)), + NewArrayInit( + typeof(Func), + relationalCollectionShaperExpression.OuterIdentifierValueComparers.Select(vc => vc.ObjectEqualsExpression)), + NewArrayInit( + typeof(Func), + relationalCollectionShaperExpression.SelfIdentifierValueComparers.Select(vc => vc.ObjectEqualsExpression)), + innerShaper, + inverseNavigation is null + ? Constant(null, typeof(INavigationBase)) + : _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery + ? _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + Constant(inverseNavigation, typeof(INavigationBase)), + LiftableConstantExpressionHelpers.BuildNavigationAccessLambda(inverseNavigation), + inverseNavigation.Name + "InverseNavigation", + typeof(INavigationBase)) + : Constant(inverseNavigation, typeof(INavigationBase)), + GenerateFixup(includingEntityClrType, relatedEntityClrType, navigation, inverseNavigation), Constant(_isTracking))); } else if (includeExpression.NavigationExpression is RelationalSplitCollectionShaperExpression @@ -862,11 +938,11 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) var entityType = entity.Type; var navigation = includeExpression.Navigation; - var includingEntityType = navigation.DeclaringEntityType.ClrType; - if (includingEntityType != entityType - && includingEntityType.IsAssignableFrom(entityType)) + var includingEntityClrType = navigation.DeclaringEntityType.ClrType; + if (includingEntityClrType != entityType + && includingEntityClrType.IsAssignableFrom(entityType)) { - includingEntityType = entityType; + includingEntityClrType = entityType; } _inline = true; @@ -889,48 +965,65 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) _includeExpressions.Add( Call( - InitializeSplitIncludeCollectionMethodInfo.MakeGenericMethod(entityType, includingEntityType), + InitializeSplitIncludeCollectionMethodInfo.MakeGenericMethod(entityType, includingEntityClrType), collectionIdConstant, QueryCompilationContext.QueryContextParameter, _dataReaderParameter, _resultCoordinatorParameter, entity, - Constant(parentIdentifierLambda.Compile()), - Constant(navigation), - Constant(navigation.GetCollectionAccessor()), + parentIdentifierLambda, + _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery + ? _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + Constant(navigation), + LiftableConstantExpressionHelpers.BuildNavigationAccessLambda(navigation), + navigation.Name + "Navigation", + typeof(INavigationBase)) + : Constant(navigation), + _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery + ? _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + Constant(navigation.GetCollectionAccessor()), + LiftableConstantExpressionHelpers.BuildClrCollectionAccessorLambda(navigation), + navigation.Name + "NavigationCollectionAccessor", + typeof(IClrCollectionAccessor)) + : Constant(navigation.GetCollectionAccessor()), Constant(_isTracking), #pragma warning disable EF1001 // Internal EF Core API usage. Constant(includeExpression.SetLoaded))); #pragma warning restore EF1001 // Internal EF Core API usage. - var relatedEntityType = innerShaper.ReturnType; + var relatedEntityClrType = innerShaper.ReturnType; var inverseNavigation = navigation.Inverse; _collectionPopulatingExpressions!.Add( Call( (_isAsync ? PopulateSplitIncludeCollectionAsyncMethodInfo : PopulateSplitIncludeCollectionMethodInfo) - .MakeGenericMethod(includingEntityType, relatedEntityType), + .MakeGenericMethod(includingEntityClrType, relatedEntityClrType), collectionIdConstant, Convert(QueryCompilationContext.QueryContextParameter, typeof(RelationalQueryContext)), _executionStrategyParameter!, - Constant(relationalCommandCache), - Constant(readerColumns, typeof(IReadOnlyList)), + relationalCommandCache, + readerColumns(), Constant(_detailedErrorsEnabled), _resultCoordinatorParameter, - Constant(childIdentifierLambda.Compile()), - Constant( - relationalSplitCollectionShaperExpression.IdentifierValueComparers, - typeof(IReadOnlyList)), - Constant(innerShaper.Compile()), - Constant( - relatedDataLoaders?.Compile(), + childIdentifierLambda, + NewArrayInit( + typeof(Func), + relationalSplitCollectionShaperExpression.IdentifierValueComparers.Select(vc => vc.ObjectEqualsExpression)), + innerShaper, + relatedDataLoaders ?? (Expression)Constant(null, _isAsync ? typeof(Func) : typeof(Action)), - Constant(inverseNavigation, typeof(INavigationBase)), - Constant( - GenerateFixup( - includingEntityType, relatedEntityType, navigation, inverseNavigation).Compile()), + inverseNavigation is null + ? Constant(null, typeof(INavigationBase)) + : _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery + ? _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + Constant(inverseNavigation, typeof(INavigationBase)), + LiftableConstantExpressionHelpers.BuildNavigationAccessLambda(inverseNavigation), + inverseNavigation.Name + "InverseNavigation", + typeof(INavigationBase)) + : Constant(inverseNavigation, typeof(INavigationBase)), + GenerateFixup(includingEntityClrType, relatedEntityClrType, navigation, inverseNavigation), Constant(_isTracking))); } else @@ -982,11 +1075,23 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) QueryCompilationContext.QueryContextParameter, entity, navigationExpression, - Constant(navigation), - Constant(inverseNavigation, typeof(INavigationBase)), - Constant( - GenerateFixup( - includingType, relatedEntityType, navigation, inverseNavigation).Compile()), + _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery + ? _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + Constant(navigation), + LiftableConstantExpressionHelpers.BuildNavigationAccessLambda(navigation), + navigation.Name + "Navigation12", + typeof(INavigation)) + : Constant(navigation), + inverseNavigation == null + ? Default(typeof(INavigation)) + : _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery + ? _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + Constant(inverseNavigation, typeof(INavigationBase)), + LiftableConstantExpressionHelpers.BuildNavigationAccessLambda(inverseNavigation), + inverseNavigation.Name + "InverseNavigation14", + typeof(INavigation)) + : Constant(inverseNavigation, typeof(INavigationBase)), + GenerateFixup(includingType, relatedEntityType, navigation, inverseNavigation), Constant(_isTracking)); _includeExpressions.Add(updatedExpression); @@ -1042,9 +1147,18 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) QueryCompilationContext.QueryContextParameter, _dataReaderParameter, _resultCoordinatorParameter, - Constant(parentIdentifierLambda.Compile()), - Constant(outerIdentifierLambda.Compile()), - Constant(collectionAccessor, typeof(IClrCollectionAccessor))))); + parentIdentifierLambda, + outerIdentifierLambda, + navigation == null + ? Default(typeof(IClrCollectionAccessor)) + : _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery + ? _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + Constant(collectionAccessor, typeof(IClrCollectionAccessor)), + LiftableConstantExpressionHelpers.BuildClrCollectionAccessorLambda(navigation), + navigation.Name + "ClrCollectionAccessor", + typeof(IClrCollectionAccessor)) + : Constant(collectionAccessor, typeof(IClrCollectionAccessor)) + ))); _valuesArrayInitializers!.Add(collectionParameter); accessor = Convert( @@ -1060,19 +1174,19 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) QueryCompilationContext.QueryContextParameter, _dataReaderParameter, _resultCoordinatorParameter, - Constant(parentIdentifierLambda.Compile()), - Constant(outerIdentifierLambda.Compile()), - Constant(selfIdentifierLambda.Compile()), - Constant( - relationalCollectionShaperExpression.ParentIdentifierValueComparers, - typeof(IReadOnlyList)), - Constant( - relationalCollectionShaperExpression.OuterIdentifierValueComparers, - typeof(IReadOnlyList)), - Constant( - relationalCollectionShaperExpression.SelfIdentifierValueComparers, - typeof(IReadOnlyList)), - Constant(innerShaper.Compile()))); + parentIdentifierLambda, + outerIdentifierLambda, + selfIdentifierLambda, + NewArrayInit( + typeof(Func), + relationalCollectionShaperExpression.ParentIdentifierValueComparers.Select(vc => vc.ObjectEqualsExpression)), + NewArrayInit( + typeof(Func), + relationalCollectionShaperExpression.OuterIdentifierValueComparers.Select(vc => vc.ObjectEqualsExpression)), + NewArrayInit( + typeof(Func), + relationalCollectionShaperExpression.SelfIdentifierValueComparers.Select(vc => vc.ObjectEqualsExpression)), + innerShaper)); _variableShaperMapping[relationalCollectionShaperExpression] = accessor; } @@ -1121,6 +1235,7 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) var collectionParameter = Parameter(collectionType); _variables.Add(collectionParameter); + _expressions.Add( Assign( collectionParameter, @@ -1130,8 +1245,16 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) QueryCompilationContext.QueryContextParameter, _dataReaderParameter, _resultCoordinatorParameter, - Constant(parentIdentifierLambda.Compile()), - Constant(collectionAccessor, typeof(IClrCollectionAccessor))))); + parentIdentifierLambda, + navigation == null + ? Default(typeof(IClrCollectionAccessor)) + : _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery + ? _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + Constant(collectionAccessor, typeof(IClrCollectionAccessor)), + LiftableConstantExpressionHelpers.BuildClrCollectionAccessorLambda(navigation), + navigation.Name + "CollectionAccessor", + typeof(IClrCollectionAccessor)) + : Constant(collectionAccessor, typeof(IClrCollectionAccessor))))); _valuesArrayInitializers!.Add(collectionParameter); accessor = Convert( @@ -1147,22 +1270,22 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) collectionIdConstant, Convert(QueryCompilationContext.QueryContextParameter, typeof(RelationalQueryContext)), _executionStrategyParameter!, - Constant(relationalCommandCache), - Constant(readerColumns, typeof(IReadOnlyList)), + relationalCommandCache, + readerColumns(), Constant(_detailedErrorsEnabled), _resultCoordinatorParameter, - Constant(childIdentifierLambda.Compile()), - Constant( - relationalSplitCollectionShaperExpression.IdentifierValueComparers, - typeof(IReadOnlyList)), - Constant(innerShaper.Compile()), - Constant( - relatedDataLoaders?.Compile(), - _isAsync + childIdentifierLambda, + NewArrayInit( + typeof(Func), + relationalSplitCollectionShaperExpression.IdentifierValueComparers.Select(vc => vc.ObjectEqualsExpression)), + innerShaper, + relatedDataLoaders == null + ? Constant(null, _isAsync ? typeof(Func) - : typeof(Action)))); + : typeof(Action)) + : relatedDataLoaders)); - _variableShaperMapping[relationalSplitCollectionShaperExpression] = accessor; + _variableShaperMapping[relationalSplitCollectionShaperExpression] = accessor; } return accessor; @@ -1170,6 +1293,9 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) case GroupByShaperExpression: throw new InvalidOperationException(RelationalStrings.ClientGroupByNotSupported); + + case LiftableConstantExpression: + return extensionExpression; } return base.VisitExtension(extensionExpression); @@ -1190,6 +1316,12 @@ Expression CompensateForCollectionMaterialization(ParameterExpression parameter, } } + /// + /// 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 VisitMethodCall(MethodCallExpression methodCallExpression) { if (methodCallExpression.Method.IsGenericMethod @@ -1329,7 +1461,7 @@ private Expression CreateJsonShapers( ReferenceEqual(Constant(null), shaperCollectionParameter), IsFalse( Call( - typeof(EnumerableExtensions).GetMethod(nameof(EnumerableExtensions.Any))!, + typeof(ShaperProcessingExpressionVisitor).GetMethod(nameof(ShaperProcessingExpressionVisitor.Any))!, shaperCollectionParameter))), shaperEntityParameter .MakeMemberAccess(ownedNavigation.GetMemberInfo(forMaterialization: true, forSet: true)) @@ -1396,7 +1528,9 @@ private Expression CreateJsonShapers( innerShapersMap, innerFixupMap, trackingInnerFixupMap, - _queryLogger).Rewrite(entityShaperMaterializer); + _queryLogger, + _parentVisitor.Dependencies.LiftableConstantFactory, + _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery).Rewrite(entityShaperMaterializer); var entityShaperMaterializerVariable = Variable( entityShaperMaterializer.Type, @@ -1494,7 +1628,13 @@ private Expression CreateJsonShapers( QueryCompilationContext.QueryContextParameter, keyValuesParameter, jsonReaderDataParameter, - Constant(navigation), + _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery + ? _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + Constant(navigation), + LiftableConstantExpressionHelpers.BuildNavigationAccessLambda(navigation), + navigation.Name + "Navigation", + typeof(INavigation)) + : Constant(navigation), shaperLambda); return materializeJsonEntityCollectionMethodCall; @@ -1520,6 +1660,8 @@ private sealed class JsonEntityMaterializerRewriter : ExpressionVisitor private readonly IDictionary _innerFixupMap; private readonly IDictionary _trackingInnerFixupMap; private readonly IDiagnosticsLogger _queryLogger; + private readonly ILiftableConstantFactory _liftableConstantFactory; + private readonly bool _supportsPrecompiledQuery; private static readonly PropertyInfo JsonEncodedTextEncodedUtf8BytesProperty = typeof(JsonEncodedText).GetProperty(nameof(JsonEncodedText.EncodedUtf8Bytes))!; @@ -1535,7 +1677,9 @@ public JsonEntityMaterializerRewriter( IDictionary innerShapersMap, IDictionary innerFixupMap, IDictionary trackingInnerFixupMap, - IDiagnosticsLogger queryLogger) + IDiagnosticsLogger queryLogger, + ILiftableConstantFactory liftableConstantFactory, + bool supportsPrecompiledQuery) { _entityType = entityType; _isTracking = isTracking; @@ -1544,6 +1688,8 @@ public JsonEntityMaterializerRewriter( _innerFixupMap = innerFixupMap; _trackingInnerFixupMap = trackingInnerFixupMap; _queryLogger = queryLogger; + _liftableConstantFactory = liftableConstantFactory; + _supportsPrecompiledQuery = supportsPrecompiledQuery; } public BlockExpression Rewrite(BlockExpression jsonEntityShaperMaterializer) @@ -1552,12 +1698,11 @@ public BlockExpression Rewrite(BlockExpression jsonEntityShaperMaterializer) protected override Expression VisitSwitch(SwitchExpression switchExpression) { if (switchExpression.SwitchValue.Type == typeof(IEntityType) - && switchExpression is - { - Cases: [{ TestValues: [ConstantExpression onlyValue], Body: BlockExpression body }] - } - && onlyValue.Value == _entityType - && body.Expressions.Count > 0) + && switchExpression is { Cases: [{ Body: BlockExpression body } onlySwitchCase] } + && onlySwitchCase.TestValues.Count == 1 + && body.Expressions.Count > 0 + && onlySwitchCase.TestValues[0] is Expression onlyValueExpression + && onlyValueExpression.GetConstantValue() == _entityType) { var valueBufferTryReadValueMethodsToProcess = new ValueBufferTryReadValueMethodsFinder(_entityType).FindValueBufferTryReadValueMethods(body); @@ -1647,7 +1792,13 @@ protected override Expression VisitSwitch(SwitchExpression switchExpression) New( JsonReaderManagerConstructor, _jsonReaderDataParameter, - Constant(_queryLogger))), + _supportsPrecompiledQuery + ? _liftableConstantFactory.CreateLiftableConstant( + Constant(_queryLogger), + c => c.Dependencies.QueryLogger, + "queryLogger", + typeof(IDiagnosticsLogger)) + : Constant(_queryLogger))), // tokenType = jsonReaderManager.CurrentReader.TokenType Assign( tokenTypeVariable, @@ -1759,8 +1910,7 @@ void ProcessFixup(IDictionary fixupMap) foreach (var valueBufferTryReadValueMethodToProcess in valueBufferTryReadValueMethodsToProcess) { - var property = (IProperty)((ConstantExpression)valueBufferTryReadValueMethodToProcess.Arguments[2]).Value!; - + var property = valueBufferTryReadValueMethodToProcess.Arguments[2].GetConstantValue(); testExpressions.Add( Call( Field( @@ -1768,7 +1918,13 @@ void ProcessFixup(IDictionary fixupMap) Utf8JsonReaderManagerCurrentReaderField), Utf8JsonReaderValueTextEqualsMethod, Property( - Constant(JsonEncodedText.Encode(property.GetJsonPropertyName()!)), + _supportsPrecompiledQuery + ? _liftableConstantFactory.CreateLiftableConstant( + Constant(JsonEncodedText.Encode(property.GetJsonPropertyName()!)), + _ => JsonEncodedText.Encode(property.GetJsonPropertyName()!, null), + property.Name + "EncodedProperty", + typeof(JsonEncodedText)) + : Constant(JsonEncodedText.Encode(property.GetJsonPropertyName()!)), JsonEncodedTextEncodedUtf8BytesProperty))); var propertyVariable = Variable(valueBufferTryReadValueMethodToProcess.Type); @@ -1794,6 +1950,7 @@ void ProcessFixup(IDictionary fixupMap) foreach (var innerShaperMapElement in _innerShapersMap) { + var innerShaperMapElementKey = innerShaperMapElement.Key; testExpressions.Add( Call( Field( @@ -1801,7 +1958,13 @@ void ProcessFixup(IDictionary fixupMap) Utf8JsonReaderManagerCurrentReaderField), Utf8JsonReaderValueTextEqualsMethod, Property( - Constant(JsonEncodedText.Encode(innerShaperMapElement.Key)), + _supportsPrecompiledQuery + ? _liftableConstantFactory.CreateLiftableConstant( + Constant(JsonEncodedText.Encode(innerShaperMapElement.Key)), + _ => JsonEncodedText.Encode(innerShaperMapElement.Key, null), + innerShaperMapElementKey + "EncodedNavigation", + typeof(JsonEncodedText)) + : Constant(JsonEncodedText.Encode(innerShaperMapElement.Key)), JsonEncodedTextEncodedUtf8BytesProperty))); var propertyVariable = Variable(innerShaperMapElement.Value.Type); @@ -1813,7 +1976,17 @@ void ProcessFixup(IDictionary fixupMap) var captureState = Call(managerVariable, Utf8JsonReaderManagerCaptureStateMethod); var assignment = Assign(propertyVariable, innerShaperMapElement.Value); var managerRecreation = Assign( - managerVariable, New(JsonReaderManagerConstructor, _jsonReaderDataParameter, Constant(_queryLogger))); + managerVariable, + New( + JsonReaderManagerConstructor, + _jsonReaderDataParameter, + _supportsPrecompiledQuery + ? _liftableConstantFactory.CreateLiftableConstant( + Constant(_queryLogger), + c => c.Dependencies.QueryLogger, + "queryLogger", + typeof(IDiagnosticsLogger)) + : Constant(_queryLogger))); readExpressions.Add( Block( @@ -1845,13 +2018,25 @@ void ProcessFixup(IDictionary fixupMap) switchCases.Add( SwitchCase( testExpression, - Constant(JsonTokenType.PropertyName))); + _supportsPrecompiledQuery + ? _liftableConstantFactory.CreateLiftableConstant( + Constant(JsonTokenType.PropertyName), + _ => JsonTokenType.PropertyName, + "PropertyNameJsonToken", + typeof(JsonTokenType)) + : Constant(JsonTokenType.PropertyName))); } switchCases.Add( SwitchCase( Break(breakLabel), - Constant(JsonTokenType.EndObject))); + _supportsPrecompiledQuery + ? _liftableConstantFactory.CreateLiftableConstant( + Constant(JsonTokenType.EndObject), + _ => JsonTokenType.EndObject, + "PropertyNameJsonToken", + typeof(JsonTokenType)) + : Constant(JsonTokenType.EndObject))); var loopBody = Block( Assign(tokenTypeVariable, Call(managerVariable, Utf8JsonReaderManagerMoveNextMethod)), @@ -2032,7 +2217,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp if (methodCallExpression.Method.IsGenericMethod && methodCallExpression.Method.GetGenericMethodDefinition() == Infrastructure.ExpressionExtensions.ValueBufferTryReadValueMethod - && ((ConstantExpression)methodCallExpression.Arguments[2]).Value is IProperty property + && methodCallExpression.Arguments[2].GetConstantValue() is IProperty property && _nonKeyProperties.Contains(property)) { _valueBufferTryReadValueMethods.Add(methodCallExpression); @@ -2115,7 +2300,7 @@ private bool IsPropertyAssignment( if (methodCallExpression.Method.IsGenericMethod && methodCallExpression.Method.GetGenericMethodDefinition() == Infrastructure.ExpressionExtensions.ValueBufferTryReadValueMethod - && ((ConstantExpression)methodCallExpression.Arguments[2]).Value is IProperty prop + && methodCallExpression.Arguments[2].GetConstantValue() is IProperty prop && _propertyAssignmentMap.TryGetValue(prop, out var param)) { property = prop; @@ -2174,7 +2359,17 @@ private bool IsPropertyAssignment( Default(typeof(JsonReaderData))), Block( Assign( - jsonReaderManagerVariable, New(JsonReaderManagerConstructor, jsonReaderDataVariable, Constant(_queryLogger))), + jsonReaderManagerVariable, + New( + JsonReaderManagerConstructor, + jsonReaderDataVariable, + _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery + ? _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + Constant(_queryLogger), + c => c.Dependencies.QueryLogger, + "queryLogger", + typeof(IDiagnosticsLogger)) + : Constant(_queryLogger))), Call(jsonReaderManagerVariable, Utf8JsonReaderManagerMoveNextMethod), Call(jsonReaderManagerVariable, Utf8JsonReaderManagerCaptureStateMethod))); @@ -2308,10 +2503,14 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) Left: MethodCallExpression { Method: { IsGenericMethod: true } method, - Arguments: [_, _, ConstantExpression { Value: IProperty property }] + Arguments: [_, _, Expression leftExpression] }, - Right: ConstantExpression { Value: null } + Right: Expression rightExpression } + && leftExpression is ConstantExpression or LiftableConstantExpression + && leftExpression.GetConstantValue() is IProperty property + && rightExpression is ConstantExpression or LiftableConstantExpression + && rightExpression.GetConstantValue() == null && method.GetGenericMethodDefinition() == Infrastructure.ExpressionExtensions.ValueBufferTryReadValueMethod) { return _mappedProperties.Contains(property) @@ -2327,8 +2526,10 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp if (methodCallExpression is { Method: { IsGenericMethod: true } method, - Arguments: [_, _, ConstantExpression { Value: IProperty property }] + Arguments: [_, _, Expression argumentExpression] } + && argumentExpression is ConstantExpression or LiftableConstantExpression + && argumentExpression.GetConstantValue() is IProperty property && method.GetGenericMethodDefinition() == Infrastructure.ExpressionExtensions.ValueBufferTryReadValueMethod && !_mappedProperties.Contains(property)) { @@ -2339,7 +2540,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp } } - private static LambdaExpression GenerateFixup( + private LambdaExpression GenerateFixup( Type entityType, Type relatedEntityType, INavigationBase navigation, @@ -2401,7 +2602,14 @@ private static LambdaExpression GenerateReferenceFixupForJson( return Lambda(Block(typeof(void), expressions), entityParameter, relatedEntityParameter); } - private static void InverseCollectionFixup( + /// + /// 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 static void InverseCollectionFixup( ICollection collection, TEntity entity, Action elementFixup) @@ -2418,7 +2626,7 @@ private static Expression AssignReferenceNavigation( INavigationBase navigation) => entity.MakeMemberAccess(navigation.GetMemberInfo(forMaterialization: true, forSet: true)).Assign(relatedEntity); - private static Expression GetOrCreateCollectionObjectLambda( + private Expression GetOrCreateCollectionObjectLambda( Type entityType, INavigationBase navigation) { @@ -2428,19 +2636,31 @@ private static Expression GetOrCreateCollectionObjectLambda( Block( typeof(void), Call( - Constant(navigation.GetCollectionAccessor()), + _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery + ? _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + Constant(navigation.GetCollectionAccessor()), + LiftableConstantExpressionHelpers.BuildClrCollectionAccessorLambda(navigation), + navigation.Name + "NavigationCollectionAccessor", + typeof(IClrCollectionAccessor)) + : Constant(navigation.GetCollectionAccessor()), CollectionAccessorGetOrCreateMethodInfo, prm, Constant(true))), prm); } - private static Expression AddToCollectionNavigation( + private Expression AddToCollectionNavigation( ParameterExpression entity, ParameterExpression relatedEntity, INavigationBase navigation) => Call( - Constant(navigation.GetCollectionAccessor()), + _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery + ? _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + Constant(navigation.GetCollectionAccessor()), + LiftableConstantExpressionHelpers.BuildClrCollectionAccessorLambda(navigation), + navigation.Name + "NavigationCollectionAccessor", + typeof(IClrCollectionAccessor)) + : Constant(navigation.GetCollectionAccessor()), CollectionAccessorAddMethodInfo, entity, relatedEntity, @@ -2508,7 +2728,7 @@ Expression valueExpression Lambda( bufferedReaderLambdaExpression, dbDataReader, - _indexMapParameter ?? Parameter(typeof(int[]))).Compile()); + _indexMapParameter ?? Parameter(typeof(int[]), "indexMap"))); } valueExpression = Call( @@ -2579,23 +2799,76 @@ Expression valueExpression exceptionParameter, Call(dbDataReader, GetFieldValueMethod.MakeGenericMethod(typeof(object)), indexExpression), Constant(valueExpression.Type.MakeNullable(nullable), typeof(Type)), - Constant(property, typeof(IPropertyBase)))); + property == null + ? Default(typeof(IPropertyBase)) + : _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery + ? _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + Constant(property, typeof(IPropertyBase)), + LiftableConstantExpressionHelpers.BuildMemberAccessLambdaForProperty(property), + property + "Property", + typeof(IPropertyBase)) + : Constant(property, typeof(IPropertyBase)))); valueExpression = TryCatch(valueExpression, catchBlock); } + valueExpression = _displayClassConstantFixer.Visit(valueExpression); + return valueExpression; } + // TODO: revisit this + private sealed class DisplayClassConstantFixer : ExpressionVisitor + { + protected override Expression VisitMember(MemberExpression memberExpression) + { + if (memberExpression.Type == typeof(JsonValueReaderWriter)) + { + var expression = Visit(memberExpression.Expression); + if (expression is ConstantExpression constant + && constant.Type.Attributes.HasFlag(TypeAttributes.NestedPrivate) + && Attribute.IsDefined(constant.Type, typeof(CompilerGeneratedAttribute), inherit: true)) + { + var updatedMember = memberExpression.Update(expression); + + var jsonReaderWriterObject = Lambda>( + Convert(updatedMember, typeof(JsonValueReaderWriter))) + .Compile(preferInterpretation: true) + .Invoke(); + + return ((JsonValueReaderWriter)jsonReaderWriterObject).ConstructorExpression; + } + + return memberExpression.Update(expression); + } + + return base.VisitMember(memberExpression); + } + } + private Expression CreateReadJsonPropertyValueExpression( ParameterExpression jsonReaderManagerParameter, IProperty property) { - var nullable = property.IsNullable; - var typeMapping = property.GetTypeMapping(); - - var jsonReaderWriterExpression = Constant( - property.GetJsonValueReaderWriter() ?? property.GetTypeMapping().JsonValueReaderWriter!); + var jsonReaderWriter = property.GetJsonValueReaderWriter() ?? property.GetTypeMapping().JsonValueReaderWriter!; + var prm = Parameter(typeof(MaterializerLiftableConstantContext), "c"); + var jsonReaderWriterExpression = _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery + ? _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + Constant(property.GetJsonValueReaderWriter() ?? property.GetTypeMapping().JsonValueReaderWriter!), + Lambda>( + Coalesce( + Call( + LiftableConstantExpressionHelpers.BuildMemberAccessForProperty(property, prm), + PropertyGetJsonValueReaderWriterMethod), + Property( + Call( + LiftableConstantExpressionHelpers.BuildMemberAccessForProperty(property, prm), + PropertyGetTypeMappingMethod), + nameof(CoreTypeMapping.JsonValueReaderWriter))), + prm), + property.Name + "PropertyName", + jsonReaderWriter.GetType()) + : (Expression)Constant(property.GetJsonValueReaderWriter() ?? property.GetTypeMapping().JsonValueReaderWriter!); var fromJsonMethod = jsonReaderWriterExpression.Type.GetMethod( nameof(JsonValueReaderWriter.FromJsonTyped), @@ -2603,9 +2876,9 @@ private Expression CreateReadJsonPropertyValueExpression( Expression resultExpression = Convert( Call(jsonReaderWriterExpression, fromJsonMethod, jsonReaderManagerParameter, Default(typeof(object))), - typeMapping.ClrType); + property.GetTypeMapping().ClrType); - if (nullable) + if (property.IsNullable) { // in case of null value we can't just use the JsonReader method, but rather check the current token type // if it's JsonTokenType.Null means value is null, only if it's not we are safe to read the value @@ -2642,6 +2915,64 @@ private Expression CreateReadJsonPropertyValueExpression( return resultExpression; } + // TODO: No, this must be a lifted constant, otherwise we re-instantiate on each query + private Func CreateReaderColumnsExpression() + => () => + { + if (_readerColumns is null) + { + return Constant(null, typeof(ReaderColumn?[])); + } + + var materializerLiftableConstantContextParameter = Parameter(typeof(MaterializerLiftableConstantContext)); + var initializers = new List(); + + foreach (var readerColumn in _readerColumns) + { + var currentReaderColumn = readerColumn; + if (currentReaderColumn is null) + { + initializers.Add(Constant(null, typeof(ReaderColumn))); + continue; + } + + var propertyExpression = default(Expression); + var property = currentReaderColumn.Property; + if (property is null) + { + propertyExpression = Constant(null, typeof(IProperty)); + } + else + { + propertyExpression = _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery + ? LiftableConstantExpressionHelpers.BuildMemberAccessForProperty(property, materializerLiftableConstantContextParameter) + : Constant(property); + } + + initializers.Add( + New( + ReaderColumn.GetConstructor(currentReaderColumn.Type), + Constant(currentReaderColumn.IsNullable), + Constant(currentReaderColumn.Name, typeof(string)), + propertyExpression, + currentReaderColumn.GetFieldValueExpression)); + } + + var result = _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery + ? _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + Constant(_readerColumns), + Lambda>( + NewArrayInit( + typeof(ReaderColumn), + initializers), + materializerLiftableConstantContextParameter), + "readerColumns", + typeof(ReaderColumn[])) + : (Expression)Constant(_readerColumns); + + return result; + }; + private sealed class CollectionShaperFindingExpressionVisitor : ExpressionVisitor { private bool _containsCollection; diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs index ede5805ad30..0cff182242b 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs @@ -70,17 +70,12 @@ protected virtual Expression VisitNonQuery(NonQueryExpression nonQueryExpression break; } - var relationalCommandCache = new RelationalCommandCache( - Dependencies.MemoryCache, - RelationalDependencies.QuerySqlGeneratorFactory, - RelationalDependencies.RelationalParameterBasedSqlProcessorFactory, - innerExpression, - _useRelationalNulls); + var relationalCommandCache = CreateRelationalCommandCacheExpression(innerExpression); return Call( QueryCompilationContext.IsAsync ? NonQueryAsyncMethodInfo : NonQueryMethodInfo, Convert(QueryCompilationContext.QueryContextParameter, typeof(RelationalQueryContext)), - Constant(relationalCommandCache), + relationalCommandCache, Constant(_contextType), Constant(nonQueryExpression.CommandSource), Constant(_threadSafetyChecksEnabled)); @@ -96,7 +91,14 @@ private static readonly MethodInfo NonQueryAsyncMethodInfo .GetDeclaredMethods(nameof(NonQueryResultAsync)) .Single(mi => mi.GetParameters().Length == 5); - private static int NonQueryResult( + /// + /// 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 static int NonQueryResult( RelationalQueryContext relationalQueryContext, RelationalCommandCache relationalCommandCache, Type contextType, @@ -167,7 +169,14 @@ private static int NonQueryResult( } } - private static Task NonQueryResultAsync( + /// + /// 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 static Task NonQueryResultAsync( RelationalQueryContext relationalQueryContext, RelationalCommandCache relationalCommandCache, Type contextType, @@ -270,30 +279,31 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery if (splitQuery) { - var relatedDataLoadersParameter = Constant( - QueryCompilationContext.IsAsync ? null : relatedDataLoaders?.Compile(), - typeof(Action)); + var relatedDataLoadersParameter = QueryCompilationContext.IsAsync || relatedDataLoaders == null + ? (Expression)Constant(null, typeof(Action)) + : relatedDataLoaders; - var relatedDataLoadersAsyncParameter = Constant( - QueryCompilationContext.IsAsync ? relatedDataLoaders?.Compile() : null, - typeof(Func)); + var relatedDataLoadersAsyncParameter = QueryCompilationContext.IsAsync && relatedDataLoaders != null + ? relatedDataLoaders! + : (Expression)Constant(null, typeof(Func)); return New( typeof(GroupBySplitQueryingEnumerable<,>).MakeGenericType( keySelector.ReturnType, elementSelector.ReturnType).GetConstructors()[0], Convert(QueryCompilationContext.QueryContextParameter, typeof(RelationalQueryContext)), - Constant(relationalCommandCache), - Constant(readerColumns, typeof(IReadOnlyList)), - Constant(keySelector.Compile()), - Constant(keyIdentifier.Compile()), - Constant(relationalGroupByResultExpression.KeyIdentifierValueComparers, typeof(IReadOnlyList)), - Constant(elementSelector.Compile()), + relationalCommandCache, + readerColumns(), + keySelector, + keyIdentifier, + NewArrayInit( + typeof(Func), + relationalGroupByResultExpression.KeyIdentifierValueComparers.Select(vc => vc.ObjectEqualsExpression)), + elementSelector, relatedDataLoadersParameter, relatedDataLoadersAsyncParameter, Constant(_contextType), - Constant( - QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution), + Constant(QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution), Constant(_detailedErrorsEnabled), Constant(_threadSafetyChecksEnabled)); } @@ -303,15 +313,16 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery keySelector.ReturnType, elementSelector.ReturnType).GetConstructors()[0], Convert(QueryCompilationContext.QueryContextParameter, typeof(RelationalQueryContext)), - Constant(relationalCommandCache), - Constant(readerColumns, typeof(IReadOnlyList)), - Constant(keySelector.Compile()), - Constant(keyIdentifier.Compile()), - Constant(relationalGroupByResultExpression.KeyIdentifierValueComparers, typeof(IReadOnlyList)), - Constant(elementSelector.Compile()), + relationalCommandCache, + readerColumns(), + keySelector, + keyIdentifier, + NewArrayInit( + typeof(Func), + relationalGroupByResultExpression.KeyIdentifierValueComparers.Select(vc => vc.ObjectEqualsExpression)), + elementSelector, Constant(_contextType), - Constant( - QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution), + Constant(QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution), Constant(_detailedErrorsEnabled), Constant(_threadSafetyChecksEnabled)); } @@ -319,8 +330,8 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery { var nonComposedFromSql = selectExpression.IsNonComposedFromSql(); var shaper = new ShaperProcessingExpressionVisitor(this, selectExpression, _tags, splitQuery, nonComposedFromSql).ProcessShaper( - shapedQueryExpression.ShaperExpression, - out var relationalCommandCache, out var readerColumns, out var relatedDataLoaders, ref collectionCount); + shapedQueryExpression.ShaperExpression, out var relationalCommandCache, out var readerColumns, + out var relatedDataLoaders, ref collectionCount); if (querySplittingBehavior == null && collectionCount > 1) @@ -333,55 +344,80 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery return New( typeof(FromSqlQueryingEnumerable<>).MakeGenericType(shaper.ReturnType).GetConstructors()[0], Convert(QueryCompilationContext.QueryContextParameter, typeof(RelationalQueryContext)), - Constant(relationalCommandCache), - Constant(readerColumns, typeof(IReadOnlyList)), - Constant( - selectExpression.Projection.Select(pe => ((ColumnExpression)pe.Expression).Name).ToList(), - typeof(IReadOnlyList)), - Constant(shaper.Compile()), + relationalCommandCache, + readerColumns(), + NewArrayInit( + typeof(string), + selectExpression.Projection.Select(pe => Constant(((ColumnExpression)pe.Expression).Name, typeof(string)))), + shaper, Constant(_contextType), - Constant( - QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution), + Constant(QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution), Constant(_detailedErrorsEnabled), Constant(_threadSafetyChecksEnabled)); } if (splitQuery) { - var relatedDataLoadersParameter = Constant( - QueryCompilationContext.IsAsync ? null : relatedDataLoaders?.Compile(), - typeof(Action)); + var relatedDataLoadersParameter = + QueryCompilationContext.IsAsync || relatedDataLoaders is null + ? Constant(null, typeof(Action)) + : (Expression)relatedDataLoaders; - var relatedDataLoadersAsyncParameter = Constant( - QueryCompilationContext.IsAsync ? relatedDataLoaders?.Compile() : null, - typeof(Func)); + var relatedDataLoadersAsyncParameter = + QueryCompilationContext.IsAsync && relatedDataLoaders is not null + ? (Expression)relatedDataLoaders + : Constant(null, typeof(Func)); return New( typeof(SplitQueryingEnumerable<>).MakeGenericType(shaper.ReturnType).GetConstructors().Single(), Convert(QueryCompilationContext.QueryContextParameter, typeof(RelationalQueryContext)), - Constant(relationalCommandCache), - Constant(readerColumns, typeof(IReadOnlyList)), - Constant(shaper.Compile()), + relationalCommandCache, + readerColumns(), + shaper, relatedDataLoadersParameter, relatedDataLoadersAsyncParameter, Constant(_contextType), - Constant( - QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution), + Constant(QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution), Constant(_detailedErrorsEnabled), Constant(_threadSafetyChecksEnabled)); } - return New( - typeof(SingleQueryingEnumerable<>).MakeGenericType(shaper.ReturnType).GetConstructors()[0], + // TODO: Do the same for the other QueryingEnumerables + return Call( + typeof(SingleQueryingEnumerable).GetMethods() + .Single(m => m.Name == nameof(SingleQueryingEnumerable.Create)) + .MakeGenericMethod(shaper.ReturnType), Convert(QueryCompilationContext.QueryContextParameter, typeof(RelationalQueryContext)), - Constant(relationalCommandCache), - Constant(readerColumns, typeof(IReadOnlyList)), - Constant(shaper.Compile()), + relationalCommandCache, + readerColumns(), + shaper, Constant(_contextType), - Constant( - QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution), + Constant(QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution), Constant(_detailedErrorsEnabled), Constant(_threadSafetyChecksEnabled)); } } + + private Expression CreateRelationalCommandCacheExpression(Expression queryExpression) + { + var relationalCommandCache = new RelationalCommandCache( + Dependencies.MemoryCache, + RelationalDependencies.QuerySqlGeneratorFactory, + RelationalDependencies.RelationalParameterBasedSqlProcessorFactory, + queryExpression, + _useRelationalNulls); + + return QueryCompilationContext.SupportsPrecompiledQuery + ? RelationalDependencies.RelationalLiftableConstantFactory.CreateLiftableConstant( + Constant(relationalCommandCache), + c => new RelationalCommandCache( + c.Dependencies.MemoryCache, + c.RelationalDependencies.QuerySqlGeneratorFactory, + c.RelationalDependencies.RelationalParameterBasedSqlProcessorFactory, + queryExpression, + _useRelationalNulls), + "relationalCommandCache", + typeof(RelationalCommandCache)) + : Constant(relationalCommandCache); + } } diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitorDependencies.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitorDependencies.cs index c32ba6d83b5..bca7ecafc93 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitorDependencies.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitorDependencies.cs @@ -47,10 +47,12 @@ public sealed record RelationalShapedQueryCompilingExpressionVisitorDependencies [EntityFrameworkInternal] public RelationalShapedQueryCompilingExpressionVisitorDependencies( IQuerySqlGeneratorFactory querySqlGeneratorFactory, - IRelationalParameterBasedSqlProcessorFactory relationalParameterBasedSqlProcessorFactory) + IRelationalParameterBasedSqlProcessorFactory relationalParameterBasedSqlProcessorFactory, + IRelationalLiftableConstantFactory relationalLiftableConstantFactory) { QuerySqlGeneratorFactory = querySqlGeneratorFactory; RelationalParameterBasedSqlProcessorFactory = relationalParameterBasedSqlProcessorFactory; + RelationalLiftableConstantFactory = relationalLiftableConstantFactory; } /// @@ -62,4 +64,9 @@ public RelationalShapedQueryCompilingExpressionVisitorDependencies( /// The SQL processor based on parameter values. /// public IRelationalParameterBasedSqlProcessorFactory RelationalParameterBasedSqlProcessorFactory { get; init; } + + /// + /// The liftable constant factory. + /// + public IRelationalLiftableConstantFactory RelationalLiftableConstantFactory { get; init; } } diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index 85db91718ea..5e88f73e11b 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -2048,7 +2048,7 @@ when sqlParameterExpression.Name.StartsWith(QueryCompilationContext.QueryParamet Expression.Constant(sqlParameterExpression.Name, typeof(string)), Expression.Constant(null, typeof(List)), Expression.Constant(property, typeof(IProperty))), - QueryCompilationContext.QueryContextParameter); + QueryCompilationContext.QueryContextParameter); var newParameterName = $"{RuntimeParameterPrefix}" @@ -2107,7 +2107,14 @@ when memberInitExpression.Bindings.SingleOrDefault(mb => mb.Member.Name == compl _ => throw new UnreachableException() }; - private static T? ParameterValueExtractor( + /// + /// 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 static T? ParameterValueExtractor( QueryContext context, string baseParameterName, List? complexPropertyChain, @@ -2131,7 +2138,14 @@ when memberInitExpression.Bindings.SingleOrDefault(mb => mb.Member.Name == compl return baseValue == null ? (T?)(object?)null : (T?)property.GetGetter().GetClrValue(baseValue); } - private static List? ParameterListValueExtractor( + /// + /// 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 static List? ParameterListValueExtractor( QueryContext context, string baseParameterName, IProperty property) diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index 11733db8518..47db6c9f955 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -2098,68 +2098,68 @@ StructuralTypeProjectionExpression ProcessStructuralType( var complexPropertyCache = new Dictionary(); var type = structuralProjection1.StructuralType; - foreach (var property in type.GetPropertiesInHierarchy()) + foreach (var property in type.GetPropertiesInHierarchy()) + { + var column1 = structuralProjection1.BindProperty(property); + var column2 = structuralProjection2.BindProperty(property); + var alias = GenerateUniqueColumnAlias(column1.Name); + var innerProjection = new ProjectionExpression(column1, alias); + select1._projection.Add(innerProjection); + select2._projection.Add(new ProjectionExpression(column2, alias)); + var outerColumn = CreateColumnExpression(innerProjection, setOperationAlias); + if (column1.IsNullable + || column2.IsNullable) { - var column1 = structuralProjection1.BindProperty(property); - var column2 = structuralProjection2.BindProperty(property); - var alias = GenerateUniqueColumnAlias(column1.Name); - var innerProjection = new ProjectionExpression(column1, alias); - select1._projection.Add(innerProjection); - select2._projection.Add(new ProjectionExpression(column2, alias)); - var outerColumn = CreateColumnExpression(innerProjection, setOperationAlias); - if (column1.IsNullable - || column2.IsNullable) - { - outerColumn = outerColumn.MakeNullable(); - } + outerColumn = outerColumn.MakeNullable(); + } - propertyExpressions[property] = outerColumn; + propertyExpressions[property] = outerColumn; - // Lift up any identifier columns to the set operation result (the outer). - // This is typically the entity primary key columns, but can also be all of a complex type's properties if Distinct - // was previously called. - if (outerIdentifiers.Length > 0) + // Lift up any identifier columns to the set operation result (the outer). + // This is typically the entity primary key columns, but can also be all of a complex type's properties if Distinct + // was previously called. + if (outerIdentifiers.Length > 0) + { + var index = select1._identifier.FindIndex(e => e.Column.Equals(column1)); + if (index != -1) { - var index = select1._identifier.FindIndex(e => e.Column.Equals(column1)); - if (index != -1) + if (select2._identifier[index].Column.Equals(column2)) { - if (select2._identifier[index].Column.Equals(column2)) - { - outerIdentifiers[index] = outerColumn; - } - else - { - // If select1 matched but select2 did not then we erase all identifiers - // TODO: We could make this little more robust by allow the indexes to be different. See issue#24475 - // i.e. Identifier ordering being different. - outerIdentifiers = []; - } + outerIdentifiers[index] = outerColumn; } - // If the top-level projection - not the current nested one - is a complex type and not an entity type, then add - // all its columns to the "otherExpressions" list (i.e. columns not part of a an entity primary key). This is - // the same as with a non-structural type projection. - else if (projection1.StructuralType is IComplexType) + else { - var outerTypeMapping = column1.TypeMapping ?? column1.TypeMapping; - if (outerTypeMapping == null) - { - throw new InvalidOperationException( - RelationalStrings.SetOperationsRequireAtLeastOneSideWithValidTypeMapping(setOperationType)); - } - - otherExpressions.Add((outerColumn, outerTypeMapping.KeyComparer)); + // If select1 matched but select2 did not then we erase all identifiers + // TODO: We could make this little more robust by allow the indexes to be different. See issue#24475 + // i.e. Identifier ordering being different. + outerIdentifiers = []; + } + } + // If the top-level projection - not the current nested one - is a complex type and not an entity type, then add + // all its columns to the "otherExpressions" list (i.e. columns not part of a an entity primary key). This is + // the same as with a non-structural type projection. + else if (projection1.StructuralType is IComplexType) + { + var outerTypeMapping = column1.TypeMapping ?? column1.TypeMapping; + if (outerTypeMapping == null) + { + throw new InvalidOperationException( + RelationalStrings.SetOperationsRequireAtLeastOneSideWithValidTypeMapping(setOperationType)); } + + otherExpressions.Add((outerColumn, outerTypeMapping.KeyComparer)); } } + } - foreach (var complexProperty in GetAllComplexPropertiesInHierarchy(type)) - { - var complexPropertyShaper1 = structuralProjection1.BindComplexProperty(complexProperty); + foreach (var complexProperty in GetAllComplexPropertiesInHierarchy(type)) + { + var complexPropertyShaper1 = structuralProjection1.BindComplexProperty(complexProperty); var complexPropertyShaper2 = structuralProjection2.BindComplexProperty(complexProperty); var resultComplexProjection = ProcessStructuralType( (StructuralTypeProjectionExpression)complexPropertyShaper1.ValueBufferExpression, - (StructuralTypeProjectionExpression)complexPropertyShaper2.ValueBufferExpression); + (StructuralTypeProjectionExpression)complexPropertyShaper2.ValueBufferExpression); var resultComplexShaper = new RelationalStructuralTypeShaperExpression( complexProperty.ComplexType, diff --git a/src/EFCore.Relational/Storage/ReaderColumn.cs b/src/EFCore.Relational/Storage/ReaderColumn.cs index e6aa7627825..138aa7cc8d4 100644 --- a/src/EFCore.Relational/Storage/ReaderColumn.cs +++ b/src/EFCore.Relational/Storage/ReaderColumn.cs @@ -29,12 +29,14 @@ public abstract class ReaderColumn /// A value indicating if the column is nullable. /// The name of the column. /// The property being read if any, null otherwise. - protected ReaderColumn(Type type, bool nullable, string? name, IPropertyBase? property) + /// A lambda expression to get field value for the column from the reader. + protected ReaderColumn(Type type, bool nullable, string? name, IPropertyBase? property, LambdaExpression getFieldValueExpression) { Type = type; IsNullable = nullable; Name = name; Property = property; + GetFieldValueExpression = getFieldValueExpression; } /// @@ -57,6 +59,11 @@ protected ReaderColumn(Type type, bool nullable, string? name, IPropertyBase? pr /// public virtual IPropertyBase? Property { get; } + /// + /// A lambda expression to get field value for the column from the reader. + /// + public virtual LambdaExpression GetFieldValueExpression { get; } + /// /// Creates an instance of . /// @@ -73,10 +80,17 @@ public static ReaderColumn Create( bool nullable, string? columnName, IPropertyBase? property, - object readFunc) + LambdaExpression readFunc) => (ReaderColumn)GetConstructor(type).Invoke([nullable, columnName, property, readFunc]); - private static ConstructorInfo GetConstructor(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 + /// 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 static ConstructorInfo GetConstructor(Type type) => Constructors.GetOrAdd( type, t => typeof(ReaderColumn<>).MakeGenericType(t).GetConstructors().First(ci => ci.GetParameters().Length == 4)); } diff --git a/src/EFCore.Relational/Storage/ReaderColumn`.cs b/src/EFCore.Relational/Storage/ReaderColumn`.cs index 9de7877e94f..b3ad9531ac2 100644 --- a/src/EFCore.Relational/Storage/ReaderColumn`.cs +++ b/src/EFCore.Relational/Storage/ReaderColumn`.cs @@ -24,15 +24,15 @@ public class ReaderColumn : ReaderColumn /// A value indicating if the column is nullable. /// The name of the column. /// The property being read if any, null otherwise. - /// A function to get field value for the column from the reader. + /// A lambda expression to get field value for the column from the reader. public ReaderColumn( bool nullable, string? name, IPropertyBase? property, - Func getFieldValue) - : base(typeof(T), nullable, name, property) + Expression> getFieldValueExpression) + : base(typeof(T), nullable, name, property, getFieldValueExpression) { - GetFieldValue = getFieldValue; + GetFieldValue = getFieldValueExpression.Compile(); } /// diff --git a/src/EFCore.SqlServer.HierarchyId/Storage/Json/SqlServerJsonHierarchyIdReaderWriter.cs b/src/EFCore.SqlServer.HierarchyId/Storage/Json/SqlServerJsonHierarchyIdReaderWriter.cs index 6e6e1bf0b0b..f6dfa16b5ab 100644 --- a/src/EFCore.SqlServer.HierarchyId/Storage/Json/SqlServerJsonHierarchyIdReaderWriter.cs +++ b/src/EFCore.SqlServer.HierarchyId/Storage/Json/SqlServerJsonHierarchyIdReaderWriter.cs @@ -27,4 +27,9 @@ public override HierarchyId FromJsonTyped(ref Utf8JsonReaderManager manager, obj /// public override void ToJsonTyped(Utf8JsonWriter writer, HierarchyId value) => writer.WriteStringValue(value.ToString()); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore.SqlServer.HierarchyId/Storage/Json/SqlServerJsonSqlHierarchyIdReaderWriter.cs b/src/EFCore.SqlServer.HierarchyId/Storage/Json/SqlServerJsonSqlHierarchyIdReaderWriter.cs index bedbb8bf860..3ffe732d2e9 100644 --- a/src/EFCore.SqlServer.HierarchyId/Storage/Json/SqlServerJsonSqlHierarchyIdReaderWriter.cs +++ b/src/EFCore.SqlServer.HierarchyId/Storage/Json/SqlServerJsonSqlHierarchyIdReaderWriter.cs @@ -28,4 +28,9 @@ public override SqlHierarchyId FromJsonTyped(ref Utf8JsonReaderManager manager, /// public override void ToJsonTyped(Utf8JsonWriter writer, SqlHierarchyId value) => writer.WriteStringValue(value.ToString()); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore.SqlServer.NTS/Storage/Json/SqlServerJsonGeometryWktReaderWriter.cs b/src/EFCore.SqlServer.NTS/Storage/Json/SqlServerJsonGeometryWktReaderWriter.cs index a1ccc63910a..38cc543eaf5 100644 --- a/src/EFCore.SqlServer.NTS/Storage/Json/SqlServerJsonGeometryWktReaderWriter.cs +++ b/src/EFCore.SqlServer.NTS/Storage/Json/SqlServerJsonGeometryWktReaderWriter.cs @@ -31,4 +31,9 @@ public override Geometry FromJsonTyped(ref Utf8JsonReaderManager manager, object /// public override void ToJsonTyped(Utf8JsonWriter writer, Geometry value) => writer.WriteStringValue(value.ToText()); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs index 3bedb9a1008..ba13e7f0cb1 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Internal; diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs index c3cb7f851b2..438a00a3d86 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlTranslatingExpressionVisitor.cs @@ -355,7 +355,14 @@ StartsEndsWithContains.StartsWith or StartsEndsWithContains.EndsWith } } - private static string? ConstructLikePatternParameter( + /// + /// 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 static string? ConstructLikePatternParameter( QueryContext queryContext, string baseParameterName, StartsEndsWithContains methodType) @@ -378,10 +385,37 @@ StartsEndsWithContains.StartsWith or StartsEndsWithContains.EndsWith _ => throw new UnreachableException() }; - private enum StartsEndsWithContains + /// + /// 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 enum StartsEndsWithContains { + /// + /// 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. + /// StartsWith, + + /// + /// 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. + /// EndsWith, + + /// + /// 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. + /// Contains } diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryableMethodTranslatingExpressionVisitor.cs index dc9d782e20d..41265f09819 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryableMethodTranslatingExpressionVisitor.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; using Microsoft.EntityFrameworkCore.Sqlite.Internal; using Microsoft.EntityFrameworkCore.Sqlite.Query.SqlExpressions.Internal; diff --git a/src/EFCore.Sqlite.Core/Storage/Json/Internal/SqliteJsonByteArrayReaderWriter.cs b/src/EFCore.Sqlite.Core/Storage/Json/Internal/SqliteJsonByteArrayReaderWriter.cs index 12376cc11af..92a3d92f9e1 100644 --- a/src/EFCore.Sqlite.Core/Storage/Json/Internal/SqliteJsonByteArrayReaderWriter.cs +++ b/src/EFCore.Sqlite.Core/Storage/Json/Internal/SqliteJsonByteArrayReaderWriter.cs @@ -47,4 +47,9 @@ public override byte[] FromJsonTyped(ref Utf8JsonReaderManager manager, object? /// public override void ToJsonTyped(Utf8JsonWriter writer, byte[] value) => writer.WriteStringValue(Convert.ToHexString(value)); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore.Sqlite.Core/Storage/Json/Internal/SqliteJsonDateTimeOffsetReaderWriter.cs b/src/EFCore.Sqlite.Core/Storage/Json/Internal/SqliteJsonDateTimeOffsetReaderWriter.cs index d57cd9da2a1..ec868f79e1e 100644 --- a/src/EFCore.Sqlite.Core/Storage/Json/Internal/SqliteJsonDateTimeOffsetReaderWriter.cs +++ b/src/EFCore.Sqlite.Core/Storage/Json/Internal/SqliteJsonDateTimeOffsetReaderWriter.cs @@ -56,4 +56,9 @@ public override void ToJsonTyped(Utf8JsonWriter writer, DateTimeOffset value) JsonEncodedText.Encode( string.Format(CultureInfo.InvariantCulture, DateTimeOffsetFormatConst, value), JavaScriptEncoder.UnsafeRelaxedJsonEscaping)); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore.Sqlite.Core/Storage/Json/Internal/SqliteJsonDateTimeReaderWriter.cs b/src/EFCore.Sqlite.Core/Storage/Json/Internal/SqliteJsonDateTimeReaderWriter.cs index 29e8025a919..f577bef2777 100644 --- a/src/EFCore.Sqlite.Core/Storage/Json/Internal/SqliteJsonDateTimeReaderWriter.cs +++ b/src/EFCore.Sqlite.Core/Storage/Json/Internal/SqliteJsonDateTimeReaderWriter.cs @@ -50,4 +50,9 @@ public override DateTime FromJsonTyped(ref Utf8JsonReaderManager manager, object /// public override void ToJsonTyped(Utf8JsonWriter writer, DateTime value) => writer.WriteStringValue(string.Format(CultureInfo.InvariantCulture, DateTimeFormatConst, value)); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore.Sqlite.Core/Storage/Json/Internal/SqliteJsonDecimalReaderWriter.cs b/src/EFCore.Sqlite.Core/Storage/Json/Internal/SqliteJsonDecimalReaderWriter.cs index d9cc869b838..1913f775461 100644 --- a/src/EFCore.Sqlite.Core/Storage/Json/Internal/SqliteJsonDecimalReaderWriter.cs +++ b/src/EFCore.Sqlite.Core/Storage/Json/Internal/SqliteJsonDecimalReaderWriter.cs @@ -50,4 +50,9 @@ public override decimal FromJsonTyped(ref Utf8JsonReaderManager manager, object? /// public override void ToJsonTyped(Utf8JsonWriter writer, decimal value) => writer.WriteStringValue(string.Format(CultureInfo.InvariantCulture, DecimalFormatConst, value)); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore.Sqlite.Core/Storage/Json/Internal/SqliteJsonGuidReaderWriter.cs b/src/EFCore.Sqlite.Core/Storage/Json/Internal/SqliteJsonGuidReaderWriter.cs index 0b90ce03d1a..c8d9c44d9a5 100644 --- a/src/EFCore.Sqlite.Core/Storage/Json/Internal/SqliteJsonGuidReaderWriter.cs +++ b/src/EFCore.Sqlite.Core/Storage/Json/Internal/SqliteJsonGuidReaderWriter.cs @@ -47,4 +47,9 @@ public override Guid FromJsonTyped(ref Utf8JsonReaderManager manager, object? ex /// public override void ToJsonTyped(Utf8JsonWriter writer, Guid value) => writer.WriteStringValue(value.ToString().ToUpperInvariant()); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore.Sqlite.NTS/Storage/Json/SqliteJsonGeometryWktReaderWriter.cs b/src/EFCore.Sqlite.NTS/Storage/Json/SqliteJsonGeometryWktReaderWriter.cs index c6e784e402d..86fe5d0d051 100644 --- a/src/EFCore.Sqlite.NTS/Storage/Json/SqliteJsonGeometryWktReaderWriter.cs +++ b/src/EFCore.Sqlite.NTS/Storage/Json/SqliteJsonGeometryWktReaderWriter.cs @@ -31,4 +31,9 @@ public override Geometry FromJsonTyped(ref Utf8JsonReaderManager manager, object /// public override void ToJsonTyped(Utf8JsonWriter writer, Geometry value) => writer.WriteStringValue(value.ToText()); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/ChangeTracking/ListComparer.cs b/src/EFCore/ChangeTracking/ListComparer.cs index d64272f8079..7df3819193a 100644 --- a/src/EFCore/ChangeTracking/ListComparer.cs +++ b/src/EFCore/ChangeTracking/ListComparer.cs @@ -37,7 +37,14 @@ public ListComparer(ValueComparer elementComparer) /// public ValueComparer ElementComparer { get; } - private static bool Compare(IEnumerable? a, IEnumerable? b, ValueComparer elementComparer) + /// + /// 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 static bool Compare(IEnumerable? a, IEnumerable? b, ValueComparer elementComparer) { if (ReferenceEquals(a, b)) { diff --git a/src/EFCore/ChangeTracking/NullableValueTypeListComparer.cs b/src/EFCore/ChangeTracking/NullableValueTypeListComparer.cs index 7acbff280f9..67453dbe78b 100644 --- a/src/EFCore/ChangeTracking/NullableValueTypeListComparer.cs +++ b/src/EFCore/ChangeTracking/NullableValueTypeListComparer.cs @@ -38,7 +38,14 @@ public NullableValueTypeListComparer(ValueComparer elementComparer) /// public ValueComparer ElementComparer { get; } - private static bool Compare(IEnumerable? a, IEnumerable? b, ValueComparer elementComparer) + /// + /// 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 static bool Compare(IEnumerable? a, IEnumerable? b, ValueComparer elementComparer) { if (ReferenceEquals(a, b)) { diff --git a/src/EFCore/ChangeTracking/ValueComparer.cs b/src/EFCore/ChangeTracking/ValueComparer.cs index 2b5e80fd71f..1835113d36d 100644 --- a/src/EFCore/ChangeTracking/ValueComparer.cs +++ b/src/EFCore/ChangeTracking/ValueComparer.cs @@ -155,6 +155,11 @@ protected ValueComparer( /// public virtual LambdaExpression EqualsExpression { get; } + /// + /// The object comparison expression. + /// + public abstract LambdaExpression ObjectEqualsExpression { get; } + /// /// The hash code expression. /// diff --git a/src/EFCore/ChangeTracking/ValueComparer`.cs b/src/EFCore/ChangeTracking/ValueComparer`.cs index 84f7632f435..f2fe7e8ed56 100644 --- a/src/EFCore/ChangeTracking/ValueComparer`.cs +++ b/src/EFCore/ChangeTracking/ValueComparer`.cs @@ -38,6 +38,7 @@ public class ValueComparer private Func? _equals; private Func? _hashCode; private Func? _snapshot; + private LambdaExpression? _objectEqualsExpression; /// /// Creates a new with a default comparison @@ -248,6 +249,34 @@ public override bool Equals(object? left, object? right) return v1Null || v2Null ? v1Null && v2Null : Equals((T?)left, (T?)right); } + /// + public override LambdaExpression ObjectEqualsExpression + { + get + { + if (_objectEqualsExpression == null) + { + var left = Expression.Parameter(typeof(object), "left"); + var right = Expression.Parameter(typeof(object), "right"); + + _objectEqualsExpression = Expression.Lambda>( + Expression.Condition( + Expression.Equal(left, Expression.Constant(null)), + Expression.Equal(right, Expression.Constant(null)), + Expression.AndAlso( + Expression.NotEqual(right, Expression.Constant(null)), + Expression.Invoke( + EqualsExpression, + Expression.Convert(left, typeof(T)), + Expression.Convert(right, typeof(T))))), + left, + right); + } + + return _objectEqualsExpression; + } + } + /// /// Returns the hash code for the given instance. /// diff --git a/src/EFCore/EFCore.csproj b/src/EFCore/EFCore.csproj index 77b927ca73d..e217d1fa7cb 100644 --- a/src/EFCore/EFCore.csproj +++ b/src/EFCore/EFCore.csproj @@ -13,6 +13,7 @@ Microsoft.EntityFrameworkCore.DbSet Microsoft.EntityFrameworkCore true true + $(NoWarn);EF9100 diff --git a/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs b/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs index 60b90236db4..c76d4aa9c84 100644 --- a/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs +++ b/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs @@ -83,6 +83,7 @@ public static readonly IDictionary CoreServices { typeof(IMemoryCache), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IEvaluatableExpressionFilter), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(INavigationExpansionExtensibilityHelper), new ServiceCharacteristics(ServiceLifetime.Singleton) }, + { typeof(ILiftableConstantFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IExceptionDetector), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IJsonValueReaderWriterSource), new ServiceCharacteristics(ServiceLifetime.Singleton) }, { typeof(IProviderConventionSetBuilder), new ServiceCharacteristics(ServiceLifetime.Scoped) }, @@ -125,6 +126,7 @@ public static readonly IDictionary CoreServices { typeof(IShapedQueryCompilingExpressionVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IDbContextLogger), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IAdHocMapper), new ServiceCharacteristics(ServiceLifetime.Scoped) }, + { typeof(ILiftableConstantProcessor), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(ILazyLoader), new ServiceCharacteristics(ServiceLifetime.Transient) }, { typeof(ILazyLoaderFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IParameterBindingFactory), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) }, @@ -309,6 +311,8 @@ public virtual EntityFrameworkServicesBuilder TryAddCoreServices() TryAdd(); TryAdd(); TryAdd(); + TryAdd(); + TryAdd(); TryAdd( p => p.GetService()?.FindExtension()?.DbContextLogger @@ -329,12 +333,12 @@ public virtual EntityFrameworkServicesBuilder TryAddCoreServices() .AddDependencySingleton() .AddDependencySingleton() .AddDependencySingleton() - .AddDependencySingleton() .AddDependencySingleton() .AddDependencySingleton() .AddDependencySingleton() .AddDependencySingleton() .AddDependencySingleton() + .AddDependencySingleton() .AddDependencyScoped() .AddDependencyScoped() .AddDependencyScoped() @@ -344,6 +348,7 @@ public virtual EntityFrameworkServicesBuilder TryAddCoreServices() .AddDependencyScoped() .AddDependencyScoped() .AddDependencyScoped() + .AddDependencyScoped() .AddDependencyScoped() .AddDependencyScoped() .AddDependencyScoped() diff --git a/src/EFCore/Infrastructure/Internal/LazyLoader.cs b/src/EFCore/Infrastructure/Internal/LazyLoader.cs index 569bd45cd63..b2ea2ec93d9 100644 --- a/src/EFCore/Infrastructure/Internal/LazyLoader.cs +++ b/src/EFCore/Infrastructure/Internal/LazyLoader.cs @@ -43,10 +43,10 @@ public LazyLoader( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual void Injected(DbContext context, object entity, ParameterBindingInfo bindingInfo) + public virtual void Injected(DbContext context, object entity, QueryTrackingBehavior? queryTrackingBehavior, ITypeBase structuralType) { - _queryTrackingBehavior = bindingInfo.QueryTrackingBehavior; - _nonLazyNavigations ??= InitNavigationsMetadata(bindingInfo.StructuralType as IEntityType + _queryTrackingBehavior = queryTrackingBehavior; + _nonLazyNavigations ??= InitNavigationsMetadata(structuralType as IEntityType ?? throw new NotImplementedException("Navigations on complex types are not supported")); } diff --git a/src/EFCore/Internal/IInjectableService.cs b/src/EFCore/Internal/IInjectableService.cs index 0ff7e38b67b..4f90168ef9e 100644 --- a/src/EFCore/Internal/IInjectableService.cs +++ b/src/EFCore/Internal/IInjectableService.cs @@ -22,7 +22,7 @@ public interface IInjectableService /// 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. /// - void Injected(DbContext context, object entity, ParameterBindingInfo bindingInfo); + void Injected(DbContext context, object entity, QueryTrackingBehavior? queryTrackingBehavior, ITypeBase structuralType); /// /// diff --git a/src/EFCore/Metadata/ContextParameterBinding.cs b/src/EFCore/Metadata/ContextParameterBinding.cs index beb71d5afa4..8381287d04a 100644 --- a/src/EFCore/Metadata/ContextParameterBinding.cs +++ b/src/EFCore/Metadata/ContextParameterBinding.cs @@ -48,6 +48,24 @@ var propertyExpression : propertyExpression; } + /// + public override Expression BindToParameter( + Expression materializationExpression, + ParameterBindingInfo bindingInfo) + { + Check.NotNull(materializationExpression, nameof(materializationExpression)); + Check.NotNull(bindingInfo, nameof(bindingInfo)); + + var propertyExpression + = Expression.Property( + materializationExpression, + MaterializationContext.ContextProperty); + + return ServiceType != typeof(DbContext) + ? Expression.TypeAs(propertyExpression, ServiceType) + : propertyExpression; + } + /// /// Creates a copy that contains the given consumed properties. /// diff --git a/src/EFCore/Metadata/DependencyInjectionParameterBinding.cs b/src/EFCore/Metadata/DependencyInjectionParameterBinding.cs index 1cd9b2b55ca..0c38cce5955 100644 --- a/src/EFCore/Metadata/DependencyInjectionParameterBinding.cs +++ b/src/EFCore/Metadata/DependencyInjectionParameterBinding.cs @@ -56,6 +56,23 @@ public override Expression BindToParameter( typeof(IInfrastructure))); } + /// + public override Expression BindToParameter( + Expression materializationExpression, + ParameterBindingInfo bindingInfo) + { + Check.NotNull(materializationExpression, nameof(materializationExpression)); + Check.NotNull(bindingInfo, nameof(bindingInfo)); + + return Expression.Call( + GetServiceMethod.MakeGenericMethod(ServiceType), + Expression.Convert( + Expression.Property( + materializationExpression, + MaterializationContext.ContextProperty), + typeof(IInfrastructure))); + } + /// /// Creates a copy that contains the given consumed properties. /// diff --git a/src/EFCore/Metadata/EntityTypeParameterBinding.cs b/src/EFCore/Metadata/EntityTypeParameterBinding.cs index 1826eb8ab9c..9c152fbd7cf 100644 --- a/src/EFCore/Metadata/EntityTypeParameterBinding.cs +++ b/src/EFCore/Metadata/EntityTypeParameterBinding.cs @@ -41,6 +41,22 @@ public override Expression BindToParameter( : result; } + /// + public override Expression BindToParameter( + Expression materializationExpression, + ParameterBindingInfo bindingInfo) + { + var bindingInfoExpression = (Expression)Expression.Constant(bindingInfo); + + var result = bindingInfoExpression.Type == typeof(IEntityType) || bindingInfoExpression.Type == typeof(IComplexType) + ? bindingInfoExpression + : Expression.Constant(bindingInfo.StructuralType); + + return ServiceType != typeof(ITypeBase) + ? Expression.Convert(result, ServiceType) + : result; + } + /// /// Creates a copy that contains the given consumed properties. /// diff --git a/src/EFCore/Metadata/FactoryMethodBinding.cs b/src/EFCore/Metadata/FactoryMethodBinding.cs index fadf66c8cf5..ef6c97e630b 100644 --- a/src/EFCore/Metadata/FactoryMethodBinding.cs +++ b/src/EFCore/Metadata/FactoryMethodBinding.cs @@ -12,6 +12,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; public class FactoryMethodBinding : InstantiationBinding { private readonly object? _factoryInstance; + private readonly Expression? _factoryInstanceExpression; private readonly MethodInfo _factoryMethod; /// @@ -50,6 +51,29 @@ public FactoryMethodBinding( Check.NotNull(factoryInstance, nameof(factoryInstance)); _factoryInstance = factoryInstance; + _factoryInstanceExpression = Expression.Constant(_factoryInstance); + } + + /// + /// Creates a new instance for a non-static factory method. + /// + /// The object on which the factory method should be called. + /// Expression representing the factory instance object. + /// The factory method to bind to. + /// The parameters to use. + /// The CLR type of the instance created by the factory method. + public FactoryMethodBinding( + object factoryInstance, + Expression factoryInstanceExpression, + MethodInfo factoryMethod, + IReadOnlyList parameterBindings, + Type runtimeType) + : this(factoryMethod, parameterBindings, runtimeType) + { + Check.NotNull(factoryInstance, nameof(factoryInstance)); + + _factoryInstance = factoryInstance; + _factoryInstanceExpression = factoryInstanceExpression; } /// @@ -67,7 +91,7 @@ Expression expression _factoryMethod, arguments) : Expression.Call( - Expression.Constant(_factoryInstance), + _factoryInstanceExpression, _factoryMethod, arguments); @@ -92,5 +116,5 @@ Expression expression public override InstantiationBinding With(IReadOnlyList parameterBindings) => _factoryInstance == null ? new FactoryMethodBinding(_factoryMethod, parameterBindings, RuntimeType) - : new FactoryMethodBinding(_factoryInstance, _factoryMethod, parameterBindings, RuntimeType); + : new FactoryMethodBinding(_factoryInstance, _factoryInstanceExpression!, _factoryMethod, parameterBindings, RuntimeType); } diff --git a/src/EFCore/Metadata/ServiceParameterBinding.cs b/src/EFCore/Metadata/ServiceParameterBinding.cs index 74ffe10b924..b4a4aa531b8 100644 --- a/src/EFCore/Metadata/ServiceParameterBinding.cs +++ b/src/EFCore/Metadata/ServiceParameterBinding.cs @@ -56,7 +56,7 @@ public override Expression BindToParameter(ParameterBindingInfo bindingInfo) return BindToParameter( bindingInfo.MaterializationContextExpression, - Expression.Constant(bindingInfo)); + bindingInfo); } /// @@ -70,6 +70,17 @@ public abstract Expression BindToParameter( Expression materializationExpression, Expression bindingInfoExpression); + /// + /// Creates an expression tree representing the binding of the value of a property from a + /// materialization expression to a parameter of the constructor, factory method, etc. + /// + /// The expression representing the materialization context. + /// The parameter binding information. + /// The expression tree. + public abstract Expression BindToParameter( + Expression materializationExpression, + ParameterBindingInfo bindingInfo); + /// /// A delegate to set a CLR service property on an entity instance. /// diff --git a/src/EFCore/Query/ILiftableConstantFactory.cs b/src/EFCore/Query/ILiftableConstantFactory.cs new file mode 100644 index 00000000000..46110b04d30 --- /dev/null +++ b/src/EFCore/Query/ILiftableConstantFactory.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.EntityFrameworkCore.Query; + +/// +/// This is an experimental API used by the Entity Framework Core feature and it is 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. +/// +[Experimental(EFDiagnostics.PrecompiledQueryExperimental)] +public interface ILiftableConstantFactory +{ + /// + /// This is an experimental API used by the Entity Framework Core feature and it is 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. + /// + LiftableConstantExpression CreateLiftableConstant( + ConstantExpression originalExpression, + Expression> resolverExpression, + string variableName, + Type type); +} diff --git a/src/EFCore/Query/ILiftableConstantProcessor.cs b/src/EFCore/Query/ILiftableConstantProcessor.cs new file mode 100644 index 00000000000..0b3bdb6aa7e --- /dev/null +++ b/src/EFCore/Query/ILiftableConstantProcessor.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.EntityFrameworkCore.Query; + +/// +/// This is an experimental API used by the Entity Framework Core feature and it is 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. +/// +[Experimental(EFDiagnostics.PrecompiledQueryExperimental)] +public interface ILiftableConstantProcessor +{ + /// + /// Exposes all constants that have been lifted during the last invocation of . + /// + IReadOnlyList<(ParameterExpression Parameter, Expression Expression)> LiftedConstants { get; } + + /// + /// Inlines all liftable constants as simple nodes in the tree, containing the result of + /// evaluating the liftable constants' resolvers. + /// + /// An expression containing nodes. + /// + /// An expression tree containing nodes instead of nodes. + /// + /// + /// Liftable constant inlining is performed in the regular, non-precompiled query pipeline flow. + /// + Expression InlineConstants(Expression expression); + + /// + /// Lifts all nodes, embedding in their place and + /// exposing the parameter and resolver via . + /// + /// An expression containing nodes. + /// + /// The to be embedded in the liftable constant nodes' resolvers, instead of their lambda + /// parameter. + /// + /// + /// A set of variables already in use, for uniquification. Any generates variables will be added to this set. + /// + /// + /// An expression tree containing nodes instead of nodes. + /// + /// + /// Constant lifting is performed in the precompiled query pipeline flow. + /// + Expression LiftConstants(Expression expression, ParameterExpression contextParameter, HashSet variableNames); +} diff --git a/src/EFCore/Query/Internal/EntityMaterializerSource.cs b/src/EFCore/Query/Internal/EntityMaterializerSource.cs index a4046b3b6e8..4c3a46658cb 100644 --- a/src/EFCore/Query/Internal/EntityMaterializerSource.cs +++ b/src/EFCore/Query/Internal/EntityMaterializerSource.cs @@ -18,6 +18,12 @@ public class EntityMaterializerSource : IEntityMaterializerSource private static readonly MethodInfo InjectableServiceInjectedMethod = typeof(IInjectableService).GetMethod(nameof(IInjectableService.Injected))!; + private static readonly ConstructorInfo EntityMaterializerSourceParametersCtor + = typeof(EntityMaterializerSourceParameters).GetConstructor([typeof(ITypeBase), typeof(string), typeof(QueryTrackingBehavior?)])!; + + private static readonly ConstructorInfo ParameterBindingInfoCtor + = typeof(ParameterBindingInfo).GetConstructor([typeof(EntityMaterializerSourceParameters), typeof(Expression)])!; + private ConcurrentDictionary>? _materializers; private ConcurrentDictionary>? _emptyMaterializers; private readonly List _bindingInterceptors; @@ -86,7 +92,20 @@ public Expression CreateMaterializeExpression( } var constructorBinding = ModifyBindings(structuralType, structuralType.ConstructorBinding!); + + var entityMaterializerSourceParametersExpression = Expression.New( + EntityMaterializerSourceParametersCtor, + Expression.Constant(structuralType), + Expression.Constant(entityInstanceName), + Expression.Constant(parameters.QueryTrackingBehavior, typeof(QueryTrackingBehavior?))); + var bindingInfo = new ParameterBindingInfo(parameters, materializationContextExpression); + + var bindingInfoExpression = Expression.New( + ParameterBindingInfoCtor, + entityMaterializerSourceParametersExpression, + Expression.Constant(materializationContextExpression)); + var blockExpressions = new List(); var instanceVariable = Expression.Variable(constructorBinding.RuntimeType, entityInstanceName); @@ -129,6 +148,7 @@ public Expression CreateMaterializeExpression( properties, _materializationInterceptor, bindingInfo, + bindingInfoExpression, constructorExpression, instanceVariable, blockExpressions); @@ -239,7 +259,8 @@ private static void AddAttachServiceExpressions( InjectableServiceInjectedMethod, getContext, instanceVariable, - Expression.Constant(bindingInfo, typeof(ParameterBindingInfo))))); + Expression.Constant(bindingInfo.QueryTrackingBehavior, typeof(QueryTrackingBehavior?)), + Expression.Constant(bindingInfo.StructuralType)))); } } @@ -308,6 +329,7 @@ private Expression CreateInterceptionMaterializeExpression( HashSet properties, IMaterializationInterceptor materializationInterceptor, ParameterBindingInfo bindingInfo, + Expression bindingInfoExpression, Expression constructorExpression, ParameterExpression instanceVariable, List blockExpressions) @@ -337,7 +359,7 @@ private Expression CreateInterceptionMaterializeExpression( blockExpressions.Add( Expression.Assign( accessorDictionaryVariable, - CreateAccessorDictionaryExpression())); + CreateAccessorDictionaryExpression(structuralType, bindingInfo))); blockExpressions.Add( Expression.Assign( materializationDataVariable, @@ -409,7 +431,7 @@ private Expression CreateInterceptionMaterializeExpression( bindingInfo.ServiceInstances.Concat(new[] { accessorDictionaryVariable, materializationDataVariable, creatingResultVariable }), blockExpressions); - BlockExpression CreateAccessorDictionaryExpression() + static BlockExpression CreateAccessorDictionaryExpression(ITypeBase structuralType, ParameterBindingInfo bindingInfo) { var dictionaryVariable = Expression.Variable( typeof(Dictionary)>), "dictionary"); @@ -534,6 +556,15 @@ public virtual Func GetEmptyMaterializer(IEntity var bindingInfo = new ParameterBindingInfo( new EntityMaterializerSourceParameters(entityType, "instance", null), materializationContextExpression); + var bindingInfoExpression = Expression.New( + ParameterBindingInfoCtor, + Expression.New( + EntityMaterializerSourceParametersCtor, + Expression.Constant(entityType), + Expression.Constant("instance"), + Expression.Constant(null, typeof(QueryTrackingBehavior?))), + Expression.Constant(materializationContextExpression)); + var blockExpressions = new List(); var instanceVariable = Expression.Variable(binding.RuntimeType, "instance"); var serviceProperties = entityType.GetServiceProperties().ToList(); @@ -560,6 +591,7 @@ public virtual Func GetEmptyMaterializer(IEntity [], _materializationInterceptor, bindingInfo, + bindingInfoExpression, constructorExpression, instanceVariable, blockExpressions), diff --git a/src/EFCore/Query/Internal/LiftableConstantExpressionDependencies.cs b/src/EFCore/Query/Internal/LiftableConstantExpressionDependencies.cs new file mode 100644 index 00000000000..bdf28e58c33 --- /dev/null +++ b/src/EFCore/Query/Internal/LiftableConstantExpressionDependencies.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query.Internal; + +/// +/// +/// Service dependencies parameter class for +/// +/// +/// This type is typically used by database providers (and other extensions). It is generally +/// not used in application code. +/// +/// +/// +/// +/// Do not construct instances of this class directly from either provider or application code as the +/// constructor signature may change as new dependencies are added. Instead, use this type in +/// your constructor so that an instance will be created and injected automatically by the +/// dependency injection container. To create an instance with some dependent services replaced, +/// first resolve the object from the dependency injection container, then replace selected +/// services using the C# 'with' operator. 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 . +/// +/// +public sealed record LiftableConstantExpressionDependencies +{ +} diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs index 26388a01c89..fec5ffba751 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs @@ -501,7 +501,7 @@ outerKey is NewArrayExpression newArrayExpression private sealed class IncludeExpandingExpressionVisitor : ExpandingExpressionVisitor { private static readonly MethodInfo FetchJoinEntityMethodInfo = - typeof(IncludeExpandingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(FetchJoinEntity))!; + typeof(NavigationExpandingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(FetchJoinEntity))!; private readonly bool _queryStateManager; private readonly bool _ignoreAutoIncludes; @@ -892,11 +892,6 @@ private Expression ExpandIncludesHelper(Expression root, EntityReference entityR return result; } -#pragma warning disable IDE0060 // Remove unused parameter - private static TTarget FetchJoinEntity(TJoin joinEntity, TTarget targetEntity) - => targetEntity; -#pragma warning restore IDE0060 // Remove unused parameter - private static Expression RemapFilterExpressionForJoinEntity( ParameterExpression filterParameter, Expression filterExpressionBody, @@ -1383,4 +1378,16 @@ public override ExpressionType NodeType public IEntityType? EntityType { get; } } } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] +#pragma warning disable IDE0060 // Remove unused parameter + public static TTarget FetchJoinEntity(TJoin joinEntity, TTarget targetEntity) + => targetEntity; +#pragma warning restore IDE0060 // Remove unused parameter } diff --git a/src/EFCore/Query/LiftableConstantExpression.cs b/src/EFCore/Query/LiftableConstantExpression.cs new file mode 100644 index 00000000000..d631d2504ed --- /dev/null +++ b/src/EFCore/Query/LiftableConstantExpression.cs @@ -0,0 +1,104 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.EntityFrameworkCore.Query; + +/// +/// A node containing an expression expressing how to obtain a constant value, which may get lifted out of an expression tree. +/// +/// +/// +/// When the expression tree is compiled, the constant value can simply be evaluated beforehand, and a +/// expression can directly reference the result. +/// +/// +/// When the expression tree is translated to source code instead (in query pre-compilation), the expression can be rendered out +/// separately, to be assigned to a variable, and this node is replaced by a reference to that variable. +/// +/// +[DebuggerDisplay("{Microsoft.EntityFrameworkCore.Query.ExpressionPrinter.Print(this), nq}")] +[Experimental(EFDiagnostics.PrecompiledQueryExperimental)] +public class LiftableConstantExpression : Expression, IPrintableExpression +{ + /// + /// This is an experimental API used by the Entity Framework Core feature and it is 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 LiftableConstantExpression( + ConstantExpression originalExpression, + LambdaExpression resolverExpression, + string variableName, + Type type) + { + OriginalExpression = originalExpression; + ResolverExpression = resolverExpression; + VariableName = char.ToLower(variableName[0]) + variableName[1..]; + Type = type; + } + + /// + /// This is an experimental API used by the Entity Framework Core feature and it is 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 ConstantExpression OriginalExpression { get; } + + /// + /// This is an experimental API used by the Entity Framework Core feature and it is 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 LambdaExpression ResolverExpression { get; } + + /// + /// This is an experimental API used by the Entity Framework Core feature and it is 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 string VariableName { get; } + + /// + public override Type Type { get; } + + /// + public override ExpressionType NodeType + => ExpressionType.Extension; + + // TODO: Complete other expression stuff (equality, etc.) + + /// + protected override Expression VisitChildren(ExpressionVisitor visitor) + { + var resolverExpression = (LambdaExpression)visitor.Visit(ResolverExpression); + + return Update(resolverExpression); + } + + /// + /// Creates a new expression that is like this one, but using the supplied children. If all of the children are the same, it will + /// return this expression. + /// + /// The property of the result. + /// This expression if no children changed, or an expression with the updated children. + public virtual LiftableConstantExpression Update(LambdaExpression resolverExpression) + => resolverExpression != ResolverExpression + ? new LiftableConstantExpression(OriginalExpression, resolverExpression, VariableName, Type) + : this; + + /// + public void Print(ExpressionPrinter expressionPrinter) + { + expressionPrinter.Append("[LIFTABLE Constant: "); + expressionPrinter.Visit(OriginalExpression); + expressionPrinter.Append(" | Resolver: "); + expressionPrinter.Visit(ResolverExpression); + expressionPrinter.Append("]"); + } +} diff --git a/src/EFCore/Query/LiftableConstantExpressionHelpers.cs b/src/EFCore/Query/LiftableConstantExpressionHelpers.cs new file mode 100644 index 00000000000..8c6176a5118 --- /dev/null +++ b/src/EFCore/Query/LiftableConstantExpressionHelpers.cs @@ -0,0 +1,285 @@ +// 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; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Net; +using System.Runtime.CompilerServices; +using System.Text; +using Microsoft.EntityFrameworkCore.Internal; + +namespace Microsoft.EntityFrameworkCore.Query; + +/// +/// 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. +/// + +[Experimental(EFDiagnostics.PrecompiledQueryExperimental)] +public class LiftableConstantExpressionHelpers +{ + private static readonly MethodInfo ModelFindEntiyTypeMethod = + typeof(IModel).GetRuntimeMethod(nameof(IModel.FindEntityType), [typeof(string)])!; + + private static readonly MethodInfo RuntimeModelFindAdHocEntiyTypeMethod = + typeof(RuntimeModel).GetRuntimeMethod(nameof(RuntimeModel.FindAdHocEntityType), [typeof(Type)])!; + + private static readonly MethodInfo TypeBaseFindComplexPropertyMethod = + typeof(ITypeBase).GetRuntimeMethod(nameof(ITypeBase.FindComplexProperty), [typeof(string)])!; + + private static readonly MethodInfo TypeBaseFindPropertyMethod = + typeof(ITypeBase).GetRuntimeMethod(nameof(ITypeBase.FindProperty), [typeof(string)])!; + + private static readonly MethodInfo TypeBaseFindServicePropertyMethod = + typeof(IEntityType).GetRuntimeMethod(nameof(IEntityType.FindServiceProperty), [typeof(string)])!; + + private static readonly MethodInfo EntityTypeFindNavigationMethod = + typeof(IEntityType).GetRuntimeMethod(nameof(IEntityType.FindNavigation), [typeof(string)])!; + + private static readonly MethodInfo EntityTypeFindSkipNavigationMethod = + typeof(IEntityType).GetRuntimeMethod(nameof(IEntityType.FindSkipNavigation), [typeof(string)])!; + + private static readonly MethodInfo NavigationBaseClrCollectionAccessorMethod = + typeof(INavigationBase).GetRuntimeMethod(nameof(INavigationBase.GetCollectionAccessor), [])!; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static bool IsLiteral(object? value) + { + if (value == StructuralComparisons.StructuralEqualityComparer) + { + return true; + } + + return value switch + { + int or long or uint or ulong or short or sbyte or ushort or byte or double or float or decimal or string or char or bool => true, + null or Type or Enum or CultureInfo or Encoding or IPAddress => true, + TimeSpan or DateTime or DateTimeOffset or DateOnly or TimeOnly or Guid => true, + ITuple tuple + when tuple.GetType() is { IsGenericType: true } tupleType + && tupleType.Name.StartsWith("ValueTuple`", StringComparison.Ordinal) + && tupleType.Namespace == "System" + => IsTupleLiteral(tuple), + + Array array => IsCollectionOfLiterals(array), + IList list => IsCollectionOfLiterals(list), + + _ => false + }; + + bool IsTupleLiteral(ITuple tuple) + { + for (var i = 0; i < tuple.Length; i++) + { + if (!IsLiteral(tuple[i])) + { + return false; + } + } + + return true; + } + + bool IsCollectionOfLiterals(IEnumerable enumerable) + { + foreach (var enumerableElement in enumerable) + { + if (!IsLiteral(enumerableElement)) + { + return false; + } + } + + return true; + } + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static Expression BuildMemberAccessForEntityOrComplexType(ITypeBase targetType, ParameterExpression liftableConstantContextParameter) + { + var (rootEntityType, complexTypes) = FindPathForEntityOrComplexType(targetType); + + Expression result; + + if (rootEntityType.IsAdHoc()) + { + result = Expression.Call( + Expression.Convert( + Expression.Property( + Expression.Property( + liftableConstantContextParameter, + nameof(MaterializerLiftableConstantContext.Dependencies)), + nameof(ShapedQueryCompilingExpressionVisitorDependencies.Model)), + typeof(RuntimeModel)), + RuntimeModelFindAdHocEntiyTypeMethod, + Expression.Constant(rootEntityType.ClrType)); + + } + else + { + result = Expression.Call( + Expression.Property( + Expression.Property( + liftableConstantContextParameter, + nameof(MaterializerLiftableConstantContext.Dependencies)), + nameof(ShapedQueryCompilingExpressionVisitorDependencies.Model)), + ModelFindEntiyTypeMethod, + Expression.Constant(rootEntityType.Name)); + } + + foreach (var complexType in complexTypes) + { + var complexPropertyName = complexType.ComplexProperty.Name; + result = Expression.Property( + Expression.Call(result, TypeBaseFindComplexPropertyMethod, Expression.Constant(complexPropertyName)), + nameof(IComplexProperty.ComplexType)); + } + + return result; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static Expression> BuildMemberAccessLambdaForEntityOrComplexType(ITypeBase type) + { + var prm = Expression.Parameter(typeof(MaterializerLiftableConstantContext)); + var body = BuildMemberAccessForEntityOrComplexType(type, prm); + + return Expression.Lambda>(body, prm); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static Expression BuildMemberAccessForProperty(IPropertyBase property, ParameterExpression liftableConstantContextParameter) + { + var declaringType = property.DeclaringType; + var declaringTypeMemberAccessExpression = BuildMemberAccessForEntityOrComplexType(declaringType, liftableConstantContextParameter); + + return Expression.Call( + declaringTypeMemberAccessExpression, + property is IServiceProperty ? TypeBaseFindServicePropertyMethod : TypeBaseFindPropertyMethod, + Expression.Constant(property.Name)); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static Expression> BuildMemberAccessLambdaForProperty(IPropertyBase property) + { + var prm = Expression.Parameter(typeof(MaterializerLiftableConstantContext)); + var body = BuildMemberAccessForProperty(property, prm); + + return Expression.Lambda>(body, prm); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static Expression BuildNavigationAccess(INavigationBase navigation, ParameterExpression liftableConstantContextParameter) + { + var declaringType = navigation.DeclaringType; + var declaringTypeExpression = BuildMemberAccessForEntityOrComplexType(declaringType, liftableConstantContextParameter); + + var result = Expression.Call( + declaringTypeExpression, + navigation is ISkipNavigation ? EntityTypeFindSkipNavigationMethod : EntityTypeFindNavigationMethod, + Expression.Constant(navigation.Name)); + + return result; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static Expression> BuildNavigationAccessLambda(INavigationBase navigation) + { + var prm = Expression.Parameter(typeof(MaterializerLiftableConstantContext)); + var body = BuildNavigationAccess(navigation, prm); + + return Expression.Lambda>(body, prm); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static Expression BuildClrCollectionAccessor(INavigationBase navigation, ParameterExpression liftableConstantContextParameter) + { + var navigationAccessExpression = BuildNavigationAccess(navigation, liftableConstantContextParameter); + var result = Expression.Call(navigationAccessExpression, NavigationBaseClrCollectionAccessorMethod); + + return result; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static Expression> BuildClrCollectionAccessorLambda(INavigationBase navigation) + { + var prm = Expression.Parameter(typeof(MaterializerLiftableConstantContext)); + var body = BuildClrCollectionAccessor(navigation, prm); + + return Expression.Lambda>(body, prm); + } + + private static (IEntityType RootEntity, List ComplexTypes) FindPathForEntityOrComplexType(ITypeBase targetType) + { + if (targetType is IEntityType targetEntity) + { + return (targetEntity, []); + } + + var targetComplexType = (IComplexType)targetType; + var declaringType = targetComplexType.ComplexProperty.DeclaringType; + if (declaringType is IEntityType declaringEntityType) + { + return (declaringEntityType, [targetComplexType]); + } + + var complexTypes = new List(); + while (declaringType is IComplexType complexType) + { + complexTypes.Insert(0, complexType); + declaringType = complexType.ComplexProperty.DeclaringType; + } + + complexTypes.Add(targetComplexType); + + return ((IEntityType)declaringType, complexTypes); + } +} diff --git a/src/EFCore/Query/LiftableConstantFactory.cs b/src/EFCore/Query/LiftableConstantFactory.cs new file mode 100644 index 00000000000..0b2de32949f --- /dev/null +++ b/src/EFCore/Query/LiftableConstantFactory.cs @@ -0,0 +1,35 @@ +// 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.Query.Internal; + +namespace Microsoft.EntityFrameworkCore.Query; + +/// +/// TODO +/// +public class LiftableConstantFactory : ILiftableConstantFactory +{ + /// + /// TODO + /// + public LiftableConstantFactory(LiftableConstantExpressionDependencies dependencies) + { + Dependencies = dependencies; + } + + /// + /// TODO + /// + public virtual LiftableConstantExpressionDependencies Dependencies { get; } + + /// + /// TODO + /// + public virtual LiftableConstantExpression CreateLiftableConstant( + ConstantExpression originalExpression, + Expression> resolverExpression, + string variableName, + Type type) + => new(originalExpression, resolverExpression, variableName, type); +} diff --git a/src/EFCore/Query/LiftableConstantProcessor.cs b/src/EFCore/Query/LiftableConstantProcessor.cs new file mode 100644 index 00000000000..65cd8af8226 --- /dev/null +++ b/src/EFCore/Query/LiftableConstantProcessor.cs @@ -0,0 +1,489 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.EntityFrameworkCore.Query; + +#pragma warning disable CS1591 + +/// +/// This is an experimental API used by the Entity Framework Core feature and it is 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. +/// +[Experimental(EFDiagnostics.PrecompiledQueryExperimental)] +public class LiftableConstantProcessor : ExpressionVisitor, ILiftableConstantProcessor +{ + private bool _inline; + private readonly MaterializerLiftableConstantContext _materializerLiftableConstantContext; + + /// + /// Exposes all constants that have been lifted during the last invocation of . + /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IReadOnlyList<(ParameterExpression Parameter, Expression Expression)> LiftedConstants { get; private set; } + = Array.Empty<(ParameterExpression Parameter, Expression Expression)>(); + + private sealed record LiftedConstant(ParameterExpression Parameter, Expression Expression, ParameterExpression? ReplacingParameter = null); + + private readonly List _liftedConstants = new(); + private readonly LiftedExpressionProcessor _liftedExpressionProcessor = new(); + private readonly LiftedConstantOptimizer _liftedConstantOptimizer = new(); + + private ParameterExpression? _contextParameter; + + private int _counter = 0; + + public LiftableConstantProcessor(ShapedQueryCompilingExpressionVisitorDependencies dependencies) + { + _materializerLiftableConstantContext = new(dependencies); + + _liftedConstants.Clear(); + } + + /// + /// Inlines all liftable constants as simple nodes in the tree, containing the result of + /// evaluating the liftable constants' resolvers. + /// + /// An expression containing nodes. + /// + /// An expression tree containing nodes instead of nodes. + /// + /// + /// + /// Liftable constant inlining is performed in the regular, non-precompiled query pipeline flow. + /// + /// + /// 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 Expression InlineConstants(Expression expression) + { + _liftedConstants.Clear(); + _inline = true; + + return Visit(expression); + } + + /// + /// Lifts all nodes, embedding in their place and + /// exposing the parameter and resolver via . + /// + /// An expression containing nodes. + /// + /// The to be embedded in the lifted constant nodes' resolvers, instead of their lambda + /// parameter. + /// + /// + /// A set of variables already in use, for uniquification. Any generates variables will be added to this set. + /// + /// + /// An expression tree containing nodes instead of nodes. + /// + public virtual Expression LiftConstants(Expression expression, ParameterExpression contextParameter, HashSet variableNames) + { + _liftedConstants.Clear(); + + _inline = false; + _contextParameter = contextParameter; + + var expressionAfterLifting = Visit(expression); + + // All liftable constant nodes have been lifted out. + // We'll now optimize them, looking for greatest common denominator tree fragments, in cases where e.g. two lifted constants look up + // the same entity type. + _liftedConstantOptimizer.Optimize(_liftedConstants); + + // Uniquify all variable names, taking into account possible remapping done in the optimization phase above + var replacedParameters = new Dictionary(); + // var (originalParameters, newParameters) = (new List(), new List()); + for (var i = 0; i < _liftedConstants.Count; i++) + { + var liftedConstant = _liftedConstants[i]; + + if (liftedConstant.ReplacingParameter is not null) + { + // This lifted constant is being removed, since it's a duplicate of another with the same expression. + // We still need to remap the parameter in the expression, but no uniquification etc. + replacedParameters.Add(liftedConstant.Parameter, + replacedParameters.TryGetValue(liftedConstant.ReplacingParameter, out var replacedReplacingParameter) + ? replacedReplacingParameter + : liftedConstant.ReplacingParameter); + _liftedConstants.RemoveAt(i--); + continue; + } + + var name = liftedConstant.Parameter.Name ?? "unknown"; + var baseName = name; + for (var j = 0; variableNames.Contains(name); j++) + { + name = baseName + j; + } + + variableNames.Add(name); + + if (name != liftedConstant.Parameter.Name) + { + var newParameter = Expression.Parameter(liftedConstant.Parameter.Type, name); + _liftedConstants[i] = liftedConstant with { Parameter = newParameter }; + replacedParameters.Add(liftedConstant.Parameter, newParameter); + } + } + + // Finally, apply all remapping (optimization, uniquification) to both the expression tree and to the lifted constant variable + // themselves. + + // var (originalParametersArray, newParametersArray) = (originalParameters.ToArray(), newParameters.ToArray()); + // var remappedExpression = ReplacingExpressionVisitor.Replace(originalParametersArray, newParametersArray, expressionAfterLifting); + var originalParameters = new Expression[replacedParameters.Count]; + var newParameters = new Expression[replacedParameters.Count]; + var index = 0; + foreach (var (originalParameter, newParameter) in replacedParameters) + { + originalParameters[index] = originalParameter; + newParameters[index] = newParameter; + index++; + } + var remappedExpression = ReplacingExpressionVisitor.Replace(originalParameters, newParameters, expressionAfterLifting); + + for (var i = 0; i < _liftedConstants.Count; i++) + { + var liftedConstant = _liftedConstants[i]; + var remappedLiftedConstantExpression = + ReplacingExpressionVisitor.Replace(originalParameters, newParameters, liftedConstant.Expression); + + if (remappedLiftedConstantExpression != liftedConstant.Expression) + { + _liftedConstants[i] = liftedConstant with { Expression = remappedLiftedConstantExpression }; + } + } + + LiftedConstants = _liftedConstants.Select(c => (c.Parameter, c.Expression)).ToArray(); + return remappedExpression; + } + + protected override Expression VisitExtension(Expression node) + { + if (node is LiftableConstantExpression liftedConstant) + { + return _inline + ? InlineConstant(liftedConstant) + : LiftConstant(liftedConstant); + } + + return base.VisitExtension(node); + } + + protected virtual ConstantExpression InlineConstant(LiftableConstantExpression liftableConstant) + { + if (liftableConstant.ResolverExpression is Expression> + resolverExpression) + { + _counter++; + try + { + if (_counter == 28) + { + Console.WriteLine( "fgf"); + //namelessParameter{0} => namelessParameter{0}.Dependencies.Model.FindEntityType("OwnedQueryTestBase+LeafA.LeafAAddress#OwnedAddress").FindNavigation("Country").GetCollectionAccessor() + } + + var resolver = resolverExpression.Compile(preferInterpretation: true); + var value = resolver(_materializerLiftableConstantContext); + + return Expression.Constant(value, liftableConstant.Type); + + }catch (Exception ex) + { + Console.WriteLine(_counter); + throw ex; + + } + } + + throw new InvalidOperationException( + $"Unknown resolved expression of type {liftableConstant.ResolverExpression.GetType().Name} found on liftable constant expression"); + } + + protected virtual ParameterExpression LiftConstant(LiftableConstantExpression liftableConstant) + { + var resolverLambda = liftableConstant.ResolverExpression; + var parameter = resolverLambda.Parameters[0]; + + // Extract the lambda body, replacing the lambda parameter with our lifted constant context parameter, and also inline any captured + // literals + var body = _liftedExpressionProcessor.Process(resolverLambda.Body, parameter, _contextParameter!); + + // If the lambda returns a value type, a Convert to object node gets needed that we need to unwrap + if (body is UnaryExpression { NodeType: ExpressionType.Convert } convertNode + && convertNode.Type == typeof(object)) + { + body = convertNode.Operand; + } + + // Register the lifted constant; note that the name will be uniquified later + var variableParameter = Expression.Parameter(liftableConstant.Type, liftableConstant.VariableName); + _liftedConstants.Add(new(variableParameter, body)); + + return variableParameter; + } + + private sealed class LiftedConstantOptimizer : ExpressionVisitor + { + private List _liftedConstants = null!; + + private sealed record ExpressionInfo(ExpressionStatus Status, ParameterExpression? Parameter = null, string? PreferredName = null); + private readonly Dictionary _indexedExpressions = new(ExpressionEqualityComparer.Instance); + private LiftedConstant _currentLiftedConstant = null!; + private bool _firstPass; + private int _index; + + public void Optimize(List liftedConstants) + { + _liftedConstants = liftedConstants; + _indexedExpressions.Clear(); + + _firstPass = true; + + // Phase 1: recursively seek out tree fragments which appear more than once across the lifted constants. These will be extracted + // out to separate variables. + foreach (var liftedConstant in liftedConstants) + { + _currentLiftedConstant = liftedConstant; + Visit(liftedConstant.Expression); + } + + // Filter out fragments which don't appear at least once + foreach (var (expression, expressionInfo) in _indexedExpressions) + { + if (expressionInfo.Status == ExpressionStatus.SeenOnce) + { + _indexedExpressions.Remove(expression); + continue; + } + + Check.DebugAssert(expressionInfo.Status == ExpressionStatus.SeenMultipleTimes, + "expressionInfo.Status == ExpressionStatus.SeenMultipleTimes"); + } + + // Second pass: extract common denominator tree fragments to separate variables + _firstPass = false; + for (_index = 0; _index < liftedConstants.Count; _index++) + { + _currentLiftedConstant = _liftedConstants[_index]; + if (_indexedExpressions.TryGetValue(_currentLiftedConstant.Expression, out var expressionInfo) + && expressionInfo.Status == ExpressionStatus.Extracted) + { + // This entire lifted constant has already been extracted before, so we no longer need it as a separate variable. + _liftedConstants[_index] = _currentLiftedConstant with { ReplacingParameter = expressionInfo.Parameter }; + + continue; + } + + var optimizedExpression = Visit(_currentLiftedConstant.Expression); + if (optimizedExpression != _currentLiftedConstant.Expression) + { + _liftedConstants[_index] = _currentLiftedConstant with { Expression = optimizedExpression }; + } + } + } + + [return: NotNullIfNotNull(nameof(node))] + public override Expression? Visit(Expression? node) + { + if (node is null) + { + return null; + } + + if (node is ParameterExpression or ConstantExpression || node.Type.IsAssignableTo(typeof(LambdaExpression))) + { + return node; + } + + if (_firstPass) + { + var preferredName = ReferenceEquals(node, _currentLiftedConstant.Expression) + ? _currentLiftedConstant.Parameter.Name + : null; + + if (!_indexedExpressions.TryGetValue(node, out var expressionInfo)) + { + // Unseen expression, add it to the dictionary with a null value, to indicate it's only a candidate at this point. + _indexedExpressions[node] = new(ExpressionStatus.SeenOnce, PreferredName: preferredName); + return base.Visit(node); + } + + // We've already seen this expression. + if (expressionInfo.Status == ExpressionStatus.SeenOnce + || expressionInfo.PreferredName is null && preferredName is not null) + { + // This is the 2nd time we're seeing the expression - mark it as a common denominator + _indexedExpressions[node] = _indexedExpressions[node] with + { + Status = ExpressionStatus.SeenMultipleTimes, + PreferredName = preferredName + }; + } + + // We've already seen and indexed this expression, no need to do it again + return node; + } + else + { + // 2nd pass + if (_indexedExpressions.TryGetValue(node, out var expressionInfo) && expressionInfo.Status != ExpressionStatus.SeenOnce) + { + // This fragment is common across multiple lifted constants. + if (expressionInfo.Status == ExpressionStatus.SeenMultipleTimes) + { + // This fragment hasn't yet been extracted out to its own variable in the 2nd pass. + + // If this happens to be a top-level node in the lifted constant, no need to extract an additional variable - just + // use that as the "extracted" parameter further down. + if (ReferenceEquals(node, _currentLiftedConstant.Expression)) + { + _indexedExpressions[node] = new(ExpressionStatus.Extracted, _currentLiftedConstant.Parameter); + return base.Visit(node); + } + + // Otherwise, we need to extract a new variable, integrating it just before this one. + var parameter = Expression.Parameter(node.Type, node switch + { + _ when expressionInfo.PreferredName is not null => expressionInfo.PreferredName, + MemberExpression me => char.ToLowerInvariant(me.Member.Name[0]) + me.Member.Name[1..], + MethodCallExpression mce => char.ToLowerInvariant(mce.Method.Name[0]) + mce.Method.Name[1..], + _ => "unknown" + }); + + var visitedNode = base.Visit(node); + _liftedConstants.Insert(_index++, new(parameter, visitedNode)); + + // Mark this node as having been extracted, to prevent it from getting extracted again + expressionInfo = _indexedExpressions[node] = new(ExpressionStatus.Extracted, parameter); + } + + Check.DebugAssert(expressionInfo.Parameter is not null, "expressionInfo.Parameter is not null"); + + return expressionInfo.Parameter; + } + + // This specific fragment only appears once across the lifted constants; keep going down. + return base.Visit(node); + } + } + + private enum ExpressionStatus + { + SeenOnce, + SeenMultipleTimes, + Extracted + } + } + + private sealed class LiftedExpressionProcessor : ExpressionVisitor + { + private ParameterExpression _originalParameter = null!; + private ParameterExpression _replacingParameter = null!; + + public Expression Process(Expression expression, ParameterExpression originalParameter, ParameterExpression replacingParameter) + { + _originalParameter = originalParameter; + _replacingParameter = replacingParameter; + + return Visit(expression); + } + + protected override Expression VisitMember(MemberExpression node) + { + // The expression to be lifted may contain a captured variable; for limited literal scenarios, inline that variable into the + // expression so we can render it out to C#. + + // TODO: For the general case, this needs to be a full blown "evaluatable" identifier (like ParameterExtractingEV), which can + // identify any fragments of the tree which don't depend on the lambda parameter, and evaluate them. + // But for now we're doing a reduced version. + + var visited = base.VisitMember(node); + + if (visited is MemberExpression + { + Expression: ConstantExpression { Value: { } constant }, + Member: var member + }) + { + return member switch + { + FieldInfo fi => Expression.Constant(fi.GetValue(constant), node.Type), + PropertyInfo pi => Expression.Constant(pi.GetValue(constant), node.Type), + _ => visited + }; + } + + return visited; + } + + protected override Expression VisitParameter(ParameterExpression node) + => ReferenceEquals(node, _originalParameter) + ? _replacingParameter + : base.VisitParameter(node); + } + + [return: NotNullIfNotNull("node")] + public override Expression? Visit(Expression? node) + { + return base.Visit(node); + } + + + protected override Expression VisitBinary(BinaryExpression binaryExpression) + { + var left = Visit(binaryExpression.Left); + var right = Visit(binaryExpression.Right); + var conversion = (LambdaExpression?)Visit(binaryExpression.Conversion); + + if (binaryExpression.NodeType is ExpressionType.Assign + && left is MemberExpression { Member: FieldInfo { IsInitOnly: true } } initFieldMember) + { + return (BinaryExpression)Activator.CreateInstance( + GetAssignBinaryExpressionType(), + BindingFlags.NonPublic | BindingFlags.Instance, + null, + [initFieldMember, right], + null)!; + } + + return binaryExpression.Update(left, conversion, right); + + [UnconditionalSuppressMessage( + "ReflectionAnalysis", "IL2026", + Justification = "DynamicDependency ensures AssignBinaryExpression isn't trimmed")] + static Type GetAssignBinaryExpressionType() + => typeof(Expression).Assembly.GetType("System.Linq.Expressions.AssignBinaryExpression", throwOnError: true)!; + } + +#if DEBUG + protected override Expression VisitConstant(ConstantExpression node) + { + if (!LiftableConstantExpressionHelpers.IsLiteral(node.Value)) + { + throw new InvalidOperationException($"Materializer expression contains a non-literal constant of type '{node.Value!.GetType().Name}'. "); + } + + return LiftableConstantExpressionHelpers.IsLiteral(node.Value) + ? node + : throw new InvalidOperationException( + $"Materializer expression contains a non-literal constant of type '{node.Value!.GetType().Name}'. " + + $"Use a {nameof(LiftableConstantExpression)} to reference any non-literal constants."); + } +#endif +} diff --git a/src/EFCore/Query/MaterializerLiftableConstantContext.cs b/src/EFCore/Query/MaterializerLiftableConstantContext.cs new file mode 100644 index 00000000000..a8c062cd902 --- /dev/null +++ b/src/EFCore/Query/MaterializerLiftableConstantContext.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.EntityFrameworkCore.Query; + +/// +/// This is an experimental API used by the Entity Framework Core feature and it is 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. +/// +[Experimental(EFDiagnostics.PrecompiledQueryExperimental)] +public record MaterializerLiftableConstantContext(ShapedQueryCompilingExpressionVisitorDependencies Dependencies); diff --git a/src/EFCore/Query/QueryCompilationContext.cs b/src/EFCore/Query/QueryCompilationContext.cs index def674ba898..d9f4f1fcc56 100644 --- a/src/EFCore/Query/QueryCompilationContext.cs +++ b/src/EFCore/Query/QueryCompilationContext.cs @@ -56,7 +56,8 @@ public class QueryCompilationContext private readonly IQueryTranslationPostprocessorFactory _queryTranslationPostprocessorFactory; private readonly IShapedQueryCompilingExpressionVisitorFactory _shapedQueryCompilingExpressionVisitorFactory; - private readonly ExpressionPrinter _expressionPrinter; + private readonly ExpressionPrinter _expressionPrinter = new(); + private readonly RuntimeParameterConstantLifter _runtimeParameterConstantLifter; private Dictionary? _runtimeParameters; @@ -82,8 +83,7 @@ public QueryCompilationContext( _queryableMethodTranslatingExpressionVisitorFactory = dependencies.QueryableMethodTranslatingExpressionVisitorFactory; _queryTranslationPostprocessorFactory = dependencies.QueryTranslationPostprocessorFactory; _shapedQueryCompilingExpressionVisitorFactory = dependencies.ShapedQueryCompilingExpressionVisitorFactory; - - _expressionPrinter = new ExpressionPrinter(); + _runtimeParameterConstantLifter = new(dependencies.LiftableConstantFactory); } /// @@ -148,6 +148,11 @@ public QueryCompilationContext( public virtual void AddTag(string tag) => Tags.Add(tag); + /// + /// A value indicating whether the provider supports precompiled query. Default value is . Providers that do support this feature should opt-in by setting this value to . + /// + public virtual bool SupportsPrecompiledQuery => false; + /// /// Creates the query executor func which gives results for this query. /// @@ -155,6 +160,34 @@ public virtual void AddTag(string tag) /// The query to generate executor for. /// Returns which can be invoked to get results of this query. public virtual Func CreateQueryExecutor(Expression query) + { + var queryExecutorExpression = CreateQueryExecutorExpression(query); + + // The materializer expression tree has liftable constant nodes, pointing to various constants that should be the same instances + // across invocations of the query. + // In normal mode, these nodes should simply be evaluated, and a ConstantExpression to those instances embedded directly in the + // tree (for precompiled queries we generate C# code for resolving those instances instead). + var queryExecutorAfterLiftingExpression = SupportsPrecompiledQuery + ? (Expression>)Dependencies.LiftableConstantProcessor.InlineConstants(queryExecutorExpression) + : queryExecutorExpression; + + try + { + return queryExecutorAfterLiftingExpression.Compile(); + } + finally + { + Logger.QueryExecutionPlanned(Dependencies.Context, _expressionPrinter, queryExecutorExpression); + } + } + + /// + /// Creates the query executor func which gives results for this query. + /// + /// The result type of this query. + /// The query to generate executor for. + /// Returns which can be invoked to get results of this query. + public virtual Expression> CreateQueryExecutorExpression(Expression query) { var queryAndEventData = Logger.QueryCompilationStarting(Dependencies.Context, _expressionPrinter, query); query = queryAndEventData.Query; @@ -176,14 +209,7 @@ public virtual Func CreateQueryExecutor(Expressi query, QueryContextParameter); - try - { - return queryExecutorExpression.Compile(); - } - finally - { - Logger.QueryExecutionPlanned(Dependencies.Context, _expressionPrinter, queryExecutorExpression); - } + return queryExecutorExpression; } /// @@ -193,6 +219,14 @@ public virtual Func CreateQueryExecutor(Expressi /// public virtual ParameterExpression RegisterRuntimeParameter(string name, LambdaExpression valueExtractor) { + var valueExtractorBody = valueExtractor.Body; + if (SupportsPrecompiledQuery) + { + valueExtractorBody = _runtimeParameterConstantLifter.Visit(valueExtractorBody); + } + + valueExtractor = Expression.Lambda(valueExtractorBody, valueExtractor.Parameters); + if (valueExtractor.Parameters.Count != 1 || valueExtractor.Parameters[0] != QueryContextParameter) { @@ -230,4 +264,50 @@ public override Type Type public override ExpressionType NodeType => ExpressionType.Extension; } + + private sealed class RuntimeParameterConstantLifter(ILiftableConstantFactory liftableConstantFactory) : ExpressionVisitor + { + private readonly static MethodInfo ComplexPropertyListElementAddMethod = typeof(List).GetMethod(nameof(List.Add))!; + + protected override Expression VisitConstant(ConstantExpression constantExpression) + { + switch (constantExpression.Value) + { + case IProperty property: + { + return liftableConstantFactory.CreateLiftableConstant( + constantExpression, + LiftableConstantExpressionHelpers.BuildMemberAccessLambdaForProperty(property), + property.Name + "Property", + typeof(IProperty)); + } + + case List complexPropertyChain: + { + var elementInitExpressions = new ElementInit[complexPropertyChain.Count]; + var prm = Expression.Parameter(typeof(MaterializerLiftableConstantContext)); + + for (var i = 0; i < complexPropertyChain.Count; i++) + { + var complexType = complexPropertyChain[i].ComplexType; + var complexTypeExpression = LiftableConstantExpressionHelpers.BuildMemberAccessForEntityOrComplexType(complexType, prm); + elementInitExpressions[i] = Expression.ElementInit( + ComplexPropertyListElementAddMethod, + Expression.Property(complexTypeExpression, nameof(IComplexType.ComplexProperty))); + } + + return liftableConstantFactory.CreateLiftableConstant( + constantExpression, + Expression.Lambda>( + Expression.ListInit(Expression.New(typeof(List)), elementInitExpressions), + prm), + "ComplexPropertyChain", + constantExpression.Type); + } + + default: + return base.VisitConstant(constantExpression); + } + } + } } diff --git a/src/EFCore/Query/QueryCompilationContextDependencies.cs b/src/EFCore/Query/QueryCompilationContextDependencies.cs index 85ad3c009b9..3df7db3a297 100644 --- a/src/EFCore/Query/QueryCompilationContextDependencies.cs +++ b/src/EFCore/Query/QueryCompilationContextDependencies.cs @@ -53,6 +53,8 @@ public QueryCompilationContextDependencies( IQueryableMethodTranslatingExpressionVisitorFactory queryableMethodTranslatingExpressionVisitorFactory, IQueryTranslationPostprocessorFactory queryTranslationPostprocessorFactory, IShapedQueryCompilingExpressionVisitorFactory shapedQueryCompilingExpressionVisitorFactory, + ILiftableConstantFactory liftableConstantFactory, + ILiftableConstantProcessor liftableConstantProcessor, IExecutionStrategy executionStrategy, ICurrentDbContext currentContext, IDbContextOptions contextOptions, @@ -65,6 +67,8 @@ public QueryCompilationContextDependencies( QueryableMethodTranslatingExpressionVisitorFactory = queryableMethodTranslatingExpressionVisitorFactory; QueryTranslationPostprocessorFactory = queryTranslationPostprocessorFactory; ShapedQueryCompilingExpressionVisitorFactory = shapedQueryCompilingExpressionVisitorFactory; + LiftableConstantFactory = liftableConstantFactory; + LiftableConstantProcessor = liftableConstantProcessor; IsRetryingExecutionStrategy = executionStrategy.RetriesOnFailure; ContextOptions = contextOptions; Logger = logger; @@ -114,6 +118,16 @@ public QueryTrackingBehavior QueryTrackingBehavior /// public IShapedQueryCompilingExpressionVisitorFactory ShapedQueryCompilingExpressionVisitorFactory { get; init; } + /// + /// TODO + /// + public ILiftableConstantFactory LiftableConstantFactory { get; init; } + + /// + /// The liftable constant processor. + /// + public ILiftableConstantProcessor LiftableConstantProcessor { get; init; } + /// /// Whether the configured execution strategy can retry. /// diff --git a/src/EFCore/Query/ReplacingExpressionVisitor.cs b/src/EFCore/Query/ReplacingExpressionVisitor.cs index 666a63bb096..2d5989956d9 100644 --- a/src/EFCore/Query/ReplacingExpressionVisitor.cs +++ b/src/EFCore/Query/ReplacingExpressionVisitor.cs @@ -33,6 +33,16 @@ public class ReplacingExpressionVisitor : ExpressionVisitor public static Expression Replace(Expression original, Expression replacement, Expression tree) => new ReplacingExpressionVisitor(new[] { original }, new[] { replacement }).Visit(tree); + /// + /// Replaces one expression with another in given expression tree. + /// + /// A list of original expressions to replace. + /// A list of expressions to be used as replacements. + /// The expression tree in which replacement is going to be performed. + /// An expression tree with replacements made. + public static Expression Replace(Expression[] originals, Expression[] replacements, Expression tree) + => new ReplacingExpressionVisitor(originals, replacements).Visit(tree); + /// /// Creates a new instance of the class. /// @@ -48,7 +58,7 @@ public ReplacingExpressionVisitor(IReadOnlyList originals, IReadOnly [return: NotNullIfNotNull("expression")] public override Expression? Visit(Expression? expression) { - if (expression is null or ShapedQueryExpression or StructuralTypeShaperExpression or GroupByShaperExpression) + if (expression is null or ShapedQueryExpression or StructuralTypeShaperExpression or GroupByShaperExpression or LiftableConstantExpression) { return expression; } diff --git a/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs b/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs index 7e346199f23..6ab2ce77d0f 100644 --- a/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs @@ -1,8 +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.Diagnostics.CodeAnalysis; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; +using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using static System.Linq.Expressions.Expression; @@ -39,6 +41,7 @@ private static readonly PropertyInfo CancellationTokenMemberInfo private readonly Expression _cancellationTokenParameter; private readonly EntityMaterializerInjectingExpressionVisitor _entityMaterializerInjectingExpressionVisitor; private readonly ConstantVerifyingExpressionVisitor _constantVerifyingExpressionVisitor; + private readonly MaterializationConditionConstantLifter _materializationConditionConstantLifter; /// /// Creates a new instance of the class. @@ -55,9 +58,12 @@ protected ShapedQueryCompilingExpressionVisitor( _entityMaterializerInjectingExpressionVisitor = new EntityMaterializerInjectingExpressionVisitor( dependencies.EntityMaterializerSource, - queryCompilationContext.QueryTrackingBehavior); + dependencies.LiftableConstantFactory, + queryCompilationContext.QueryTrackingBehavior, + queryCompilationContext.SupportsPrecompiledQuery); - _constantVerifyingExpressionVisitor = new ConstantVerifyingExpressionVisitor(dependencies.TypeMappingSource); + _constantVerifyingExpressionVisitor = new(dependencies.TypeMappingSource); + _materializationConditionConstantLifter = new(dependencies.LiftableConstantFactory); if (queryCompilationContext.IsAsync) { @@ -128,7 +134,14 @@ private static readonly MethodInfo SingleOrDefaultAsyncMethodInfo .GetDeclaredMethods(nameof(SingleOrDefaultAsync)) .Single(mi => mi.GetParameters().Length == 2); - private static async Task SingleAsync( + /// + /// 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 static async Task SingleAsync( IAsyncEnumerable asyncEnumerable, CancellationToken cancellationToken = default) { @@ -150,7 +163,14 @@ private static async Task SingleAsync( return result; } - private static async Task SingleOrDefaultAsync( + /// + /// 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 static async Task SingleOrDefaultAsync( IAsyncEnumerable asyncEnumerable, CancellationToken cancellationToken = default) { @@ -189,7 +209,108 @@ protected virtual Expression InjectEntityMaterializers(Expression expression) { VerifyNoClientConstant(expression); - return _entityMaterializerInjectingExpressionVisitor.Inject(expression); + var materializerExpression = _entityMaterializerInjectingExpressionVisitor.Inject(expression); + if (QueryCompilationContext.SupportsPrecompiledQuery) + { + materializerExpression = _materializationConditionConstantLifter.Visit(materializerExpression); + } + + return materializerExpression; + } + + private sealed class MaterializationConditionConstantLifter(ILiftableConstantFactory liftableConstantFactory) : ExpressionVisitor + { + protected override Expression VisitConstant(ConstantExpression constantExpression) + => constantExpression switch + { + { Value: IEntityType entityTypeValue } => liftableConstantFactory.CreateLiftableConstant( + constantExpression, + LiftableConstantExpressionHelpers.BuildMemberAccessLambdaForEntityOrComplexType(entityTypeValue), + entityTypeValue.Name + "EntityType", + constantExpression.Type), + { Value: IComplexType complexTypeValue } => liftableConstantFactory.CreateLiftableConstant( + constantExpression, + LiftableConstantExpressionHelpers.BuildMemberAccessLambdaForEntityOrComplexType(complexTypeValue), + complexTypeValue.Name + "ComplexType", + constantExpression.Type), + { Value: IProperty propertyValue } => liftableConstantFactory.CreateLiftableConstant( + constantExpression, + LiftableConstantExpressionHelpers.BuildMemberAccessLambdaForProperty(propertyValue), + propertyValue.Name + "Property", + constantExpression.Type), + { Value: IServiceProperty servicePropertyValue } => liftableConstantFactory.CreateLiftableConstant( + constantExpression, + LiftableConstantExpressionHelpers.BuildMemberAccessLambdaForProperty(servicePropertyValue), + servicePropertyValue.Name + "ServiceProperty", + constantExpression.Type), + { Value: IMaterializationInterceptor materializationInterceptorValue } => liftableConstantFactory.CreateLiftableConstant( + constantExpression, + c => (IMaterializationInterceptor?)new MaterializationInterceptorAggregator().AggregateInterceptors( + c.Dependencies.SingletonInterceptors.OfType().ToList())!, + "materializationInterceptor", + constantExpression.Type), + { Value: IInstantiationBindingInterceptor instantiationBindingInterceptorValue } => liftableConstantFactory.CreateLiftableConstant( + constantExpression, + c => c.Dependencies.SingletonInterceptors.OfType().Where(x => x == instantiationBindingInterceptorValue).Single(), + "instantiationBindingInterceptor", + constantExpression.Type), + + _ => Fallback(constantExpression) + }; + + + private Expression Fallback(ConstantExpression constantExpression) + { + // if constant is of interface type we blind guess it might be a service and try to resolve it + // we do it this way because some services are defined in different assemblies (e.g. IProxyFactory) + // so we can't match the type exactly + if (constantExpression.Value != null && constantExpression.Type.IsInterface) + { + return liftableConstantFactory.CreateLiftableConstant( + constantExpression, + c => c.Dependencies.ContextServices.InternalServiceProvider.GetService(constantExpression.Type)!, + "instantiationBindingInterceptor", + constantExpression.Type); + } + + return base.VisitConstant(constantExpression); + } + + protected override Expression VisitBinary(BinaryExpression binaryExpression) + { + var left = Visit(binaryExpression.Left); + var right = Visit(binaryExpression.Right); + var conversion = (LambdaExpression?)Visit(binaryExpression.Conversion); + + if (binaryExpression.NodeType is ExpressionType.Assign + && left is MemberExpression { Member: FieldInfo { IsInitOnly: true } } initFieldMember) + { + return (BinaryExpression)Activator.CreateInstance( + GetAssignBinaryExpressionType(), + BindingFlags.NonPublic | BindingFlags.Instance, + null, + [initFieldMember, right], + null)!; + } + + return binaryExpression.Update(left, conversion, right); + + [UnconditionalSuppressMessage( + "ReflectionAnalysis", "IL2026", + Justification = "DynamicDependency ensures AssignBinaryExpression isn't trimmed")] + static Type GetAssignBinaryExpressionType() + => typeof(Expression).Assembly.GetType("System.Linq.Expressions.AssignBinaryExpression", throwOnError: true)!; + } + + protected override Expression VisitExtension(Expression node) + { + if (node is LiftableConstantExpression) + { + return node; + } + + return base.VisitExtension(node); + } } /// @@ -199,6 +320,35 @@ protected virtual Expression InjectEntityMaterializers(Expression expression) protected virtual void VerifyNoClientConstant(Expression expression) => _constantVerifyingExpressionVisitor.Visit(expression); + /// + /// 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. + /// + [UsedImplicitly] + [EntityFrameworkInternal] + public static Exception CreateNullKeyValueInNoTrackingQuery( + IEntityType entityType, + IReadOnlyList properties, + object?[] keyValues) + { + var index = -1; + for (var i = 0; i < keyValues.Length; i++) + { + if (keyValues[i] == null) + { + index = i; + break; + } + } + + var property = properties[index]; + + throw new InvalidOperationException( + CoreStrings.InvalidKeyValue(entityType.DisplayName(), property.Name)); + } + private sealed class ConstantVerifyingExpressionVisitor : ExpressionVisitor { private readonly ITypeMappingSource _typeMappingSource; @@ -289,23 +439,33 @@ private static readonly MethodInfo StartTrackingMethodInfo nameof(QueryContext.StartTracking), [typeof(IEntityType), typeof(object), typeof(ISnapshot).MakeByRefType()])!; private static readonly MethodInfo CreateNullKeyValueInNoTrackingQueryMethod - = typeof(EntityMaterializerInjectingExpressionVisitor) + = typeof(ShapedQueryCompilingExpressionVisitor) .GetTypeInfo().GetDeclaredMethod(nameof(CreateNullKeyValueInNoTrackingQuery))!; + private static readonly MethodInfo EntityTypeFindPrimaryKeyMethod = + typeof(IEntityType).GetMethod(nameof(IEntityType.FindPrimaryKey), [])!; + private readonly IEntityMaterializerSource _entityMaterializerSource; + private readonly ILiftableConstantFactory _liftableConstantFactory; private readonly QueryTrackingBehavior _queryTrackingBehavior; private readonly bool _queryStateManager; private readonly ISet _visitedEntityTypes = new HashSet(); + private readonly MaterializationConditionConstantLifter _materializationConditionConstantLifter; + private readonly bool _supportsPrecompiledQuery; private int _currentEntityIndex; public EntityMaterializerInjectingExpressionVisitor( IEntityMaterializerSource entityMaterializerSource, - QueryTrackingBehavior queryTrackingBehavior) + ILiftableConstantFactory liftableConstantFactory, + QueryTrackingBehavior queryTrackingBehavior, + bool supportsPrecompiledQuery) { _entityMaterializerSource = entityMaterializerSource; + _liftableConstantFactory = liftableConstantFactory; _queryTrackingBehavior = queryTrackingBehavior; - _queryStateManager = - queryTrackingBehavior is QueryTrackingBehavior.TrackAll or QueryTrackingBehavior.NoTrackingWithIdentityResolution; + _queryStateManager = queryTrackingBehavior is QueryTrackingBehavior.TrackAll or QueryTrackingBehavior.NoTrackingWithIdentityResolution; + _materializationConditionConstantLifter = new(liftableConstantFactory); + _supportsPrecompiledQuery = supportsPrecompiledQuery; } public Expression Inject(Expression expression) @@ -378,13 +538,24 @@ private Expression ProcessEntityShaper(StructuralTypeShaperExpression shaper) variables.Add(entryVariable); variables.Add(hasNullKeyVariable); + var resolverPrm = Parameter(typeof(MaterializerLiftableConstantContext), "c"); expressions.Add( Assign( entryVariable, Call( QueryCompilationContext.QueryContextParameter, TryGetEntryMethodInfo, - Constant(primaryKey), + _supportsPrecompiledQuery + ? _liftableConstantFactory.CreateLiftableConstant( + Constant(primaryKey), + Lambda>( + Call( + LiftableConstantExpressionHelpers.BuildMemberAccessForEntityOrComplexType(typeBase, resolverPrm), + EntityTypeFindPrimaryKeyMethod), + resolverPrm), + typeBase.Name + "Key", + typeof(IKey)) + : Constant(primaryKey), NewArrayInit( typeof(object), primaryKey.Properties @@ -432,6 +603,8 @@ private Expression ProcessEntityShaper(StructuralTypeShaperExpression shaper) else { var keyValuesVariable = Variable(typeof(object[]), "keyValues" + _currentEntityIndex); + var resolverPrm = Parameter(typeof(MaterializerLiftableConstantContext), "c"); + expressions.Add( IfThenElse( primaryKey.Properties.Select( @@ -454,8 +627,26 @@ private Expression ProcessEntityShaper(StructuralTypeShaperExpression shaper) typeof(object), p.GetIndex(), p)))), Call( CreateNullKeyValueInNoTrackingQueryMethod, - Constant(typeBase), - Constant(primaryKey.Properties), + _supportsPrecompiledQuery + ? _liftableConstantFactory.CreateLiftableConstant( + Constant(typeBase), + LiftableConstantExpressionHelpers.BuildMemberAccessLambdaForEntityOrComplexType(typeBase), + typeBase.Name + "EntityType", + typeof(IEntityType)) + : Constant(typeBase), + _supportsPrecompiledQuery + ? _liftableConstantFactory.CreateLiftableConstant( + Constant(primaryKey.Properties), + Lambda>( + Property( + Call( + LiftableConstantExpressionHelpers.BuildMemberAccessForEntityOrComplexType(typeBase, resolverPrm), + EntityTypeFindPrimaryKeyMethod), + nameof(IKey.Properties)), + resolverPrm), + typeBase.Name + "PrimaryKeyProperties", + typeof(IReadOnlyList)) + : Constant(primaryKey.Properties), keyValuesVariable)))); } } @@ -491,18 +682,24 @@ private Expression MaterializeEntity( expressions.Add( Assign( shadowValuesVariable, - Constant(Snapshot.Empty))); + _supportsPrecompiledQuery + ? _liftableConstantFactory.CreateLiftableConstant( + Constant(Snapshot.Empty), + _ => Snapshot.Empty, + "emptySnapshot", + typeof(Snapshot)) + : Constant(Snapshot.Empty))); var returnType = typeBase.ClrType; var valueBufferExpression = Call(materializationContextVariable, MaterializationContext.GetValueBufferMethod); + + var materializationConditionBody = ReplacingExpressionVisitor.Replace( + shaper.MaterializationCondition.Parameters[0], + valueBufferExpression, + shaper.MaterializationCondition.Body); + var expressionContext = (returnType, materializationContextVariable, concreteEntityTypeVariable, shadowValuesVariable); - expressions.Add( - Assign( - concreteEntityTypeVariable, - ReplacingExpressionVisitor.Replace( - shaper.MaterializationCondition.Parameters[0], - valueBufferExpression, - shaper.MaterializationCondition.Body))); + expressions.Add(Assign(concreteEntityTypeVariable, materializationConditionBody)); var (primaryKey, concreteEntityTypes) = typeBase is IEntityType entityType ? (entityType.FindPrimaryKey(), entityType.GetConcreteDerivedTypesInclusive().Cast().ToArray()) @@ -511,9 +708,16 @@ private Expression MaterializeEntity( var switchCases = new SwitchCase[concreteEntityTypes.Length]; for (var i = 0; i < concreteEntityTypes.Length; i++) { + var concreteEntityType = concreteEntityTypes[i]; switchCases[i] = SwitchCase( CreateFullMaterializeExpression(concreteEntityTypes[i], expressionContext), - Constant(concreteEntityTypes[i], typeBase is IEntityType ? typeof(IEntityType) : typeof(IComplexType))); + _supportsPrecompiledQuery + ? _liftableConstantFactory.CreateLiftableConstant( + Constant(concreteEntityTypes[i], typeBase is IEntityType ? typeof(IEntityType) : typeof(IComplexType)), + LiftableConstantExpressionHelpers.BuildMemberAccessLambdaForEntityOrComplexType(concreteEntityType), + concreteEntityType.Name + (typeBase is IEntityType ? "EntityType" : "ComplexType"), + typeBase is IEntityType ? typeof(IEntityType) : typeof(IComplexType)) + : Constant(concreteEntityTypes[i], typeBase is IEntityType ? typeof(IEntityType) : typeof(IComplexType))); } var materializationExpression = Switch( @@ -605,27 +809,5 @@ private BlockExpression CreateFullMaterializeExpression( return Block(blockExpressions); } - - [UsedImplicitly] - private static Exception CreateNullKeyValueInNoTrackingQuery( - IEntityType entityType, - IReadOnlyList properties, - object?[] keyValues) - { - var index = -1; - for (var i = 0; i < keyValues.Length; i++) - { - if (keyValues[i] == null) - { - index = i; - break; - } - } - - var property = properties[index]; - - throw new InvalidOperationException( - CoreStrings.InvalidKeyValue(entityType.DisplayName(), property.Name)); - } } } diff --git a/src/EFCore/Query/ShapedQueryCompilingExpressionVisitorDependencies.cs b/src/EFCore/Query/ShapedQueryCompilingExpressionVisitorDependencies.cs index cc025f1e48d..a8eb8153921 100644 --- a/src/EFCore/Query/ShapedQueryCompilingExpressionVisitorDependencies.cs +++ b/src/EFCore/Query/ShapedQueryCompilingExpressionVisitorDependencies.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.Internal; using Microsoft.Extensions.Caching.Memory; namespace Microsoft.EntityFrameworkCore.Query; @@ -51,12 +52,22 @@ public ShapedQueryCompilingExpressionVisitorDependencies( IEntityMaterializerSource entityMaterializerSource, ITypeMappingSource typeMappingSource, IMemoryCache memoryCache, - ICoreSingletonOptions coreSingletonOptions) + ICoreSingletonOptions coreSingletonOptions, + IModel model, + ILiftableConstantFactory liftableConstantFactory, + IDiagnosticsLogger queryLogger, + IEnumerable singletonInterceptors, + IDbContextServices contextServices) { EntityMaterializerSource = entityMaterializerSource; TypeMappingSource = typeMappingSource; MemoryCache = memoryCache; CoreSingletonOptions = coreSingletonOptions; + Model = model; + LiftableConstantFactory = liftableConstantFactory; + QueryLogger = queryLogger; + SingletonInterceptors = singletonInterceptors; + ContextServices = contextServices; } /// @@ -78,4 +89,29 @@ public ShapedQueryCompilingExpressionVisitorDependencies( /// Core singleton options. /// public ICoreSingletonOptions CoreSingletonOptions { get; init; } + + /// + /// The model. + /// + public IModel Model { get; init; } + + /// + /// The liftable constant factory. + /// + public ILiftableConstantFactory LiftableConstantFactory { get; init; } + + /// + /// The query logger. + /// + public IDiagnosticsLogger QueryLogger { get; init; } + + /// + /// Registered singleton interceptors. + /// + public IEnumerable SingletonInterceptors { get; init; } + + /// + /// TODO + /// + public IDbContextServices ContextServices { get; init; } } diff --git a/src/EFCore/Query/StructuralTypeShaperExpression.cs b/src/EFCore/Query/StructuralTypeShaperExpression.cs index 21800633f87..c063baf356c 100644 --- a/src/EFCore/Query/StructuralTypeShaperExpression.cs +++ b/src/EFCore/Query/StructuralTypeShaperExpression.cs @@ -23,8 +23,18 @@ public class StructuralTypeShaperExpression : Expression, IPrintableExpression private static readonly MethodInfo CreateUnableToDiscriminateExceptionMethod = typeof(StructuralTypeShaperExpression).GetTypeInfo().GetDeclaredMethod(nameof(CreateUnableToDiscriminateException))!; + private static readonly MethodInfo GetDiscriminatorValueMethod + = typeof(IReadOnlyEntityType).GetTypeInfo().GetDeclaredMethod(nameof(IReadOnlyEntityType.GetDiscriminatorValue))!; + + /// + /// 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. + /// [UsedImplicitly] - private static Exception CreateUnableToDiscriminateException(ITypeBase type, object discriminator) + [EntityFrameworkInternal] + public static Exception CreateUnableToDiscriminateException(ITypeBase type, object discriminator) => new InvalidOperationException(CoreStrings.UnableToDiscriminate(type.DisplayName(), discriminator.ToString())); /// @@ -80,7 +90,7 @@ protected StructuralTypeShaperExpression( /// The entity type for which materialization was requested. /// The expression containing value of discriminator. /// - /// An expression of representing materilization condition for the entity type. + /// An expression of representing materialization condition for the entity type. /// protected static Expression CreateUnableToDiscriminateExceptionExpression(ITypeBase type, Expression discriminatorValue) => Block( @@ -131,8 +141,12 @@ protected virtual LambdaExpression GenerateMaterializationCondition(ITypeBase ty var switchCases = new SwitchCase[concreteEntityTypes.Length]; for (var i = 0; i < concreteEntityTypes.Length; i++) { - var discriminatorValue = Constant(concreteEntityTypes[i].GetDiscriminatorValue(), discriminatorProperty.ClrType); - switchCases[i] = SwitchCase(Constant(concreteEntityTypes[i], typeof(IEntityType)), discriminatorValue); + var discriminatorValueObject = concreteEntityTypes[i].GetDiscriminatorValue(); + var discriminatorValueExpression = LiftableConstantExpressionHelpers.IsLiteral(discriminatorValueObject) + ? (Expression)Constant(discriminatorValueObject, discriminatorProperty.ClrType) + : Convert(Call(Constant(concreteEntityTypes[i]), GetDiscriminatorValueMethod), discriminatorProperty.ClrType); + + switchCases[i] = SwitchCase(Constant(concreteEntityTypes[i], typeof(IEntityType)), discriminatorValueExpression); } expressions.Add(Switch(discriminatorValueVariable, exception, switchCases)); @@ -142,11 +156,18 @@ protected virtual LambdaExpression GenerateMaterializationCondition(ITypeBase ty var conditions = exception; for (var i = concreteEntityTypes.Length - 1; i >= 0; i--) { + var discriminatorValueObject = concreteEntityTypes[i].GetDiscriminatorValue(); conditions = Condition( discriminatorComparer.ExtractEqualsBody( discriminatorValueVariable, - Constant( - concreteEntityTypes[i].GetDiscriminatorValue(), + LiftableConstantExpressionHelpers.IsLiteral(discriminatorValueObject) + ? Constant( + discriminatorValueObject, + discriminatorProperty.ClrType) + : Convert( + Call( + Constant(concreteEntityTypes[i], typeof(IEntityType)), + GetDiscriminatorValueMethod), discriminatorProperty.ClrType)), Constant(concreteEntityTypes[i], typeof(IEntityType)), conditions); diff --git a/src/EFCore/Storage/Json/JsonBoolReaderWriter.cs b/src/EFCore/Storage/Json/JsonBoolReaderWriter.cs index 5db25121a4b..a1697481bd3 100644 --- a/src/EFCore/Storage/Json/JsonBoolReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonBoolReaderWriter.cs @@ -10,15 +10,15 @@ namespace Microsoft.EntityFrameworkCore.Storage.Json; /// public sealed class JsonBoolReaderWriter : JsonValueReaderWriter { + private JsonBoolReaderWriter() + { + } + /// /// The singleton instance of this stateless reader/writer. /// public static JsonBoolReaderWriter Instance { get; } = new(); - private JsonBoolReaderWriter() - { - } - /// public override bool FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) => manager.CurrentReader.GetBoolean(); @@ -26,4 +26,9 @@ public override bool FromJsonTyped(ref Utf8JsonReaderManager manager, object? ex /// public override void ToJsonTyped(Utf8JsonWriter writer, bool value) => writer.WriteBooleanValue(value); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonByteArrayReaderWriter.cs b/src/EFCore/Storage/Json/JsonByteArrayReaderWriter.cs index 5bbe3070fa1..cd6df45acc3 100644 --- a/src/EFCore/Storage/Json/JsonByteArrayReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonByteArrayReaderWriter.cs @@ -26,4 +26,9 @@ public override byte[] FromJsonTyped(ref Utf8JsonReaderManager manager, object? /// public override void ToJsonTyped(Utf8JsonWriter writer, byte[] value) => writer.WriteBase64StringValue(value); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonByteReaderWriter.cs b/src/EFCore/Storage/Json/JsonByteReaderWriter.cs index d9b22fa4e73..c0e5815c393 100644 --- a/src/EFCore/Storage/Json/JsonByteReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonByteReaderWriter.cs @@ -26,4 +26,9 @@ public override byte FromJsonTyped(ref Utf8JsonReaderManager manager, object? ex /// public override void ToJsonTyped(Utf8JsonWriter writer, byte value) => writer.WriteNumberValue(value); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonCastValueReaderWriter.cs b/src/EFCore/Storage/Json/JsonCastValueReaderWriter.cs index 98fddf1eacf..a201bc39f60 100644 --- a/src/EFCore/Storage/Json/JsonCastValueReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonCastValueReaderWriter.cs @@ -34,4 +34,10 @@ public override void ToJsonTyped(Utf8JsonWriter writer, TConverted value) JsonValueReaderWriter ICompositeJsonValueReaderWriter.InnerReaderWriter => _providerReaderWriter; + + private readonly ConstructorInfo _constructorInfo = typeof(JsonCastValueReaderWriter).GetConstructor([typeof(JsonValueReaderWriter)])!; + + /// + public override Expression ConstructorExpression => + Expression.New(_constructorInfo, ((ICompositeJsonValueReaderWriter)this).InnerReaderWriter.ConstructorExpression); } diff --git a/src/EFCore/Storage/Json/JsonCharReaderWriter.cs b/src/EFCore/Storage/Json/JsonCharReaderWriter.cs index 7eb278629cc..caff5a24fdc 100644 --- a/src/EFCore/Storage/Json/JsonCharReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonCharReaderWriter.cs @@ -26,4 +26,9 @@ public override char FromJsonTyped(ref Utf8JsonReaderManager manager, object? ex /// public override void ToJsonTyped(Utf8JsonWriter writer, char value) => writer.WriteStringValue(value.ToString()); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonCollectionReaderWriter.cs b/src/EFCore/Storage/Json/JsonCollectionReaderWriter.cs index f1fbc43d1e4..5089355dfec 100644 --- a/src/EFCore/Storage/Json/JsonCollectionReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonCollectionReaderWriter.cs @@ -107,4 +107,10 @@ public override void ToJsonTyped(Utf8JsonWriter writer, IEnumerable v JsonValueReaderWriter ICompositeJsonValueReaderWriter.InnerReaderWriter => _elementReaderWriter; + + private readonly ConstructorInfo _constructorInfo = typeof(JsonCollectionReaderWriter).GetConstructor([typeof(JsonValueReaderWriter)])!; + + /// + public override Expression ConstructorExpression => + Expression.New(_constructorInfo, ((ICompositeJsonValueReaderWriter)this).InnerReaderWriter.ConstructorExpression); } diff --git a/src/EFCore/Storage/Json/JsonConvertedValueReaderWriter.cs b/src/EFCore/Storage/Json/JsonConvertedValueReaderWriter.cs index c873191db76..1be11e24b83 100644 --- a/src/EFCore/Storage/Json/JsonConvertedValueReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonConvertedValueReaderWriter.cs @@ -45,4 +45,13 @@ JsonValueReaderWriter ICompositeJsonValueReaderWriter.InnerReaderWriter ValueConverter IJsonConvertedValueReaderWriter.Converter => _converter; + + private readonly ConstructorInfo _constructorInfo = typeof(JsonConvertedValueReaderWriter).GetConstructor([typeof(JsonValueReaderWriter), typeof(ValueConverter)])!; + + /// + public override Expression ConstructorExpression => + Expression.New( + _constructorInfo, + ((ICompositeJsonValueReaderWriter)this).InnerReaderWriter.ConstructorExpression, + ((IJsonConvertedValueReaderWriter)this).Converter.ConstructorExpression); } diff --git a/src/EFCore/Storage/Json/JsonDateOnlyReaderWriter.cs b/src/EFCore/Storage/Json/JsonDateOnlyReaderWriter.cs index 27ad1daa60b..33274e4bed7 100644 --- a/src/EFCore/Storage/Json/JsonDateOnlyReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonDateOnlyReaderWriter.cs @@ -27,4 +27,9 @@ public override DateOnly FromJsonTyped(ref Utf8JsonReaderManager manager, object /// public override void ToJsonTyped(Utf8JsonWriter writer, DateOnly value) => writer.WriteStringValue(value.ToString("o", CultureInfo.InvariantCulture)); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonDateTimeOffsetReaderWriter.cs b/src/EFCore/Storage/Json/JsonDateTimeOffsetReaderWriter.cs index 07fb536df59..dfaac734298 100644 --- a/src/EFCore/Storage/Json/JsonDateTimeOffsetReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonDateTimeOffsetReaderWriter.cs @@ -26,4 +26,9 @@ public override DateTimeOffset FromJsonTyped(ref Utf8JsonReaderManager manager, /// public override void ToJsonTyped(Utf8JsonWriter writer, DateTimeOffset value) => writer.WriteStringValue(value); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonDateTimeReaderWriter.cs b/src/EFCore/Storage/Json/JsonDateTimeReaderWriter.cs index bb9b26fd833..eab5e71fe13 100644 --- a/src/EFCore/Storage/Json/JsonDateTimeReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonDateTimeReaderWriter.cs @@ -26,4 +26,9 @@ public override DateTime FromJsonTyped(ref Utf8JsonReaderManager manager, object /// public override void ToJsonTyped(Utf8JsonWriter writer, DateTime value) => writer.WriteStringValue(value); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonDecimalReaderWriter.cs b/src/EFCore/Storage/Json/JsonDecimalReaderWriter.cs index ba55bd51cd5..8c014a58c2d 100644 --- a/src/EFCore/Storage/Json/JsonDecimalReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonDecimalReaderWriter.cs @@ -26,4 +26,9 @@ public override decimal FromJsonTyped(ref Utf8JsonReaderManager manager, object? /// public override void ToJsonTyped(Utf8JsonWriter writer, decimal value) => writer.WriteNumberValue(value); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonDoubleReaderWriter.cs b/src/EFCore/Storage/Json/JsonDoubleReaderWriter.cs index f2a5f16aa4b..8d83de052ce 100644 --- a/src/EFCore/Storage/Json/JsonDoubleReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonDoubleReaderWriter.cs @@ -26,4 +26,9 @@ public override double FromJsonTyped(ref Utf8JsonReaderManager manager, object? /// public override void ToJsonTyped(Utf8JsonWriter writer, double value) => writer.WriteNumberValue(value); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonFloatReaderWriter.cs b/src/EFCore/Storage/Json/JsonFloatReaderWriter.cs index e918acad473..129eea90935 100644 --- a/src/EFCore/Storage/Json/JsonFloatReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonFloatReaderWriter.cs @@ -26,4 +26,9 @@ public override float FromJsonTyped(ref Utf8JsonReaderManager manager, object? e /// public override void ToJsonTyped(Utf8JsonWriter writer, float value) => writer.WriteNumberValue(value); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonGuidReaderWriter.cs b/src/EFCore/Storage/Json/JsonGuidReaderWriter.cs index ff6074dafbe..90ce983cfb2 100644 --- a/src/EFCore/Storage/Json/JsonGuidReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonGuidReaderWriter.cs @@ -26,4 +26,9 @@ public override Guid FromJsonTyped(ref Utf8JsonReaderManager manager, object? ex /// public override void ToJsonTyped(Utf8JsonWriter writer, Guid value) => writer.WriteStringValue(value); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonInt16ReaderWriter.cs b/src/EFCore/Storage/Json/JsonInt16ReaderWriter.cs index f2ed6cba6b9..0ed2c39b47c 100644 --- a/src/EFCore/Storage/Json/JsonInt16ReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonInt16ReaderWriter.cs @@ -26,4 +26,9 @@ public override short FromJsonTyped(ref Utf8JsonReaderManager manager, object? e /// public override void ToJsonTyped(Utf8JsonWriter writer, short value) => writer.WriteNumberValue(value); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonInt32ReaderWriter.cs b/src/EFCore/Storage/Json/JsonInt32ReaderWriter.cs index 6eeadbaf117..b7702661f44 100644 --- a/src/EFCore/Storage/Json/JsonInt32ReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonInt32ReaderWriter.cs @@ -26,4 +26,9 @@ public override int FromJsonTyped(ref Utf8JsonReaderManager manager, object? exi /// public override void ToJsonTyped(Utf8JsonWriter writer, int value) => writer.WriteNumberValue(value); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonInt64ReaderWriter.cs b/src/EFCore/Storage/Json/JsonInt64ReaderWriter.cs index ec13f7268b9..8a276ae6eb9 100644 --- a/src/EFCore/Storage/Json/JsonInt64ReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonInt64ReaderWriter.cs @@ -26,4 +26,9 @@ public override long FromJsonTyped(ref Utf8JsonReaderManager manager, object? ex /// public override void ToJsonTyped(Utf8JsonWriter writer, long value) => writer.WriteNumberValue(value); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonNullReaderWriter.cs b/src/EFCore/Storage/Json/JsonNullReaderWriter.cs index a31d78e941f..414f0ad5b34 100644 --- a/src/EFCore/Storage/Json/JsonNullReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonNullReaderWriter.cs @@ -26,4 +26,9 @@ private JsonNullReaderWriter() /// public override void ToJsonTyped(Utf8JsonWriter writer, object? value) => writer.WriteNullValue(); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonNullableStructCollectionReaderWriter.cs b/src/EFCore/Storage/Json/JsonNullableStructCollectionReaderWriter.cs index 26a81959b4b..8bd52c6a348 100644 --- a/src/EFCore/Storage/Json/JsonNullableStructCollectionReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonNullableStructCollectionReaderWriter.cs @@ -113,4 +113,11 @@ public override void ToJsonTyped(Utf8JsonWriter writer, IEnumerable v JsonValueReaderWriter ICompositeJsonValueReaderWriter.InnerReaderWriter => _elementReaderWriter; + + private readonly ConstructorInfo _constructorInfo = + typeof(JsonNullableStructCollectionReaderWriter).GetConstructor([typeof(JsonValueReaderWriter)])!; + + /// + public override Expression ConstructorExpression => + Expression.New(_constructorInfo, ((ICompositeJsonValueReaderWriter)this).InnerReaderWriter.ConstructorExpression); } diff --git a/src/EFCore/Storage/Json/JsonSByteReaderWriter.cs b/src/EFCore/Storage/Json/JsonSByteReaderWriter.cs index ab41a52bff0..79e70419cb9 100644 --- a/src/EFCore/Storage/Json/JsonSByteReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonSByteReaderWriter.cs @@ -26,4 +26,9 @@ public override sbyte FromJsonTyped(ref Utf8JsonReaderManager manager, object? e /// public override void ToJsonTyped(Utf8JsonWriter writer, sbyte value) => writer.WriteNumberValue(value); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonSignedEnumReaderWriter.cs b/src/EFCore/Storage/Json/JsonSignedEnumReaderWriter.cs index 4870dc7ae2f..98827e8a940 100644 --- a/src/EFCore/Storage/Json/JsonSignedEnumReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonSignedEnumReaderWriter.cs @@ -27,4 +27,9 @@ public override TEnum FromJsonTyped(ref Utf8JsonReaderManager manager, object? e /// public override void ToJsonTyped(Utf8JsonWriter writer, TEnum value) => writer.WriteNumberValue((long)Convert.ChangeType(value, typeof(long))!); + + private readonly Expression>> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonStringReaderWriter.cs b/src/EFCore/Storage/Json/JsonStringReaderWriter.cs index b8e97006b37..40aa60446b8 100644 --- a/src/EFCore/Storage/Json/JsonStringReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonStringReaderWriter.cs @@ -26,4 +26,9 @@ public override string FromJsonTyped(ref Utf8JsonReaderManager manager, object? /// public override void ToJsonTyped(Utf8JsonWriter writer, string value) => writer.WriteStringValue(value); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonTimeOnlyReaderWriter.cs b/src/EFCore/Storage/Json/JsonTimeOnlyReaderWriter.cs index c209c777bc7..16995d7d8af 100644 --- a/src/EFCore/Storage/Json/JsonTimeOnlyReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonTimeOnlyReaderWriter.cs @@ -27,4 +27,9 @@ public override TimeOnly FromJsonTyped(ref Utf8JsonReaderManager manager, object /// public override void ToJsonTyped(Utf8JsonWriter writer, TimeOnly value) => writer.WriteStringValue(value.ToString("o", CultureInfo.InvariantCulture)); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonTimeSpanReaderWriter.cs b/src/EFCore/Storage/Json/JsonTimeSpanReaderWriter.cs index f91b7fa9f95..71305100651 100644 --- a/src/EFCore/Storage/Json/JsonTimeSpanReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonTimeSpanReaderWriter.cs @@ -27,4 +27,9 @@ public override TimeSpan FromJsonTyped(ref Utf8JsonReaderManager manager, object /// public override void ToJsonTyped(Utf8JsonWriter writer, TimeSpan value) => writer.WriteStringValue(value.ToString("g", CultureInfo.InvariantCulture)); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonUInt16ReaderWriter.cs b/src/EFCore/Storage/Json/JsonUInt16ReaderWriter.cs index 647f9be7fd6..29f48aa229b 100644 --- a/src/EFCore/Storage/Json/JsonUInt16ReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonUInt16ReaderWriter.cs @@ -26,4 +26,9 @@ public override ushort FromJsonTyped(ref Utf8JsonReaderManager manager, object? /// public override void ToJsonTyped(Utf8JsonWriter writer, ushort value) => writer.WriteNumberValue(value); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonUInt32ReaderWriter.cs b/src/EFCore/Storage/Json/JsonUInt32ReaderWriter.cs index 876d288ff26..d372a696c15 100644 --- a/src/EFCore/Storage/Json/JsonUInt32ReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonUInt32ReaderWriter.cs @@ -26,4 +26,9 @@ public override uint FromJsonTyped(ref Utf8JsonReaderManager manager, object? ex /// public override void ToJsonTyped(Utf8JsonWriter writer, uint value) => writer.WriteNumberValue(value); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonUInt64ReaderWriter.cs b/src/EFCore/Storage/Json/JsonUInt64ReaderWriter.cs index e2f8ab7df04..e505f032b3e 100644 --- a/src/EFCore/Storage/Json/JsonUInt64ReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonUInt64ReaderWriter.cs @@ -26,4 +26,9 @@ public override ulong FromJsonTyped(ref Utf8JsonReaderManager manager, object? e /// public override void ToJsonTyped(Utf8JsonWriter writer, ulong value) => writer.WriteNumberValue(value); + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonUnsignedEnumReaderWriter.cs b/src/EFCore/Storage/Json/JsonUnsignedEnumReaderWriter.cs index a2ee0b607bf..9e9775e1b10 100644 --- a/src/EFCore/Storage/Json/JsonUnsignedEnumReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonUnsignedEnumReaderWriter.cs @@ -27,4 +27,9 @@ public override TEnum FromJsonTyped(ref Utf8JsonReaderManager manager, object? e /// public override void ToJsonTyped(Utf8JsonWriter writer, TEnum value) => writer.WriteNumberValue((ulong)Convert.ChangeType(value, typeof(ulong))!); + + private readonly Expression>> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/Json/JsonValueReaderWriter.cs b/src/EFCore/Storage/Json/JsonValueReaderWriter.cs index 0536aa6ae43..7aba36b18cf 100644 --- a/src/EFCore/Storage/Json/JsonValueReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonValueReaderWriter.cs @@ -125,4 +125,9 @@ public string ToJsonString(object value) return null; } + + /// + /// The expression representing construction of this object. + /// + public abstract Expression ConstructorExpression { get; } } diff --git a/src/EFCore/Storage/Json/JsonWarningEnumReaderWriter.cs b/src/EFCore/Storage/Json/JsonWarningEnumReaderWriter.cs index 00eac1fffbf..f0d3857b50a 100644 --- a/src/EFCore/Storage/Json/JsonWarningEnumReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonWarningEnumReaderWriter.cs @@ -72,4 +72,9 @@ public override void ToJsonTyped(Utf8JsonWriter writer, TEnum value) writer.WriteNumberValue((ulong)Convert.ChangeType(value, typeof(ulong))); } } + + private readonly Expression>> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } diff --git a/src/EFCore/Storage/ValueConversion/BoolToStringConverter.cs b/src/EFCore/Storage/ValueConversion/BoolToStringConverter.cs index 9e2ff28a26e..de316fca16a 100644 --- a/src/EFCore/Storage/ValueConversion/BoolToStringConverter.cs +++ b/src/EFCore/Storage/ValueConversion/BoolToStringConverter.cs @@ -11,6 +11,10 @@ namespace Microsoft.EntityFrameworkCore.Storage.ValueConversion; /// public class BoolToStringConverter : BoolToTwoValuesConverter { + private static readonly MethodInfo StringToUpperInvariantMethod = typeof(string).GetMethod(nameof(string.ToUpperInvariant))!; + private static readonly MethodInfo StringCharsMethod = typeof(string).GetMethod("get_Chars", [typeof(int)])!; + private static readonly MethodInfo StringIsNullOrEmpty = typeof(string).GetMethod(nameof(string.IsNullOrEmpty), [typeof(string)])!; + /// /// Creates a new instance of this converter. A case-insensitive first character test is used /// when converting from the store. @@ -48,9 +52,31 @@ public BoolToStringConverter( private static Expression> FromProvider(string trueValue) { - var testChar = trueValue.ToUpperInvariant()[0]; + // v => !string.IsNullOrEmpty(v) && (int)v.ToUpperInvariant()[0] == (int)trueValue.ToUpperInvariant()[0]; + var prm = Expression.Parameter(typeof(string), "v"); + var result = Expression.Lambda>( + Expression.AndAlso( + Expression.Not( + Expression.Call(StringIsNullOrEmpty, prm)), + Expression.Equal( + Expression.Convert( + Expression.Call( + Expression.Call( + prm, + StringToUpperInvariantMethod), + StringCharsMethod, + Expression.Constant(0)), + typeof(int)), + Expression.Convert( + Expression.Call( + Expression.Call( + Expression.Constant(trueValue), + StringToUpperInvariantMethod), + StringCharsMethod, + Expression.Constant(0)), + typeof(int)))), + prm); - return v => !string.IsNullOrEmpty(v) - && v.ToUpperInvariant()[0] == testChar; + return result; } } diff --git a/src/EFCore/Storage/ValueConversion/StringToBytesConverter.cs b/src/EFCore/Storage/ValueConversion/StringToBytesConverter.cs index 4fedab8c122..170dbf10ebd 100644 --- a/src/EFCore/Storage/ValueConversion/StringToBytesConverter.cs +++ b/src/EFCore/Storage/ValueConversion/StringToBytesConverter.cs @@ -13,6 +13,9 @@ namespace Microsoft.EntityFrameworkCore.Storage.ValueConversion; /// public class StringToBytesConverter : ValueConverter { + private static readonly MethodInfo EncodingGetBytesMethodInfo = typeof(Encoding).GetMethod(nameof(Encoding.GetBytes), [typeof(string)])!; + private static readonly MethodInfo EncodingGetStringMethodInfo = typeof(Encoding).GetMethod(nameof(Encoding.GetString), [typeof(byte[])])!; + /// /// Creates a new instance of this converter. /// @@ -28,8 +31,8 @@ public StringToBytesConverter( Encoding encoding, ConverterMappingHints? mappingHints = null) : base( - v => encoding.GetBytes(v!), - v => encoding.GetString(v!), + FromProvider(encoding), + ToProvider(encoding), mappingHints) { } @@ -39,4 +42,26 @@ public StringToBytesConverter( /// public static ValueConverterInfo DefaultInfo { get; } = new(typeof(string), typeof(byte[]), i => new StringToBytesConverter(Encoding.UTF8, i.MappingHints)); + + private static Expression> FromProvider(Encoding encoding) + { + // v => encoding.GetBytes(v!), + var prm = Expression.Parameter(typeof(string), "v"); + var result = Expression.Lambda>( + Expression.Call(Expression.Constant(encoding), EncodingGetBytesMethodInfo, prm), + prm); + + return result; + } + + private static Expression> ToProvider(Encoding encoding) + { + // v => encoding.GetString(v!) + var prm = Expression.Parameter(typeof(byte[]), "v"); + var result = Expression.Lambda>( + Expression.Call(Expression.Constant(encoding), EncodingGetStringMethodInfo, prm), + prm); + + return result; + } } diff --git a/src/EFCore/Storage/ValueConversion/ValueConverter.cs b/src/EFCore/Storage/ValueConversion/ValueConverter.cs index b7c0a00b058..c096f0cd0fb 100644 --- a/src/EFCore/Storage/ValueConversion/ValueConverter.cs +++ b/src/EFCore/Storage/ValueConversion/ValueConverter.cs @@ -251,4 +251,9 @@ var firstConverter ? firstConverter.MappingHints : secondConverter.MappingHints.With(firstConverter.MappingHints))!; } + + /// + /// The expression representing construction of this object. + /// + public abstract Expression ConstructorExpression { get; } } diff --git a/src/EFCore/Storage/ValueConversion/ValueConverter`.cs b/src/EFCore/Storage/ValueConversion/ValueConverter`.cs index 0bf8907b7e2..f81bfa285b8 100644 --- a/src/EFCore/Storage/ValueConversion/ValueConverter`.cs +++ b/src/EFCore/Storage/ValueConversion/ValueConverter`.cs @@ -172,4 +172,20 @@ public override Type ModelClrType /// public override Type ProviderClrType => typeof(TProvider); + + private readonly ConstructorInfo _constructorInfo = typeof(ValueConverter).GetConstructor( + [ + typeof(Expression>), + typeof(Expression>), + typeof(ConverterMappingHints) + ])!; + + /// + public override Expression ConstructorExpression => + Expression.New( + _constructorInfo, + ConvertToProviderExpression, + ConvertFromProviderExpression, + // TODO: hints are not supported + Expression.Default(typeof(ConverterMappingHints))); } diff --git a/src/Shared/ExpressionExtensions.cs b/src/Shared/ExpressionExtensions.cs index 0835bd7ad95..b4484a39ce2 100644 --- a/src/Shared/ExpressionExtensions.cs +++ b/src/Shared/ExpressionExtensions.cs @@ -4,6 +4,7 @@ #nullable enable using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Query; // ReSharper disable once CheckNamespace namespace System.Linq.Expressions; @@ -45,7 +46,12 @@ private static Expression RemoveConvert(Expression expression) : expression; public static T GetConstantValue(this Expression expression) - => expression is ConstantExpression constantExpression - ? (T)constantExpression.Value! - : throw new InvalidOperationException(); + => expression switch + { + ConstantExpression constantExpression => (T)constantExpression.Value!, +#pragma warning disable EF9100 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + LiftableConstantExpression liftableConstantExpression => (T)liftableConstantExpression.OriginalExpression.Value!, +#pragma warning restore EF9100 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + _ => throw new InvalidOperationException() + }; } diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocAdvancedMappingsQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocAdvancedMappingsQueryRelationalTestBase.cs index 79a4d7cf64c..e33ea578f93 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/AdHocAdvancedMappingsQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocAdvancedMappingsQueryRelationalTestBase.cs @@ -71,7 +71,8 @@ public virtual async Task Projecting_one_of_two_similar_complex_types_picks_the_ .Select( x => new { - x.B.A.Id, x.B.Info.Created, + x.B.A.Id, + x.B.Info.Created, }).ToList(); Assert.Equal(new DateTime(2000, 1, 1), query[0].Created); diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryTestBase.cs index b51ac0f8a32..d0467f17ecc 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryTestBase.cs @@ -275,7 +275,8 @@ protected Task Seed30028(Context32939 ctx) { var entity = new Context32939.Entity32939 { - Empty = new Context32939.JsonEmpty32939(), FieldOnly = new Context32939.JsonFieldOnly32939() + Empty = new Context32939.JsonEmpty32939(), + FieldOnly = new Context32939.JsonFieldOnly32939() }; ctx.Entities.Add(entity); diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocQuerySplittingQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocQuerySplittingQueryTestBase.cs index 06c6fa948b5..db6a201e52f 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/AdHocQuerySplittingQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocQuerySplittingQueryTestBase.cs @@ -249,7 +249,8 @@ protected virtual Task CreateTestStore25225() .Select( c => new Context25225.CollectionViewModel { - Id = c.Id, ParentId = c.ParentId, + Id = c.Id, + ParentId = c.ParentId, }) .ToArray() }); diff --git a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs index 0015346d141..55c932ae72d 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs @@ -1311,7 +1311,7 @@ public virtual Task Json_collection_skip_take_in_projection_with_json_reference_ assertOrder: true, elementAsserter: (e, a) => AssertCollection(e, a, ordered: true)); - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: list comparer captures element comparer, see issue #33383")] [MemberData(nameof(IsAsyncData))] public virtual Task Json_collection_distinct_in_projection(bool async) => AssertQuery( @@ -1349,7 +1349,7 @@ public virtual Task Json_collection_leaf_filter_in_projection(bool async) assertOrder: true, elementAsserter: (e, a) => AssertCollection(e, a, ordered: true)); - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: list comparer captures element comparer, see issue #33383")] [MemberData(nameof(IsAsyncData))] public virtual Task Json_multiple_collection_projections(bool async) => AssertQuery( @@ -1376,7 +1376,7 @@ public virtual Task Json_multiple_collection_projections(bool async) AssertCollection(e.Fourth, a.Fourth); }); - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: list comparer captures element comparer, see issue #33383")] [MemberData(nameof(IsAsyncData))] public virtual Task Json_branch_collection_distinct_and_other_collection(bool async) => AssertQuery( diff --git a/test/EFCore.Relational.Specification.Tests/Query/OwnedEntityQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/OwnedEntityQueryRelationalTestBase.cs index 55789c12376..12f1b63c13d 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/OwnedEntityQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/OwnedEntityQueryRelationalTestBase.cs @@ -518,13 +518,15 @@ public Task SeedAsync() Add( new Monarch { - Name = "His August Majesty Guslav the Fifth", RulerOf = "The Union", + Name = "His August Majesty Guslav the Fifth", + RulerOf = "The Union", }); Add( new Monarch { - Name = "Emperor Uthman-ul-Dosht", RulerOf = "The Gurkish Empire", + Name = "Emperor Uthman-ul-Dosht", + RulerOf = "The Gurkish Empire", }); Add( diff --git a/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs index c43c07a2258..fdb44739e2d 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs @@ -1158,7 +1158,8 @@ public virtual void Scalar_Function_Anonymous_Type_Select_Nested_Instance() where c.Id == customerId select new { - c.LastName, OrderCount = context.StarValueInstance(starCount, context.CustomerOrderCountInstance(customerId)) + c.LastName, + OrderCount = context.StarValueInstance(starCount, context.CustomerOrderCountInstance(customerId)) }).Single(); Assert.Equal("Three", cust.LastName); @@ -1592,7 +1593,8 @@ public virtual void QF_Select_Direct_In_Anonymous() () => (from c in context.Customers select new { - c.Id, Prods = context.GetTopTwoSellingProducts().ToList(), + c.Id, + Prods = context.GetTopTwoSellingProducts().ToList(), }).ToList()).Message; Assert.Equal(RelationalStrings.InsufficientInformationToIdentifyElementOfCollectionJoin, message); @@ -1607,7 +1609,8 @@ public virtual void QF_Select_Direct_In_Anonymous_distinct() var query = (from c in context.Customers select new { - c.Id, Prods = context.GetTopTwoSellingProducts().Distinct().ToList(), + c.Id, + Prods = context.GetTopTwoSellingProducts().Distinct().ToList(), }).ToList(); } } @@ -1734,7 +1737,8 @@ public virtual void QF_Select_NonCorrelated_Subquery_In_Anonymous() () => (from c in context.Customers select new { - c.Id, Prods = context.GetTopTwoSellingProducts().Select(p => p.ProductId).ToList(), + c.Id, + Prods = context.GetTopTwoSellingProducts().Select(p => p.ProductId).ToList(), }).ToList()).Message; Assert.Equal(RelationalStrings.InsufficientInformationToIdentifyElementOfCollectionJoin, message); diff --git a/test/EFCore.Relational.Tests/Query/Internal/BufferedDataReaderTest.cs b/test/EFCore.Relational.Tests/Query/Internal/BufferedDataReaderTest.cs index 85cebb23e81..887f9899e97 100644 --- a/test/EFCore.Relational.Tests/Query/Internal/BufferedDataReaderTest.cs +++ b/test/EFCore.Relational.Tests/Query/Internal/BufferedDataReaderTest.cs @@ -177,9 +177,16 @@ private async Task Verify_method_result( columnType = typeof(object); } + var getFieldValueMethod = typeof(DbDataReader).GetMethod(nameof(DbDataReader.GetFieldValue)).MakeGenericMethod(columnType); + var prm = Expression.Parameter(typeof(DbDataReader), "r"); + var getFieldValueLambda = Expression.Lambda( + Expression.Call(prm, getFieldValueMethod, Expression.Constant(0)), + prm, + Expression.Parameter(typeof(int[]), "_")); + var columns = new[] { - ReaderColumn.Create(columnType, true, null, null, (Func)((r, _) => r.GetFieldValue(0))) + ReaderColumn.Create(columnType, true, null, null, getFieldValueLambda) }; var bufferedReader = new BufferedDataReader(reader, false); diff --git a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs index b8373ba5df8..f54a1e559f2 100644 --- a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs +++ b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs @@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using static Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor; namespace Microsoft.EntityFrameworkCore; @@ -552,7 +553,13 @@ public class RelationalApiConsistencyFixture : ApiConsistencyFixtureBase typeof(RelationalConnectionDiagnosticsLogger).GetMethod( nameof(IRelationalConnectionDiagnosticsLogger.ConnectionDisposingAsync)), typeof(RelationalConnectionDiagnosticsLogger).GetMethod( - nameof(IRelationalConnectionDiagnosticsLogger.ConnectionDisposedAsync)) + nameof(IRelationalConnectionDiagnosticsLogger.ConnectionDisposedAsync)), + + // internal methods made public for AOT + typeof(ShaperProcessingExpressionVisitor).GetMethod(nameof(ShaperProcessingExpressionVisitor.PopulateSplitIncludeCollectionAsync)), + typeof(ShaperProcessingExpressionVisitor).GetMethod(nameof(ShaperProcessingExpressionVisitor.PopulateSplitCollectionAsync)), + typeof(ShaperProcessingExpressionVisitor).GetMethod(nameof(ShaperProcessingExpressionVisitor.TaskAwaiter)), + typeof(RelationalShapedQueryCompilingExpressionVisitor).GetMethod(nameof(RelationalShapedQueryCompilingExpressionVisitor.NonQueryResultAsync)), ]; public override HashSet MetadataMethodExceptions { get; } = @@ -561,6 +568,17 @@ public class RelationalApiConsistencyFixture : ApiConsistencyFixtureBase typeof(IMutableStoredProcedure).GetMethod(nameof(IMutableStoredProcedure.AddResultColumn)) ]; + public override HashSet VirtualMethodExceptions { get; } = + [ + // non-sealed record +#pragma warning disable EF9100 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + typeof(RelationalMaterializerLiftableConstantContext).GetMethod("get_RelationalDependencies"), + typeof(RelationalMaterializerLiftableConstantContext).GetMethod("set_RelationalDependencies"), + typeof(RelationalMaterializerLiftableConstantContext).GetMethod("Deconstruct", [typeof(ShapedQueryCompilingExpressionVisitorDependencies).MakeByRefType()]), + typeof(RelationalMaterializerLiftableConstantContext).GetMethod("Deconstruct", [typeof(ShapedQueryCompilingExpressionVisitorDependencies).MakeByRefType(), typeof(RelationalShapedQueryCompilingExpressionVisitorDependencies).MakeByRefType()]), +#pragma warning restore EF9100 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + ]; + public List> RelationalMetadataMethods { get; } = []; protected override void Initialize() diff --git a/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs b/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs index 57d688e5c4b..3ad07fbeab3 100644 --- a/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs +++ b/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs @@ -1064,6 +1064,7 @@ where type.IsVisible from method in type.GetMethods(AnyInstance) where method.DeclaringType == type && !Fixture.NonVirtualMethods.Contains(method) + && !Fixture.VirtualMethodExceptions.Contains(method) && !method.IsVirtual && !method.Name.StartsWith("add_", StringComparison.Ordinal) && !method.Name.StartsWith("remove_", StringComparison.Ordinal) @@ -1275,6 +1276,15 @@ protected ApiConsistencyFixtureBase() public virtual Dictionary> UnmatchedMirrorMethods { get; } = new(); public virtual Dictionary MetadataMethodNameTransformers { get; } = new(); public virtual HashSet MetadataMethodExceptions { get; } = []; + public virtual HashSet VirtualMethodExceptions { get; } = + [ + // un-sealed record +#pragma warning disable EF9100 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + typeof(MaterializerLiftableConstantContext).GetMethod("get_Dependencies"), + typeof(MaterializerLiftableConstantContext).GetMethod("set_Dependencies"), + typeof(MaterializerLiftableConstantContext).GetMethod("Deconstruct"), +#pragma warning restore EF9100 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + ]; public virtual HashSet ComputedDependencyProperties { get; } = [ diff --git a/test/EFCore.Specification.Tests/JsonTypesTestBase.cs b/test/EFCore.Specification.Tests/JsonTypesTestBase.cs index 3aaa314d8b3..0ace20cf093 100644 --- a/test/EFCore.Specification.Tests/JsonTypesTestBase.cs +++ b/test/EFCore.Specification.Tests/JsonTypesTestBase.cs @@ -3807,6 +3807,11 @@ public override void ToJsonTyped(Utf8JsonWriter writer, Geometry value) serializer.Serialize(jsonWriter, value); writer.WriteRawValue(stringWriter.ToString()); } + + private readonly Expression> _instanceLambda = () => Instance; + + /// + public override Expression ConstructorExpression => _instanceLambda.Body; } private readonly NullabilityInfoContext _nullabilityInfoContext = new(); diff --git a/test/EFCore.Specification.Tests/MaterializationInterceptionTestBase.cs b/test/EFCore.Specification.Tests/MaterializationInterceptionTestBase.cs index d40ab02d102..d2a4bb7cae7 100644 --- a/test/EFCore.Specification.Tests/MaterializationInterceptionTestBase.cs +++ b/test/EFCore.Specification.Tests/MaterializationInterceptionTestBase.cs @@ -494,6 +494,7 @@ public InstantiationBinding ModifyBinding(InstantiationBindingInterceptionData i return new FactoryMethodBinding( this, + Expression.Constant(this), typeof(TestBindingInterceptor).GetTypeInfo().GetDeclaredMethod(nameof(BookFactory))!, new List(), interceptionData.TypeBase.ClrType); diff --git a/test/EFCore.Specification.Tests/Query/AdHocMiscellaneousQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AdHocMiscellaneousQueryTestBase.cs index 22223eb470e..02e48fa62f5 100644 --- a/test/EFCore.Specification.Tests/Query/AdHocMiscellaneousQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/AdHocMiscellaneousQueryTestBase.cs @@ -238,15 +238,18 @@ public async Task SeedAsync() Contacts.AddRange( new ServiceOperatorContact { - UserName = "service.operator@esoterix.co.uk", ServiceOperator = ServiceOperators.OrderBy(o => o.Id).First() + UserName = "service.operator@esoterix.co.uk", + ServiceOperator = ServiceOperators.OrderBy(o => o.Id).First() }, new EmployerContact { - UserName = "uwe@esoterix.co.uk", Employer = Employers.OrderBy(e => e.Id).First(e => e.Name == "UWE") + UserName = "uwe@esoterix.co.uk", + Employer = Employers.OrderBy(e => e.Id).First(e => e.Name == "UWE") }, new EmployerContact { - UserName = "hp@esoterix.co.uk", Employer = Employers.OrderBy(e => e.Id).First(e => e.Name == "Hewlett Packard") + UserName = "hp@esoterix.co.uk", + Employer = Employers.OrderBy(e => e.Id).First(e => e.Name == "Hewlett Packard") }, new Contact { UserName = "noroles@esoterix.co.uk" }); @@ -1637,7 +1640,8 @@ public virtual async Task GroupBy_aggregate_on_right_side_of_join(bool async) o => o.OrderId, (o, g) => new { - Key = o, IsPending = g.Max(y => y.ShippingDate == null && y.CancellationDate == null ? o : (o - 10000000)) + Key = o, + IsPending = g.Max(y => y.ShippingDate == null && y.CancellationDate == null ? o : (o - 10000000)) }) .OrderBy(e => e.Key); @@ -1896,7 +1900,8 @@ group t.Id by t.Value into tg select new { - A = tg.Key, B = context.Tables.Where(t => t.Value == tg.Max() * 6).Max(t => (int?)t.Id), + A = tg.Key, + B = context.Tables.Where(t => t.Value == tg.Max() * 6).Max(t => (int?)t.Id), }; var orders = async @@ -2162,7 +2167,8 @@ public virtual async Task Filter_on_nested_DTO_with_interface_gets_simplified_co CountryId = m.Company.CountryId, Country = new Context31961.CountryDto() { - Id = m.Company.Country.Id, CountryName = m.Company.Country.CountryName, + Id = m.Company.Country.Id, + CountryName = m.Company.Country.CountryName, }, } : null, diff --git a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs index c7deae9cbcc..5a23a69a79f 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs @@ -2242,7 +2242,8 @@ join l2 in ss.Set() } equals new { - A = EF.Property(l2, "Level1_Optional_Id"), B = EF.Property(l2, "OneToMany_Optional_Self_Inverse2Id") + A = EF.Property(l2, "Level1_Optional_Id"), + B = EF.Property(l2, "OneToMany_Optional_Self_Inverse2Id") } select l1); @@ -3300,7 +3301,8 @@ public virtual Task Member_over_null_check_ternary_and_nested_dto_type(bool asyn ? null : new Level2Dto { - Id = l1.OneToOne_Optional_FK1.Id, Name = l1.OneToOne_Optional_FK1.Name, + Id = l1.OneToOne_Optional_FK1.Id, + Name = l1.OneToOne_Optional_FK1.Name, } }) .OrderBy(e => e.Level2.Name) @@ -3458,7 +3460,8 @@ public virtual Task Composite_key_join_on_groupby_aggregate_projecting_only_grou o => new { o.Id, Condition = true }, i => new { - Id = i.Key, Condition = i.Sum > 10, + Id = i.Key, + Condition = i.Sum > 10, }, (o, i) => i.Key)); @@ -3763,7 +3766,8 @@ public virtual Task Project_shadow_properties8(bool async) ss => from x in ss.Set() select new { - x.Id, InheritanceLeaf2Id = EF.Property(x, "InheritanceLeaf2Id"), + x.Id, + InheritanceLeaf2Id = EF.Property(x, "InheritanceLeaf2Id"), }, elementSorter: e => e.Id); diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index fea5dc51208..493b6b4d3b2 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -653,7 +653,8 @@ public virtual Task Select_enum_has_flag(bool async) .Select( b => new { - hasFlagTrue = b.Rank.HasFlag(MilitaryRank.Corporal), hasFlagFalse = b.Rank.HasFlag(MilitaryRank.Sergeant) + hasFlagTrue = b.Rank.HasFlag(MilitaryRank.Corporal), + hasFlagFalse = b.Rank.HasFlag(MilitaryRank.Sergeant) })); [ConditionalTheory] @@ -1097,11 +1098,11 @@ public virtual Task Where_compare_anonymous_types(bool async) ss => from g in ss.Set() from o in ss.Set().OfType() where new - { - Name = g.LeaderNickname, - Squad = g.LeaderSquadId, - Five = 5 - } + { + Name = g.LeaderNickname, + Squad = g.LeaderSquadId, + Five = 5 + } == new { Name = o.Nickname, @@ -2807,7 +2808,7 @@ public virtual Task Comparing_two_collection_navigations_composite_key(bool asyn async, ss => from g1 in ss.Set() from g2 in ss.Set() - // ReSharper disable once PossibleUnintendedReferenceComparison + // ReSharper disable once PossibleUnintendedReferenceComparison where g1.Weapons == g2.Weapons orderby g1.Nickname select new { Nickname1 = g1.Nickname, Nickname2 = g2.Nickname }, @@ -6223,7 +6224,7 @@ public virtual Task Byte_array_filter_by_length_literal(bool async) [MemberData(nameof(IsAsyncData))] public virtual Task Byte_array_filter_by_length_parameter(bool async) { - var someByteArr = new[] { (byte)42, (byte)24}; + var someByteArr = new[] { (byte)42, (byte)24 }; return AssertQuery( async, ss => ss.Set().Where(w => w.Banner.Length == someByteArr.Length), diff --git a/test/EFCore.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryTestBase.cs index 6357dfe2c77..cc190fa4f36 100644 --- a/test/EFCore.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryTestBase.cs @@ -113,7 +113,7 @@ public virtual async Task Multidimensional_array_is_not_supported() #endregion Support for specific element types - [ConditionalFact] + [ConditionalFact(Skip = "AOT: custom converters are not supported")] public virtual async Task Column_with_custom_converter() { var contextFactory = await InitializeAsync( diff --git a/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs index a19ff9c3cd8..345bdade48c 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs @@ -2548,7 +2548,8 @@ public virtual Task GroupBy_group_Distinct_Select_Distinct_aggregate(bool async) g => new { - g.Key, Max = g.Distinct().Select(e => e.OrderDate).Distinct().Max(), + g.Key, + Max = g.Distinct().Select(e => e.OrderDate).Distinct().Max(), }), elementSorter: e => e.Key); @@ -2563,7 +2564,8 @@ public virtual Task GroupBy_group_Where_Select_Distinct_aggregate(bool async) g => new { - g.Key, Max = g.Where(e => e.OrderDate.HasValue).Select(e => e.OrderDate).Distinct().Max(), + g.Key, + Max = g.Where(e => e.OrderDate.HasValue).Select(e => e.OrderDate).Distinct().Max(), }), elementSorter: e => e.Key); diff --git a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs index a100eae5b3a..2afa772e4e9 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs @@ -723,7 +723,8 @@ public virtual Task Ternary_should_not_evaluate_both_sides_with_parameter(bool a o => new { // ReSharper disable SimplifyConditionalTernaryExpression - Data1 = param != null ? o.OrderDate == param.Value : true, Data2 = param == null ? true : o.OrderDate == param.Value + Data1 = param != null ? o.OrderDate == param.Value : true, + Data2 = param == null ? true : o.OrderDate == param.Value // ReSharper restore SimplifyConditionalTernaryExpression })); } diff --git a/test/EFCore.Specification.Tests/Query/OwnedEntityQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/OwnedEntityQueryTestBase.cs index 189928999a3..4c91829a155 100644 --- a/test/EFCore.Specification.Tests/Query/OwnedEntityQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/OwnedEntityQueryTestBase.cs @@ -363,7 +363,8 @@ public virtual async Task Projecting_correlated_collection_property_for_owned_en var query = context.Warehouses.Select( x => new Context18582.WarehouseModel { - WarehouseCode = x.WarehouseCode, DestinationCountryCodes = x.DestinationCountries.Select(c => c.CountryCode).ToArray() + WarehouseCode = x.WarehouseCode, + DestinationCountryCodes = x.DestinationCountries.Select(c => c.CountryCode).ToArray() }).AsNoTracking(); var result = async @@ -750,7 +751,8 @@ public virtual async Task OwnsMany_correlated_projection(bool async) var results = await context.Contacts.Select( contact => new Context22089.ContactDto { - Id = contact.Id, Names = contact.Names.Select(name => new Context22089.NameDto()).ToArray() + Id = contact.Id, + Names = contact.Names.Select(name => new Context22089.NameDto()).ToArray() }) .ToListAsync(); } diff --git a/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs index 4f8953c0f73..fa171178571 100644 --- a/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs @@ -25,7 +25,8 @@ public virtual async Task Can_query_owner_with_different_owned_types_having_same await context.AddAsync( new HeliumBalloon { - Id = Guid.NewGuid().ToString(), Gas = new Helium(), + Id = Guid.NewGuid().ToString(), + Gas = new Helium(), }); await context.AddAsync(new HydrogenBalloon { Id = Guid.NewGuid().ToString(), Gas = new Hydrogen() }); diff --git a/test/EFCore.Specification.Tests/Query/SpatialQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/SpatialQueryTestBase.cs index e9d851d9f76..2c1d13effe8 100644 --- a/test/EFCore.Specification.Tests/Query/SpatialQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/SpatialQueryTestBase.cs @@ -21,7 +21,7 @@ protected SpatialQueryTestBase(TFixture fixture) protected virtual bool AssertDistances => true; - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual async Task SimpleSelect(bool async) { @@ -42,7 +42,7 @@ await AssertQuery( ss => ss.Set()); } - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task WithConversion(bool async) => AssertQuery( @@ -111,7 +111,7 @@ public virtual Task AsText(bool async) Assert.Equal(e.Text, a.Text, WktComparer.Instance); }); - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task Boundary(bool async) => AssertQuery( @@ -124,7 +124,7 @@ public virtual Task Boundary(bool async) Assert.Equal(e.Boundary, a.Boundary, GeometryComparer.Instance); }); - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task Buffer(bool async) => AssertQuery( @@ -147,7 +147,7 @@ public virtual Task Buffer(bool async) } }); - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task Buffer_quadrantSegments(bool async) => AssertQuery( @@ -170,7 +170,7 @@ public virtual Task Buffer_quadrantSegments(bool async) } }); - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task Centroid(bool async) => AssertQuery( @@ -183,7 +183,7 @@ public virtual Task Centroid(bool async) Assert.Equal(e.Centroid, a.Centroid, GeometryComparer.Instance); }); - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task Combine_aggregate(bool async) => AssertQuery( @@ -205,7 +205,7 @@ public virtual Task Combine_aggregate(bool async) Assert.Equal(eCollection.Geometries, aCollection.Geometries); }); - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task EnvelopeCombine_aggregate(bool async) => AssertQuery( @@ -235,7 +235,7 @@ public virtual Task Contains(bool async) elementSorter: x => x.Id); } - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task ConvexHull(bool async) => AssertQuery( @@ -249,7 +249,7 @@ public virtual Task ConvexHull(bool async) Assert.Equal(e.ConvexHull, a.ConvexHull, GeometryComparer.Instance); }); - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task ConvexHull_aggregate(bool async) => AssertQuery( @@ -324,7 +324,7 @@ public virtual Task Crosses(bool async) elementSorter: x => x.Id); } - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task Difference(bool async) { @@ -590,7 +590,7 @@ public virtual Task Distance_on_converted_geometry_type_constant_lhs(bool async) } }); - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task EndPoint(bool async) => AssertQuery( @@ -598,7 +598,7 @@ public virtual Task EndPoint(bool async) ss => ss.Set().Select(e => new { e.Id, EndPoint = e.LineString == null ? null : e.LineString.EndPoint }), elementSorter: e => e.Id); - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task Envelope(bool async) => AssertQuery( @@ -625,7 +625,7 @@ public virtual Task EqualsTopologically(bool async) elementSorter: x => x.Id); } - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task ExteriorRing(bool async) => AssertQuery( @@ -642,7 +642,7 @@ public virtual Task GeometryType(bool async) e => new { e.Id, GeometryType = e.Point == null ? null : e.Point.GeometryType }), elementSorter: x => x.Id); - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task GetGeometryN(bool async) => AssertQuery( @@ -652,7 +652,7 @@ public virtual Task GetGeometryN(bool async) e => new { e.Id, Geometry0 = e.MultiLineString == null ? null : e.MultiLineString.GetGeometryN(0) }), elementSorter: x => x.Id); - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task GetGeometryN_with_null_argument(bool async) => AssertQuery( @@ -666,7 +666,7 @@ public virtual Task GetGeometryN_with_null_argument(bool async) ss => ss.Set().Select(e => new { e.Id, Geometry0 = default(Geometry) }), elementSorter: x => x.Id); - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task GetInteriorRingN(bool async) => AssertQuery( @@ -689,7 +689,7 @@ public virtual Task GetInteriorRingN(bool async) }), elementSorter: x => x.Id); - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task GetPointN(bool async) => AssertQuery( @@ -699,7 +699,7 @@ public virtual Task GetPointN(bool async) .Select(e => new { e.Id, Point0 = e.LineString == null ? null : e.LineString.GetPointN(0) }), elementSorter: x => x.Id); - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task InteriorPoint(bool async) => AssertQuery( @@ -726,7 +726,7 @@ public virtual Task InteriorPoint(bool async) } }); - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task Intersection(bool async) { @@ -841,7 +841,7 @@ public virtual Task IsWithinDistance(bool async) }); } - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task Item(bool async) => AssertQuery( @@ -958,7 +958,7 @@ public virtual Task Overlaps(bool async) elementSorter: x => x.Id); } - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task PointOnSurface(bool async) => AssertQuery( @@ -999,7 +999,7 @@ public virtual Task Relate(bool async) elementSorter: x => x.Id); } - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task Reverse(bool async) => AssertQuery( @@ -1026,7 +1026,7 @@ public virtual Task SRID_geometry(bool async) e => new { e.Id, SRID = e.Geometry == null ? (int?)null : e.Geometry.SRID }), elementSorter: x => x.Id); - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task StartPoint(bool async) => AssertQuery( @@ -1035,7 +1035,7 @@ public virtual Task StartPoint(bool async) .Select(e => new { e.Id, StartPoint = e.LineString == null ? null : e.LineString.StartPoint }), elementSorter: x => x.Id); - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task SymmetricDifference(bool async) { @@ -1096,7 +1096,7 @@ public virtual Task Touches(bool async) elementSorter: x => x.Id); } - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task Union(bool async) { @@ -1114,7 +1114,7 @@ public virtual Task Union(bool async) }); } - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task Union_aggregate(bool async) => AssertQuery( @@ -1130,7 +1130,7 @@ public virtual Task Union_aggregate(bool async) Assert.Equal(e.Union, a.Union, GeometryComparer.Instance); }); - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task Union_void(bool async) => AssertQuery( @@ -1190,7 +1190,7 @@ public virtual Task Z(bool async) } }); - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual Task XY_with_collection_join(bool async) => AssertFirstOrDefault( diff --git a/test/EFCore.Specification.Tests/Scaffolding/CompiledModelTestBase.cs b/test/EFCore.Specification.Tests/Scaffolding/CompiledModelTestBase.cs index 85782222257..eea626771a1 100644 --- a/test/EFCore.Specification.Tests/Scaffolding/CompiledModelTestBase.cs +++ b/test/EFCore.Specification.Tests/Scaffolding/CompiledModelTestBase.cs @@ -826,6 +826,11 @@ public override Guid FromJsonTyped(ref Utf8JsonReaderManager manager, object? ex public override void ToJsonTyped(Utf8JsonWriter writer, Guid value) => writer.WriteStringValue(value); + + private readonly Expression> _ctorLambda = () => new(); + + /// + public override Expression ConstructorExpression => _ctorLambda.Body; } public class ManyTypes diff --git a/test/EFCore.Specification.Tests/SpatialTestBase.cs b/test/EFCore.Specification.Tests/SpatialTestBase.cs index 840519678b5..4b70d6efac5 100644 --- a/test/EFCore.Specification.Tests/SpatialTestBase.cs +++ b/test/EFCore.Specification.Tests/SpatialTestBase.cs @@ -42,7 +42,7 @@ public virtual void Values_arent_compared_by_reference() Assert.False(db.Entry(entity).Property(e => e.Point).IsModified); } - [ConditionalFact] + [ConditionalFact(Skip = "AOT: NTS is not supported")] public virtual async void Mutation_of_tracked_values_does_not_mutate_values_in_store() { Point CreatePoint(double y = 2.2) @@ -92,7 +92,7 @@ await ExecuteWithStrategyInTransactionAsync( }); } - [ConditionalFact] + [ConditionalFact(Skip = "AOT: NTS is not supported")] public virtual void Translators_handle_static_members() { using var db = Fixture.CreateContext(); @@ -108,7 +108,7 @@ orderby e.Id }).FirstOrDefault(); } - [ConditionalFact] + [ConditionalFact(Skip = "AOT: NTS is not supported")] public virtual void Can_roundtrip_Z_and_M() { using var db = Fixture.CreateContext(); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs index 7dbdedb982d..df9ec8389ff 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs @@ -1037,7 +1037,7 @@ public class List #region 23282 - [ConditionalFact] + [ConditionalFact(Skip = "AOT: NTS is not supported")] [SqlServerCondition(SqlServerCondition.SupportsSqlClr)] public virtual async Task Can_query_point_with_buffered_data_reader() { diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs index e35cd767d91..2eefaccf6d6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs @@ -699,9 +699,12 @@ private static readonly MethodInfo SkipMethod #endregion - [ConditionalFact] - public override Task Column_with_custom_converter() - => base.Column_with_custom_converter(); + public override async Task Column_with_custom_converter() + { + await base.Column_with_custom_converter(); + + AssertSql(""); + } public override async Task Parameter_with_inferred_value_converter() { diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryLoggingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryLoggingSqlServerTest.cs index 509035f2fbc..c44b75bcfad 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryLoggingSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryLoggingSqlServerTest.cs @@ -34,7 +34,7 @@ var customers "Compiling query expression: ", Fixture.TestSqlLoggerFactory.Log[0].Message); Assert.StartsWith( - "Generated query execution expression: " + Environment.NewLine + "'queryContext => new SingleQueryingEnumerable(", + "Generated query execution expression: " + Environment.NewLine + "'queryContext => SingleQueryingEnumerable.Create(", Fixture.TestSqlLoggerFactory.Log[1].Message); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/RawSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/RawSqlServerTest.cs index b6525c3fa55..0a06adc72f0 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/RawSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/RawSqlServerTest.cs @@ -13,53 +13,50 @@ public class RawSqlServerTest : NonSharedModelTestBase [ConditionalFact] public virtual async Task ToQuery_can_use_FromSqlRaw() { - var contextFactory = await InitializeAsync(seed: c => c.SeedAsync()); + var contextFactory = await InitializeAsync(seed: c => c.SeedAsync()); + using var context = contextFactory.CreateContext(); + var query = context.Set().ToList(); - using (var context = contextFactory.CreateContext()) - { - var query = context.Set().ToList(); - - Assert.Equal(4, query.Count); + Assert.Equal(4, query.Count); - AssertSql( - """ + AssertSql( + """ SELECT o.Amount From Orders AS o -- RAW """); - } } - protected class MyContext13346(DbContextOptions options) : DbContext(options) + public class Context13346(DbContextOptions options) : DbContext(options) { - public virtual DbSet Orders { get; set; } + public virtual DbSet Orders { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { #pragma warning disable CS0618 // Type or member is obsolete - modelBuilder.Entity() + modelBuilder.Entity() .HasNoKey() - .ToQuery(() => Set().FromSqlRaw("SELECT o.Amount From Orders AS o -- RAW")); + .ToQuery(() => Set().FromSqlRaw("SELECT o.Amount From Orders AS o -- RAW")); #pragma warning restore CS0618 // Type or member is obsolete } public Task SeedAsync() { AddRange( - new Order13346 { Amount = 1 }, - new Order13346 { Amount = 2 }, - new Order13346 { Amount = 3 }, - new Order13346 { Amount = 4 } + new Order { Amount = 1 }, + new Order { Amount = 2 }, + new Order { Amount = 3 }, + new Order { Amount = 4 } ); return SaveChangesAsync(); } - public class Order13346 + public class Order { public int Id { get; set; } public int Amount { get; set; } } - public class OrderSummary13346 + public class OrderSummary { public int Amount { get; set; } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeographyTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeographyTest.cs index 8b5dc90d599..e6b90c9d77c 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeographyTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeographyTest.cs @@ -218,7 +218,7 @@ public override Task Covers(bool async) public override Task Crosses(bool async) => Task.CompletedTask; - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual async Task CurveToLine(bool async) { diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeometryTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeometryTest.cs index b7aaa29bd74..f0b4d8c986d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeometryTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SpatialQuerySqlServerGeometryTest.cs @@ -245,7 +245,7 @@ FROM [LineStringEntity] AS [l] """); } - [ConditionalTheory] + [ConditionalTheory(Skip = "AOT: NTS is not supported")] [MemberData(nameof(IsAsyncData))] public virtual async Task CurveToLine(bool async) { diff --git a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs index ee30773de2f..bf56dedb515 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs @@ -241,12 +241,12 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas dbType: System.Data.DbType.String), converter: new ValueConverter( (bool v) => (string)(v ? "B" : "A"), - (string v) => !string.IsNullOrEmpty(v) && (int)v.ToUpperInvariant()[0] == (int)'B'), + (string v) => !string.IsNullOrEmpty(v) && (int)v.ToUpperInvariant()[0] == (int)"B".ToUpperInvariant()[0]), jsonValueReaderWriter: new JsonConvertedValueReaderWriter( JsonStringReaderWriter.Instance, new ValueConverter( (bool v) => (string)(v ? "B" : "A"), - (string v) => !string.IsNullOrEmpty(v) && (int)v.ToUpperInvariant()[0] == (int)'B'))); + (string v) => !string.IsNullOrEmpty(v) && (int)v.ToUpperInvariant()[0] == (int)"B".ToUpperInvariant()[0]))); boolToStringConverterProperty.SetSentinelFromProviderValue("A"); boolToStringConverterProperty.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); diff --git a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/ManyTypesEntityType.cs b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/ManyTypesEntityType.cs index ee30773de2f..bf56dedb515 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/ManyTypesEntityType.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/ManyTypesEntityType.cs @@ -241,12 +241,12 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas dbType: System.Data.DbType.String), converter: new ValueConverter( (bool v) => (string)(v ? "B" : "A"), - (string v) => !string.IsNullOrEmpty(v) && (int)v.ToUpperInvariant()[0] == (int)'B'), + (string v) => !string.IsNullOrEmpty(v) && (int)v.ToUpperInvariant()[0] == (int)"B".ToUpperInvariant()[0]), jsonValueReaderWriter: new JsonConvertedValueReaderWriter( JsonStringReaderWriter.Instance, new ValueConverter( (bool v) => (string)(v ? "B" : "A"), - (string v) => !string.IsNullOrEmpty(v) && (int)v.ToUpperInvariant()[0] == (int)'B'))); + (string v) => !string.IsNullOrEmpty(v) && (int)v.ToUpperInvariant()[0] == (int)"B".ToUpperInvariant()[0]))); boolToStringConverterProperty.SetSentinelFromProviderValue("A"); boolToStringConverterProperty.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTestBase.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTestBase.cs index 77d2b88ed5f..2b5d2b0cdd4 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTestBase.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerValueGenerationScenariosTestBase.cs @@ -699,7 +699,7 @@ public async Task Insert_with_non_key_default_value() } } - [ConditionalFact] + [ConditionalFact(Skip = "AOT: NTS is not supported")] [SqlServerCondition(SqlServerCondition.SupportsSqlClr)] public async Task Insert_with_non_key_default_spatial_value() { diff --git a/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs b/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs index 113d9701fac..e0c4e579ce4 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs @@ -237,12 +237,12 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas size: 1), converter: new ValueConverter( (bool v) => (string)(v ? "B" : "A"), - (string v) => !string.IsNullOrEmpty(v) && (int)v.ToUpperInvariant()[0] == (int)'B'), + (string v) => !string.IsNullOrEmpty(v) && (int)v.ToUpperInvariant()[0] == (int)"B".ToUpperInvariant()[0]), jsonValueReaderWriter: new JsonConvertedValueReaderWriter( JsonStringReaderWriter.Instance, new ValueConverter( (bool v) => (string)(v ? "B" : "A"), - (string v) => !string.IsNullOrEmpty(v) && (int)v.ToUpperInvariant()[0] == (int)'B'))); + (string v) => !string.IsNullOrEmpty(v) && (int)v.ToUpperInvariant()[0] == (int)"B".ToUpperInvariant()[0]))); boolToStringConverterProperty.SetSentinelFromProviderValue("A"); var boolToTwoValuesConverterProperty = runtimeEntityType.AddProperty( diff --git a/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/ManyTypesEntityType.cs b/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/ManyTypesEntityType.cs index 113d9701fac..e0c4e579ce4 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/ManyTypesEntityType.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/ManyTypesEntityType.cs @@ -237,12 +237,12 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas size: 1), converter: new ValueConverter( (bool v) => (string)(v ? "B" : "A"), - (string v) => !string.IsNullOrEmpty(v) && (int)v.ToUpperInvariant()[0] == (int)'B'), + (string v) => !string.IsNullOrEmpty(v) && (int)v.ToUpperInvariant()[0] == (int)"B".ToUpperInvariant()[0]), jsonValueReaderWriter: new JsonConvertedValueReaderWriter( JsonStringReaderWriter.Instance, new ValueConverter( (bool v) => (string)(v ? "B" : "A"), - (string v) => !string.IsNullOrEmpty(v) && (int)v.ToUpperInvariant()[0] == (int)'B'))); + (string v) => !string.IsNullOrEmpty(v) && (int)v.ToUpperInvariant()[0] == (int)"B".ToUpperInvariant()[0]))); boolToStringConverterProperty.SetSentinelFromProviderValue("A"); var boolToTwoValuesConverterProperty = runtimeEntityType.AddProperty( diff --git a/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs b/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs index 2993dd43077..251e884d8d5 100644 --- a/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs @@ -554,6 +554,10 @@ public override string FromJsonTyped(ref Utf8JsonReaderManager manager, object e public override void ToJsonTyped(Utf8JsonWriter writer, string value) => writer.WriteStringValue(value); + + private readonly Expression> _instanceLambda = () => new(); + + public override Expression ConstructorExpression => _instanceLambda.Body; } private class JasonValueReaderWriterWithPrivateInstance : JsonValueReaderWriter @@ -565,6 +569,10 @@ public override string FromJsonTyped(ref Utf8JsonReaderManager manager, object e public override void ToJsonTyped(Utf8JsonWriter writer, string value) => writer.WriteStringValue(value); + + private readonly Expression> _instanceLambda = () => Instance; + + public override Expression ConstructorExpression => _instanceLambda.Body; } private class JasonValueReaderWriterWithBadInstance : JsonValueReaderWriter @@ -576,6 +584,8 @@ public override string FromJsonTyped(ref Utf8JsonReaderManager manager, object e public override void ToJsonTyped(Utf8JsonWriter writer, string value) => writer.WriteStringValue(value); + + public override Expression ConstructorExpression => Expression.Default(typeof(JasonValueReaderWriterWithBadInstance)); } private class SimpleJasonValueReaderWriterWithInstance : JsonValueReaderWriter @@ -587,6 +597,10 @@ public override string FromJsonTyped(ref Utf8JsonReaderManager manager, object e public override void ToJsonTyped(Utf8JsonWriter writer, string value) => writer.WriteStringValue(value); + + private readonly Expression> _instanceLambda = () => Instance; + + public override Expression ConstructorExpression => _instanceLambda.Body; } private class SimpleJasonValueReaderWriterWithInstanceAndPrivateConstructor : JsonValueReaderWriter @@ -602,6 +616,10 @@ public override string FromJsonTyped(ref Utf8JsonReaderManager manager, object e public override void ToJsonTyped(Utf8JsonWriter writer, string value) => writer.WriteStringValue(value); + + private readonly Expression> _instanceLambda = () => Instance; + + public override Expression ConstructorExpression => _instanceLambda.Body; } private class NonDerivedJsonValueReaderWriter; @@ -616,6 +634,10 @@ public override void ToJson(Utf8JsonWriter writer, object value) public override Type ValueType => typeof(string); + + private readonly Expression> _instanceLambda = () => new(); + + public override Expression ConstructorExpression => _instanceLambda.Body; } private abstract class AbstractJasonValueReaderWriter : JsonValueReaderWriter; @@ -631,17 +653,24 @@ public override string FromJsonTyped(ref Utf8JsonReaderManager manager, object e public override void ToJsonTyped(Utf8JsonWriter writer, string value) => writer.WriteStringValue(value); + + private readonly Expression> _instanceLambda = () => new(); + + public override Expression ConstructorExpression => _instanceLambda.Body; } -#pragma warning disable CS9113 // Parameter '_' is unread private class NonParameterlessJsonValueReaderWriter(bool _) : JsonValueReaderWriter -#pragma warning restore CS9113 { public override string FromJsonTyped(ref Utf8JsonReaderManager manager, object existingObject = null) => manager.CurrentReader.GetString()!; public override void ToJsonTyped(Utf8JsonWriter writer, string value) => writer.WriteStringValue(value); + + private readonly ConstructorInfo _constructorInfo = typeof(NonParameterlessJsonValueReaderWriter).GetConstructor([typeof(bool)])!; + + public override Expression ConstructorExpression => + Expression.New(_constructorInfo, Expression.Constant(_)); } private static IMutableModel CreateModel() diff --git a/test/EFCore.Tests/Query/EntityMaterializerSourceTest.cs b/test/EFCore.Tests/Query/EntityMaterializerSourceTest.cs index 5214e55cd2f..ca133e44786 100644 --- a/test/EFCore.Tests/Query/EntityMaterializerSourceTest.cs +++ b/test/EFCore.Tests/Query/EntityMaterializerSourceTest.cs @@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Proxies.Internal; using Microsoft.EntityFrameworkCore.Query.Internal; // ReSharper disable UnusedMember.Local @@ -178,7 +179,8 @@ public void Can_create_materializer_for_entity_with_instance_factory_method(bool et.ConstructorBinding = new FactoryMethodBinding( TestProxyFactory.Instance, - typeof(TestProxyFactory).GetTypeInfo().GetDeclaredMethod(nameof(TestProxyFactory.Create))!, + Expression.Constant(TestProxyFactory.Instance), + typeof(TestProxyFactory).GetMethod(nameof(TestProxyFactory.Create), [typeof(IEntityType)])!, new List { new EntityTypeParameterBinding() }, et.ClrType); }); diff --git a/test/EFCore.Tests/Query/Internal/NavigationExpandingExpressionVisitorTests.cs b/test/EFCore.Tests/Query/Internal/NavigationExpandingExpressionVisitorTests.cs index 7d3492fdecc..aa8517389b9 100644 --- a/test/EFCore.Tests/Query/Internal/NavigationExpandingExpressionVisitorTests.cs +++ b/test/EFCore.Tests/Query/Internal/NavigationExpandingExpressionVisitorTests.cs @@ -27,6 +27,8 @@ public TestNavigationExpandingExpressionVisitor() null, null, null, + null, + null, new ExecutionStrategyTest.TestExecutionStrategy(new MyDemoContext()), new CurrentDbContext(new MyDemoContext()), null,