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.Analyzers/EFDiagnostics.cs b/src/EFCore.Analyzers/EFDiagnostics.cs new file mode 100644 index 00000000000..49b461169f6 --- /dev/null +++ b/src/EFCore.Analyzers/EFDiagnostics.cs @@ -0,0 +1,14 @@ +// 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; + +/// +/// Contains IDs of diagnostics created by EF analyzers and other mechanisms. +/// +public static class EFDiagnostics +{ + public const string InternalUsage = "EF1001"; + public const string SuppressUninitializedDbSetRule = "EFSPR1001"; + public const string PrecompiledQueryExperimental = "EF2001"; +} diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs index 322ab4e6fcb..c7c6b3c413d 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs @@ -341,6 +341,10 @@ static bool IsTypeConstant(Expression expression, out Type? type) } } + //private static readonly MethodInfo PropertyGetValueConverterMethod + // = typeof(IReadOnlyProperty).GetMethod(nameof(IReadOnlyProperty.GetValueComparer))!; + // //= typeof(IProperty).GetMethod(nameof(IProperty.GetValueComparer))!; + private static bool TryUseComparer( Expression? newLeft, Expression? newRight, diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs index 5d833b29426..68495f09d99 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; @@ -896,6 +895,9 @@ private static Expression GetGroupingKey(Expression key, List groupi } } + //private static readonly MethodInfo PropertyGetValueConverterMethod + // = typeof(IReadOnlyProperty).GetMethod(nameof(IReadOnlyProperty.GetValueComparer))!; + private Expression AddJoin( InMemoryQueryExpression innerQueryExpression, LambdaExpression? outerKeySelector, 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/EFCore.Relational.csproj b/src/EFCore.Relational/EFCore.Relational.csproj index 77feb85648d..aa4bfcfd424 100644 --- a/src/EFCore.Relational/EFCore.Relational.csproj +++ b/src/EFCore.Relational/EFCore.Relational.csproj @@ -8,7 +8,7 @@ Microsoft.EntityFrameworkCore true true - $(NoWarn);EF1003 + $(NoWarn);EF2001 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 class EntityFrameworkRelationalServicesBuilder : EntityFrameworkServicesB { 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..c2a7c1e86ff --- /dev/null +++ b/src/EFCore.Relational/Query/IRelationalLiftableConstantFactory.cs @@ -0,0 +1,19 @@ +// 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; + +/// +/// TODO +/// +public interface IRelationalLiftableConstantFactory : ILiftableConstantFactory +{ + /// + /// TODO + /// + LiftableConstantExpression CreateLiftableConstant( + ConstantExpression originalExpression, + Expression> resolverExpression, + string variableName, + Type type); +} diff --git a/src/EFCore.Relational/Query/IRelationalQuotableExpression.cs b/src/EFCore.Relational/Query/IRelationalQuotableExpression.cs index f8e59c412c8..40319ba777e 100644 --- a/src/EFCore.Relational/Query/IRelationalQuotableExpression.cs +++ b/src/EFCore.Relational/Query/IRelationalQuotableExpression.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Query; /// Represents an expression that is quotable, that is, capable of returning an expression that, when evaluated, would construct an /// expression identical to this one. Used to generate code for precompiled queries, which reconstructs this expression. /// -[Experimental("EF1003")] +[Experimental(EFDiagnostics.PrecompiledQueryExperimental)] public interface IRelationalQuotableExpression { /// diff --git a/src/EFCore.Relational/Query/Internal/GroupBySingleQueryingEnumerable.cs b/src/EFCore.Relational/Query/Internal/GroupBySingleQueryingEnumerable.cs index b53b4dc5d7c..92d1cb03cfb 100644 --- a/src/EFCore.Relational/Query/Internal/GroupBySingleQueryingEnumerable.cs +++ b/src/EFCore.Relational/Query/Internal/GroupBySingleQueryingEnumerable.cs @@ -20,7 +20,8 @@ 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 IReadOnlyList> _keyIdentifierValueComparers; private readonly Func _elementSelector; private readonly Type _contextType; private readonly IDiagnosticsLogger _queryLogger; @@ -40,7 +41,8 @@ public class GroupBySingleQueryingEnumerable IReadOnlyList? readerColumns, Func keySelector, Func keyIdentifier, - IReadOnlyList keyIdentifierValueComparers, + //IReadOnlyList keyIdentifierValueComparers, + IReadOnlyList> keyIdentifierValueComparers, Func elementSelector, Type contextType, bool standAloneStateManager, @@ -139,12 +141,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 +162,8 @@ 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 IReadOnlyList> _keyIdentifierValueComparers; private readonly Func _elementSelector; private readonly Type _contextType; private readonly IDiagnosticsLogger _queryLogger; @@ -344,7 +347,8 @@ private sealed class AsyncEnumerator : IAsyncEnumerator? _readerColumns; private readonly Func _keySelector; private readonly Func _keyIdentifier; - private readonly IReadOnlyList _keyIdentifierValueComparers; + //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..f614dbf0931 100644 --- a/src/EFCore.Relational/Query/Internal/GroupBySplitQueryingEnumerable.cs +++ b/src/EFCore.Relational/Query/Internal/GroupBySplitQueryingEnumerable.cs @@ -20,7 +20,8 @@ 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 IReadOnlyList> _keyIdentifierValueComparers; private readonly Func _elementSelector; private readonly Action? _relatedDataLoaders; private readonly Func? _relatedDataLoadersAsync; @@ -42,7 +43,8 @@ public class GroupBySplitQueryingEnumerable IReadOnlyList? readerColumns, Func keySelector, Func keyIdentifier, - IReadOnlyList keyIdentifierValueComparers, + //IReadOnlyList keyIdentifierValueComparers, + IReadOnlyList> keyIdentifierValueComparers, Func elementSelector, Action? relatedDataLoaders, Func? relatedDataLoadersAsync, @@ -145,12 +147,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 +168,8 @@ 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 IReadOnlyList> _keyIdentifierValueComparers; private readonly Func _elementSelector; private readonly Action? _relatedDataLoaders; private readonly Type _contextType; @@ -340,7 +343,8 @@ private sealed class AsyncEnumerator : IAsyncEnumerator? _readerColumns; private readonly Func _keySelector; private readonly Func _keyIdentifier; - private readonly IReadOnlyList _keyIdentifierValueComparers; + //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..24a040293ee 100644 --- a/src/EFCore.Relational/Query/Internal/SingleQueryingEnumerable.cs +++ b/src/EFCore.Relational/Query/Internal/SingleQueryingEnumerable.cs @@ -6,6 +6,37 @@ 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 +{ + /// + /// TODO + /// + 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/RelationalExpressionQuotingUtilities.cs b/src/EFCore.Relational/Query/RelationalExpressionQuotingUtilities.cs index 699ad327ab3..612bf93928e 100644 --- a/src/EFCore.Relational/Query/RelationalExpressionQuotingUtilities.cs +++ b/src/EFCore.Relational/Query/RelationalExpressionQuotingUtilities.cs @@ -11,7 +11,7 @@ namespace Microsoft.EntityFrameworkCore.Query; /// /// Utilities used for implementing . /// -[Experimental("EF1003")] +[Experimental(EFDiagnostics.PrecompiledQueryExperimental)] public static class RelationalExpressionQuotingUtilities { private static readonly ParameterExpression RelationalModelParameter diff --git a/src/EFCore.Relational/Query/RelationalLiftableConstantFactory.cs b/src/EFCore.Relational/Query/RelationalLiftableConstantFactory.cs new file mode 100644 index 00000000000..bab8e3d63d6 --- /dev/null +++ b/src/EFCore.Relational/Query/RelationalLiftableConstantFactory.cs @@ -0,0 +1,40 @@ +// 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 RelationalLiftableConstantFactory : LiftableConstantFactory, IRelationalLiftableConstantFactory +{ + /// + /// TODO + /// + 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; + } + + /// + /// TODO + /// + public virtual RelationalLiftableConstantExpressionDependencies RelationalDependencies { get; } + + /// + /// TODO + /// + 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..b8fab3919bf --- /dev/null +++ b/src/EFCore.Relational/Query/RelationalLiftableConstantProcessor.cs @@ -0,0 +1,39 @@ +// 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; + +#pragma warning disable EF1001 // LiftableConstantProcessor is internal + +/// +/// TODO +/// +public class RelationalLiftableConstantProcessor : LiftableConstantProcessor +{ + private readonly RelationalMaterializerLiftableConstantContext _relationalMaterializerLiftableConstantContext; + + /// + /// TODO + /// + 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..815578c836a --- /dev/null +++ b/src/EFCore.Relational/Query/RelationalMaterializerLiftableConstantContext.cs @@ -0,0 +1,12 @@ +// 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; + +/// +/// TODO +/// +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 d4bdfe0bd94..e0e3628ce8f 100644 --- a/src/EFCore.Relational/Query/RelationalQueryCompilationContext.cs +++ b/src/EFCore.Relational/Query/RelationalQueryCompilationContext.cs @@ -47,4 +47,7 @@ public class RelationalQueryCompilationContext : QueryCompilationContext /// 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..b7254223fd6 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 sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisit 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 TValue ThrowExtractJsonPropertyException(Exception except } } - 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 TValue ThrowExtractJsonPropertyException(Exception except 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,12 @@ private static TValue ThrowExtractJsonPropertyException(Exception except Func parentIdentifier, Func outerIdentifier, Func selfIdentifier, - IReadOnlyList parentIdentifierValueComparers, - IReadOnlyList outerIdentifierValueComparers, - IReadOnlyList selfIdentifierValueComparers, + // IReadOnlyList parentIdentifierValueComparers, + // IReadOnlyList outerIdentifierValueComparers, + // IReadOnlyList selfIdentifierValueComparers, + IReadOnlyList> parentIdentifierValueComparers, + IReadOnlyList> outerIdentifierValueComparers, + IReadOnlyList> selfIdentifierValueComparers, Func innerShaper, INavigationBase? inverseNavigation, Action fixup, @@ -319,7 +358,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 +404,14 @@ void GenerateCurrentElementIfPending() 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 +420,8 @@ void GenerateCurrentElementIfPending() bool detailedErrorsEnabled, SplitQueryResultCoordinator resultCoordinator, Func childIdentifier, - IReadOnlyList identifierValueComparers, + // IReadOnlyList identifierValueComparers, + IReadOnlyList> identifierValueComparers, Func innerShaper, Action? relatedDataLoaders, INavigationBase? inverseNavigation, @@ -442,7 +496,14 @@ void GenerateCurrentElementIfPending() } } - 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 +512,8 @@ void GenerateCurrentElementIfPending() bool detailedErrorsEnabled, SplitQueryResultCoordinator resultCoordinator, Func childIdentifier, - IReadOnlyList identifierValueComparers, + // IReadOnlyList identifierValueComparers, + IReadOnlyList> identifierValueComparers, Func innerShaper, Func? relatedDataLoaders, INavigationBase? inverseNavigation, @@ -538,7 +600,14 @@ void GenerateCurrentElementIfPending() } } - 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 +629,14 @@ void GenerateCurrentElementIfPending() 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 +644,12 @@ void GenerateCurrentElementIfPending() Func parentIdentifier, Func outerIdentifier, Func selfIdentifier, - IReadOnlyList parentIdentifierValueComparers, - IReadOnlyList outerIdentifierValueComparers, - IReadOnlyList selfIdentifierValueComparers, + //IReadOnlyList parentIdentifierValueComparers, + //IReadOnlyList outerIdentifierValueComparers, + //IReadOnlyList selfIdentifierValueComparers, + IReadOnlyList> parentIdentifierValueComparers, + IReadOnlyList> outerIdentifierValueComparers, + IReadOnlyList> selfIdentifierValueComparers, Func innerShaper) where TRelatedEntity : TElement where TCollection : class, ICollection @@ -590,8 +669,8 @@ void GenerateCurrentElementIfPending() } 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 +695,8 @@ void GenerateCurrentElementIfPending() 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 +752,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 +777,14 @@ void GenerateCurrentElementIfPending() 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 +793,8 @@ void GenerateCurrentElementIfPending() bool detailedErrorsEnabled, SplitQueryResultCoordinator resultCoordinator, Func childIdentifier, - IReadOnlyList identifierValueComparers, + //IReadOnlyList identifierValueComparers, + IReadOnlyList> identifierValueComparers, Func innerShaper, Action? relatedDataLoaders) where TRelatedEntity : TElement @@ -770,7 +864,14 @@ void GenerateCurrentElementIfPending() 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 +880,8 @@ void GenerateCurrentElementIfPending() bool detailedErrorsEnabled, SplitQueryResultCoordinator resultCoordinator, Func childIdentifier, - IReadOnlyList identifierValueComparers, + //IReadOnlyList identifierValueComparers, + IReadOnlyList> identifierValueComparers, Func innerShaper, Func? relatedDataLoaders) where TRelatedEntity : TElement @@ -861,7 +963,14 @@ void GenerateCurrentElementIfPending() 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 +1009,14 @@ void GenerateCurrentElementIfPending() 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 +1083,14 @@ void GenerateCurrentElementIfPending() 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 +1114,14 @@ void GenerateCurrentElementIfPending() } } - private static void IncludeJsonEntityCollection( + /// + /// 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 +1188,31 @@ void GenerateCurrentElementIfPending() manager.CaptureState(); } - private static async Task TaskAwaiter(Func[] 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 +1220,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..bb65159a64e 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,9 @@ private sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisit private static readonly MethodInfo IsDbNullMethod = typeof(DbDataReader).GetRuntimeMethod(nameof(DbDataReader.IsDBNull), [typeof(int)])!; + /// + /// TODO + /// public static readonly MethodInfo GetFieldValueMethod = typeof(DbDataReader).GetRuntimeMethod(nameof(DbDataReader.GetFieldValue), [typeof(int)])!; @@ -77,6 +88,9 @@ private sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisit 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 RelationalShapedQueryCompilingExpressionVisitor _parentVisitor; private readonly ISet? _tags; private readonly bool _isTracking; @@ -160,6 +174,12 @@ private sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisit /// private readonly Dictionary _jsonArrayNonConstantElementAccessMap = 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 +192,6 @@ private sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisit _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 +266,17 @@ private sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisit _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 IReadOnlyList? readerColumns, out LambdaExpression keySelector, out LambdaExpression keyIdentifier, out LambdaExpression? relatedDataLoaders, @@ -277,10 +303,17 @@ private sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisit 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 IReadOnlyList? readerColumns, out LambdaExpression? relatedDataLoaders, ref int collectionId) { @@ -293,13 +326,8 @@ private sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisit _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 +348,9 @@ private sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisit 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 +431,9 @@ private sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisit } 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 +446,12 @@ private sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisit } } + /// + /// 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 +476,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 +499,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 +541,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 +647,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 +768,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 +819,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 +849,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 +929,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 +956,68 @@ 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; + var inverseNavigationDeclaringEntityTypeName = inverseNavigation?.DeclaringEntityType.Name; + var inverseNavigationName = inverseNavigation?.Name; + _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 +1069,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 +1141,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 +1168,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 +1229,7 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) var collectionParameter = Parameter(collectionType); _variables.Add(collectionParameter); + _expressions.Add( Assign( collectionParameter, @@ -1130,8 +1239,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 +1264,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 +1287,9 @@ when GetProjectionIndex(collectionResultExpression.ProjectionBindingExpression) case GroupByShaperExpression: throw new InvalidOperationException(RelationalStrings.ClientGroupByNotSupported); + + case LiftableConstantExpression: + return extensionExpression; } return base.VisitExtension(extensionExpression); @@ -1190,6 +1310,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 +1455,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp 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 +1522,9 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp innerShapersMap, innerFixupMap, trackingInnerFixupMap, - _queryLogger).Rewrite(entityShaperMaterializer); + _queryLogger, + _parentVisitor.Dependencies.LiftableConstantFactory, + _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery).Rewrite(entityShaperMaterializer); var entityShaperMaterializerVariable = Variable( entityShaperMaterializer.Type, @@ -1494,7 +1622,13 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp 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 +1654,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 +1671,9 @@ private sealed class JsonEntityMaterializerRewriter : ExpressionVisitor IDictionary innerShapersMap, IDictionary innerFixupMap, IDictionary trackingInnerFixupMap, - IDiagnosticsLogger queryLogger) + IDiagnosticsLogger queryLogger, + ILiftableConstantFactory liftableConstantFactory, + bool supportsPrecompiledQuery) { _entityType = entityType; _isTracking = isTracking; @@ -1544,6 +1682,8 @@ private sealed class JsonEntityMaterializerRewriter : ExpressionVisitor _innerFixupMap = innerFixupMap; _trackingInnerFixupMap = trackingInnerFixupMap; _queryLogger = queryLogger; + _liftableConstantFactory = liftableConstantFactory; + _supportsPrecompiledQuery = supportsPrecompiledQuery; } public BlockExpression Rewrite(BlockExpression jsonEntityShaperMaterializer) @@ -1552,12 +1692,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 +1786,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 +1904,8 @@ void ProcessFixup(IDictionary fixupMap) foreach (var valueBufferTryReadValueMethodToProcess in valueBufferTryReadValueMethodsToProcess) { - var property = (IProperty)((ConstantExpression)valueBufferTryReadValueMethodToProcess.Arguments[2]).Value!; - + var property = valueBufferTryReadValueMethodToProcess.Arguments[2].GetConstantValue(); + var encodedPropertyName = JsonEncodedText.Encode(property.GetJsonPropertyName()!); testExpressions.Add( Call( Field( @@ -1768,7 +1913,13 @@ void ProcessFixup(IDictionary fixupMap) Utf8JsonReaderManagerCurrentReaderField), Utf8JsonReaderValueTextEqualsMethod, Property( - Constant(JsonEncodedText.Encode(property.GetJsonPropertyName()!)), + _supportsPrecompiledQuery + ? _liftableConstantFactory.CreateLiftableConstant( + Constant(encodedPropertyName), + _ => encodedPropertyName, + property.Name + "EncodedProperty", + typeof(JsonEncodedText)) + : Constant(encodedPropertyName), JsonEncodedTextEncodedUtf8BytesProperty))); var propertyVariable = Variable(valueBufferTryReadValueMethodToProcess.Type); @@ -1794,6 +1945,8 @@ void ProcessFixup(IDictionary fixupMap) foreach (var innerShaperMapElement in _innerShapersMap) { + var innerShaperMapElementKey = innerShaperMapElement.Key; + var encodedNavigationName = JsonEncodedText.Encode(innerShaperMapElement.Key); testExpressions.Add( Call( Field( @@ -1801,7 +1954,13 @@ void ProcessFixup(IDictionary fixupMap) Utf8JsonReaderManagerCurrentReaderField), Utf8JsonReaderValueTextEqualsMethod, Property( - Constant(JsonEncodedText.Encode(innerShaperMapElement.Key)), + _supportsPrecompiledQuery + ? _liftableConstantFactory.CreateLiftableConstant( + Constant(encodedNavigationName), + _ => encodedNavigationName, + innerShaperMapElementKey + "EncodedNavigation", + typeof(JsonEncodedText)) + : Constant(encodedNavigationName), JsonEncodedTextEncodedUtf8BytesProperty))); var propertyVariable = Variable(innerShaperMapElement.Value.Type); @@ -1813,7 +1972,18 @@ 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, 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 +2015,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 +2214,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 +2297,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp 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 +2356,17 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp 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 +2500,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 +2523,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 +2537,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp } } - private static LambdaExpression GenerateFixup( + private LambdaExpression GenerateFixup( Type entityType, Type relatedEntityType, INavigationBase navigation, @@ -2401,7 +2599,14 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp 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 +2623,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp 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 +2633,31 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp 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 +2725,7 @@ Expression valueExpression Lambda( bufferedReaderLambdaExpression, dbDataReader, - _indexMapParameter ?? Parameter(typeof(int[]))).Compile()); + _indexMapParameter ?? Parameter(typeof(int[]), "indexMap"))); } valueExpression = Call( @@ -2579,14 +2796,52 @@ 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 = new DisplayClassConstantFixer().Visit(valueExpression); + return valueExpression; } + 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) @@ -2594,8 +2849,23 @@ Expression valueExpression var nullable = property.IsNullable; var typeMapping = property.GetTypeMapping(); - var jsonReaderWriterExpression = Constant( - property.GetJsonValueReaderWriter() ?? property.GetTypeMapping().JsonValueReaderWriter!); + var jsonReaderWriter = property.GetJsonValueReaderWriter() ?? property.GetTypeMapping().JsonValueReaderWriter!; + + //var jsonReaderWriterExpression = Constant( + // property.GetJsonValueReaderWriter() ?? property.GetTypeMapping().JsonValueReaderWriter!); + + // complex types are not supported in JSON for now, so property is guaranteed to be on an entity type + var declaringEntityTypeName = property.DeclaringType.Name; + var propertyName = property.Name; + + var jsonReaderWriterExpression = _parentVisitor.QueryCompilationContext.SupportsPrecompiledQuery + ? _parentVisitor.Dependencies.LiftableConstantFactory.CreateLiftableConstant( + Constant(property.GetJsonValueReaderWriter() ?? property.GetTypeMapping().JsonValueReaderWriter!), + c => c.Dependencies.Model.FindEntityType(declaringEntityTypeName)!.FindProperty(propertyName)!.GetJsonValueReaderWriter() + ?? c.Dependencies.Model.FindEntityType(declaringEntityTypeName)!.FindProperty(propertyName)!.GetTypeMapping().JsonValueReaderWriter!, + propertyName + "PropertyName", + jsonReaderWriter.GetType()) + : (Expression)Constant(property.GetJsonValueReaderWriter() ?? property.GetTypeMapping().JsonValueReaderWriter!); var fromJsonMethod = jsonReaderWriterExpression.Type.GetMethod( nameof(JsonValueReaderWriter.FromJsonTyped), @@ -2642,6 +2912,73 @@ Expression valueExpression return resultExpression; } + private static readonly MethodInfo ModelFindEntiyTypeMethod = + typeof(IModel).GetRuntimeMethod(nameof(IModel.FindEntityType), [typeof(string)])!; + + 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)])!; + + // 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..fe59c7272af 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs @@ -70,17 +70,19 @@ protected virtual Expression VisitNonQuery(NonQueryExpression nonQueryExpression break; } - var relationalCommandCache = new RelationalCommandCache( - Dependencies.MemoryCache, - RelationalDependencies.QuerySqlGeneratorFactory, - RelationalDependencies.RelationalParameterBasedSqlProcessorFactory, - innerExpression, - _useRelationalNulls); + // 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 +98,14 @@ protected virtual Expression VisitNonQuery(NonQueryExpression nonQueryExpression .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 +176,14 @@ protected virtual Expression VisitNonQuery(NonQueryExpression nonQueryExpression } } - 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 +286,41 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery if (splitQuery) { - var relatedDataLoadersParameter = Constant( - QueryCompilationContext.IsAsync ? null : relatedDataLoaders?.Compile(), - typeof(Action)); - - var relatedDataLoadersAsyncParameter = Constant( - QueryCompilationContext.IsAsync ? relatedDataLoaders?.Compile() : null, - typeof(Func)); + //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()), + //Constant(relationalCommandCache), + relationalCommandCache, + //Constant(readerColumns, typeof(IReadOnlyList)), + readerColumns(), + //Constant(keySelector.Compile()), + //Constant(keyIdentifier.Compile()), + keySelector, + keyIdentifier, + //Constant(relationalGroupByResultExpression.KeyIdentifierValueComparers, typeof(IReadOnlyList)), + NewArrayInit(typeof(Func), relationalGroupByResultExpression.KeyIdentifierValueComparers.Select(vc => vc.ObjectEqualsExpression)), + //Constant(elementSelector.Compile()), + elementSelector, relatedDataLoadersParameter, relatedDataLoadersAsyncParameter, Constant(_contextType), - Constant( - QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution), + Constant(QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution), Constant(_detailedErrorsEnabled), Constant(_threadSafetyChecksEnabled)); } @@ -303,24 +330,35 @@ 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()), + //Constant(relationalCommandCache), + relationalCommandCache, + //Constant(readerColumns, typeof(IReadOnlyList)), + readerColumns(), + //Constant(keySelector.Compile()), + //Constant(keyIdentifier.Compile()), + keySelector, + keyIdentifier, + //Constant(relationalGroupByResultExpression.KeyIdentifierValueComparers, typeof(IReadOnlyList)), + NewArrayInit(typeof(Func), relationalGroupByResultExpression.KeyIdentifierValueComparers.Select(vc => vc.ObjectEqualsExpression)), + + //Constant(elementSelector.Compile()), + elementSelector, Constant(_contextType), - Constant( - QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution), + Constant(QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution), Constant(_detailedErrorsEnabled), Constant(_threadSafetyChecksEnabled)); } else { 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); + var shaper = new ShaperProcessingExpressionVisitor(this, selectExpression, _tags, splitQuery, nonComposedFromSql) + .ProcessShaper( + shapedQueryExpression.ShaperExpression, out var relationalCommandCache, out var readerColumns, + out var relatedDataLoaders, ref collectionCount); + + + var output = ExpressionPrinter.Print(shaper); + if (querySplittingBehavior == null && collectionCount > 1) @@ -333,12 +371,14 @@ 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(), + //Constant( + // selectExpression.Projection.Select(pe => ((ColumnExpression)pe.Expression).Name).ToList(), + // typeof(IReadOnlyList)), + NewArrayInit(typeof(string), selectExpression.Projection.Select(pe => Constant(((ColumnExpression)pe.Expression).Name, typeof(string)))), + //Constant(shaper.Compile()), + shaper, Constant(_contextType), Constant( QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution), @@ -348,20 +388,30 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery if (splitQuery) { - var relatedDataLoadersParameter = Constant( - QueryCompilationContext.IsAsync ? null : relatedDataLoaders?.Compile(), - typeof(Action)); + var relatedDataLoadersParameter = + QueryCompilationContext.IsAsync || relatedDataLoaders is null + ? Expression.Constant(null, typeof(Action)) + : (Expression)relatedDataLoaders; + + // var relatedDataLoadersParameter = Constant( + // QueryCompilationContext.IsAsync ? null : relatedDataLoaders?.Compile(), + // typeof(Action)); + + var relatedDataLoadersAsyncParameter = + QueryCompilationContext.IsAsync && relatedDataLoaders is not null + ? (Expression)relatedDataLoaders + : Expression.Constant(null, typeof(Func)); - var relatedDataLoadersAsyncParameter = Constant( - QueryCompilationContext.IsAsync ? relatedDataLoaders?.Compile() : null, - typeof(Func)); + // var relatedDataLoadersAsyncParameter = Constant( + // QueryCompilationContext.IsAsync ? relatedDataLoaders?.Compile() : 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), @@ -371,12 +421,15 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery 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), @@ -384,4 +437,28 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery Constant(_threadSafetyChecksEnabled)); } } + + private Expression CreateRelationalCommandCacheExpression(Expression queryExpression) + //private LiftableConstantExpression 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 sealed record 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..40c78919a79 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -2048,7 +2048,7 @@ private Expression CreatePropertyAccessExpression(Expression target, IProperty p 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,13 @@ MemberInitExpression memberInitExpression _ => 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 +2137,13 @@ MemberInitExpression memberInitExpression 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 @@ public void ApplyUnion(SelectExpression source2, bool distinct) 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 @@ protected ReaderColumn(Type type, bool nullable, string? name, IPropertyBase? pr 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/EFCore.SqlServer.csproj b/src/EFCore.SqlServer/EFCore.SqlServer.csproj index 9e23cff53e2..5f468dc12f4 100644 --- a/src/EFCore.SqlServer/EFCore.SqlServer.csproj +++ b/src/EFCore.SqlServer/EFCore.SqlServer.csproj @@ -9,7 +9,7 @@ true $(PackageTags);SQL Server true - EF1003 + EF2001 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 @@ methodType switch } } - 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 @@ methodType switch _ => 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/EFCore.Sqlite.Core.csproj b/src/EFCore.Sqlite.Core/EFCore.Sqlite.Core.csproj index 4b82a88a403..bd07aa6992a 100644 --- a/src/EFCore.Sqlite.Core/EFCore.Sqlite.Core.csproj +++ b/src/EFCore.Sqlite.Core/EFCore.Sqlite.Core.csproj @@ -10,7 +10,7 @@ true $(PackageTags);SQLite true - EF1003 + EF2001 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..2199a8fadaa 100644 --- a/src/EFCore/ChangeTracking/ValueComparer.cs +++ b/src/EFCore/ChangeTracking/ValueComparer.cs @@ -155,6 +155,11 @@ static ValueComparer() /// public virtual LambdaExpression EqualsExpression { get; } + /// + /// TODO + /// + 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..e3737dee50c 100644 --- a/src/EFCore/ChangeTracking/ValueComparer`.cs +++ b/src/EFCore/ChangeTracking/ValueComparer`.cs @@ -248,6 +248,32 @@ public override bool Equals(object? left, object? right) return v1Null || v2Null ? v1Null && v2Null : Equals((T?)left, (T?)right); } + /// + /// TODO + /// + public override LambdaExpression ObjectEqualsExpression + { + get + { + // TODO: Cache this + var left = Expression.Parameter(typeof(object), "left"); + var right = Expression.Parameter(typeof(object), "right"); + + return 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); + } + } + /// /// Returns the hash code for the given instance. /// diff --git a/src/EFCore/EFCore.csproj b/src/EFCore/EFCore.csproj index 77b927ca73d..585e3bea94f 100644 --- a/src/EFCore/EFCore.csproj +++ b/src/EFCore/EFCore.csproj @@ -13,6 +13,7 @@ Microsoft.EntityFrameworkCore.DbSet Microsoft.EntityFrameworkCore true true + $(NoWarn);EF2001 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 class EntityFrameworkServicesBuilder { 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 class EntityFrameworkServicesBuilder { 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..4bc09fbc477 100644 --- a/src/EFCore/Infrastructure/Internal/LazyLoader.cs +++ b/src/EFCore/Infrastructure/Internal/LazyLoader.cs @@ -43,10 +43,17 @@ public class LazyLoader : ILazyLoader, 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. /// - public virtual void Injected(DbContext context, object entity, ParameterBindingInfo bindingInfo) + //public virtual void Injected(DbContext context, object entity, ParameterBindingInfo bindingInfo) + //{ + // _queryTrackingBehavior = bindingInfo.QueryTrackingBehavior; + // _nonLazyNavigations ??= InitNavigationsMetadata(bindingInfo.StructuralType as IEntityType + // ?? throw new NotImplementedException("Navigations on complex types are not supported")); + //} + + 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..84e89fa464d 100644 --- a/src/EFCore/Internal/IInjectableService.cs +++ b/src/EFCore/Internal/IInjectableService.cs @@ -22,7 +22,8 @@ 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, 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..a21717bf4bf 100644 --- a/src/EFCore/Metadata/ContextParameterBinding.cs +++ b/src/EFCore/Metadata/ContextParameterBinding.cs @@ -48,6 +48,22 @@ var propertyExpression : propertyExpression; } + /// + public override Expression BindToParameter2(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..d2b1a095842 100644 --- a/src/EFCore/Metadata/DependencyInjectionParameterBinding.cs +++ b/src/EFCore/Metadata/DependencyInjectionParameterBinding.cs @@ -56,6 +56,21 @@ public class DependencyInjectionParameterBinding : ServiceParameterBinding typeof(IInfrastructure))); } + /// + public override Expression BindToParameter2(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..bd4f3175005 100644 --- a/src/EFCore/Metadata/EntityTypeParameterBinding.cs +++ b/src/EFCore/Metadata/EntityTypeParameterBinding.cs @@ -41,6 +41,23 @@ public EntityTypeParameterBinding(params IPropertyBase[]? serviceProperties) : result; } + /// + public override Expression BindToParameter2(Expression materializationExpression, ParameterBindingInfo bindingInfo) + { + //throw new InvalidOperationException("no idea what to do here"); + + var bindingInfoExpression = (Expression)Expression.Constant(bindingInfo); + + var result = bindingInfoExpression.Type == typeof(IEntityType) || bindingInfoExpression.Type == typeof(IComplexType) + ? bindingInfoExpression + //: Expression.Property(bindingInfoExpression, nameof(ParameterBindingInfo.StructuralType)); + : 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..0382cbefc0d 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; /// @@ -37,11 +38,13 @@ public class FactoryMethodBinding : InstantiationBinding /// 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) @@ -50,6 +53,7 @@ public class FactoryMethodBinding : InstantiationBinding Check.NotNull(factoryInstance, nameof(factoryInstance)); _factoryInstance = factoryInstance; + _factoryInstanceExpression = factoryInstanceExpression; } /// @@ -67,7 +71,8 @@ Expression expression _factoryMethod, arguments) : Expression.Call( - Expression.Constant(_factoryInstance), + //Expression.Constant(_factoryInstance), + _factoryInstanceExpression, _factoryMethod, arguments); @@ -92,5 +97,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..37a7244a4b2 100644 --- a/src/EFCore/Metadata/ServiceParameterBinding.cs +++ b/src/EFCore/Metadata/ServiceParameterBinding.cs @@ -54,9 +54,13 @@ public override Expression BindToParameter(ParameterBindingInfo bindingInfo) return serviceInstance; } - return BindToParameter( + //return BindToParameter( + // bindingInfo.MaterializationContextExpression, + // Expression.Constant(bindingInfo)); + + return BindToParameter2( bindingInfo.MaterializationContextExpression, - Expression.Constant(bindingInfo)); + bindingInfo); } /// @@ -70,6 +74,18 @@ public override Expression BindToParameter(ParameterBindingInfo bindingInfo) 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 binding information. + /// The expression tree. + public abstract Expression BindToParameter2( + 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..2fe71cb2c91 --- /dev/null +++ b/src/EFCore/Query/ILiftableConstantFactory.cs @@ -0,0 +1,19 @@ +// 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; + +/// +/// TODO +/// +public interface ILiftableConstantFactory +{ + /// + /// TODO + /// + 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..c998d9bd3d7 --- /dev/null +++ b/src/EFCore/Query/ILiftableConstantProcessor.cs @@ -0,0 +1,48 @@ +// 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; + +/// +/// TODO +/// +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..fe355861ffb 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 EntityMaterializerSource(EntityMaterializerSourceDependencies dependencie } 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 EntityMaterializerSource(EntityMaterializerSourceDependencies dependencie properties, _materializationInterceptor, bindingInfo, + bindingInfoExpression, constructorExpression, instanceVariable, blockExpressions); @@ -239,7 +259,12 @@ public static IList PopulateList(IEnumerable buffer, IList target) InjectableServiceInjectedMethod, getContext, instanceVariable, - Expression.Constant(bindingInfo, typeof(ParameterBindingInfo))))); + Expression.Constant(bindingInfo.QueryTrackingBehavior, typeof(QueryTrackingBehavior?)), + Expression.Constant(bindingInfo.StructuralType) + + //Expression.Constant(bindingInfo, typeof(ParameterBindingInfo)) + ////bindingInfoExpression + ))); } } @@ -308,6 +333,7 @@ public static IList PopulateList(IEnumerable buffer, IList target) HashSet properties, IMaterializationInterceptor materializationInterceptor, ParameterBindingInfo bindingInfo, + Expression bindingInfoExpression, Expression constructorExpression, ParameterExpression instanceVariable, List blockExpressions) @@ -337,7 +363,7 @@ public static IList PopulateList(IEnumerable buffer, IList target) blockExpressions.Add( Expression.Assign( accessorDictionaryVariable, - CreateAccessorDictionaryExpression())); + CreateAccessorDictionaryExpression(structuralType, bindingInfo))); blockExpressions.Add( Expression.Assign( materializationDataVariable, @@ -409,7 +435,7 @@ public static IList PopulateList(IEnumerable buffer, IList target) 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 +560,16 @@ var materializationContextParameter var bindingInfo = new ParameterBindingInfo( new EntityMaterializerSourceParameters(entityType, "instance", null), materializationContextExpression); + var bindingInfoExpression = Expression.New( + ParameterBindingInfoCtor, + Expression.New( + EntityMaterializerSourceParametersCtor, + Expression.Constant(entityType), + Expression.Constant("intance"), + 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 +596,7 @@ var materializationContextParameter [], _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 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp 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..6c88613c491 --- /dev/null +++ b/src/EFCore/Query/LiftableConstantExpression.cs @@ -0,0 +1,93 @@ +// 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; + +/// +/// 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}")] +public class LiftableConstantExpression : Expression, IPrintableExpression +{ + /// + /// TODO + /// + public LiftableConstantExpression( + ConstantExpression originalExpression, + LambdaExpression resolverExpression, + string variableName, + Type type) + { + OriginalExpression = originalExpression; + ResolverExpression = resolverExpression; + VariableName = char.ToLower(variableName[0]) + variableName[1..]; + Type = type; + } + + /// + /// TODO + /// + public virtual ConstantExpression OriginalExpression { get; } + + /// + /// TODO + /// + public virtual LambdaExpression ResolverExpression { get; } + + /// + /// TODO + /// + public virtual string VariableName { get; } + + /// + /// TODO + /// + public override Type Type { get; } + + /// + /// TODO + /// + 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..71182182b5f --- /dev/null +++ b/src/EFCore/Query/LiftableConstantExpressionHelpers.cs @@ -0,0 +1,291 @@ +// 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; + } + + if (value != null && value.GetType().Name == "SelectExpression") + { + 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 or ValueBuffer /*VB is temporary for InMemory */=> 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..503ac78ca22 --- /dev/null +++ b/src/EFCore/Query/LiftableConstantProcessor.cs @@ -0,0 +1,482 @@ +// 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.Internal; + +#pragma warning disable CS1591 + +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..70949f21b41 --- /dev/null +++ b/src/EFCore/Query/MaterializerLiftableConstantContext.cs @@ -0,0 +1,9 @@ +// 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; + +/// +/// TODO +/// +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 class QueryCompilationContext _queryableMethodTranslatingExpressionVisitorFactory = dependencies.QueryableMethodTranslatingExpressionVisitorFactory; _queryTranslationPostprocessorFactory = dependencies.QueryTranslationPostprocessorFactory; _shapedQueryCompilingExpressionVisitorFactory = dependencies.ShapedQueryCompilingExpressionVisitorFactory; - - _expressionPrinter = new ExpressionPrinter(); + _runtimeParameterConstantLifter = new(dependencies.LiftableConstantFactory); } /// @@ -148,6 +148,11 @@ public class 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 void AddTag(string tag) query, QueryContextParameter); - try - { - return queryExecutorExpression.Compile(); - } - finally - { - Logger.QueryExecutionPlanned(Dependencies.Context, _expressionPrinter, queryExecutorExpression); - } + return queryExecutorExpression; } /// @@ -193,6 +219,14 @@ public virtual void AddTag(string tag) /// 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 sealed record QueryCompilationContextDependencies IQueryableMethodTranslatingExpressionVisitorFactory queryableMethodTranslatingExpressionVisitorFactory, IQueryTranslationPostprocessorFactory queryTranslationPostprocessorFactory, IShapedQueryCompilingExpressionVisitorFactory shapedQueryCompilingExpressionVisitorFactory, + ILiftableConstantFactory liftableConstantFactory, + ILiftableConstantProcessor liftableConstantProcessor, IExecutionStrategy executionStrategy, ICurrentDbContext currentContext, IDbContextOptions contextOptions, @@ -65,6 +67,8 @@ public sealed record 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..a937694bffe 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; @@ -55,7 +57,9 @@ public abstract class ShapedQueryCompilingExpressionVisitor : ExpressionVisitor _entityMaterializerInjectingExpressionVisitor = new EntityMaterializerInjectingExpressionVisitor( dependencies.EntityMaterializerSource, - queryCompilationContext.QueryTrackingBehavior); + dependencies.LiftableConstantFactory, + queryCompilationContext.QueryTrackingBehavior, + queryCompilationContext.SupportsPrecompiledQuery); _constantVerifyingExpressionVisitor = new ConstantVerifyingExpressionVisitor(dependencies.TypeMappingSource); @@ -128,7 +132,14 @@ protected override Expression VisitExtension(Expression extensionExpression) .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 +161,14 @@ protected override Expression VisitExtension(Expression extensionExpression) 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 +207,106 @@ protected virtual Expression InjectEntityMaterializers(Expression expression) { VerifyNoClientConstant(expression); - return _entityMaterializerInjectingExpressionVisitor.Inject(expression); + var materializerExpression = _entityMaterializerInjectingExpressionVisitor.Inject(expression); + if (QueryCompilationContext.SupportsPrecompiledQuery) + { + materializerExpression = new MaterializationConditionConstantLifter(Dependencies.LiftableConstantFactory).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 + 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 +316,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 +435,30 @@ private sealed class EntityMaterializerInjectingExpressionVisitor : ExpressionVi nameof(QueryContext.StartTracking), [typeof(IEntityType), typeof(object), typeof(ISnapshot).MakeByRefType()])!; private static readonly MethodInfo CreateNullKeyValueInNoTrackingQueryMethod - = typeof(EntityMaterializerInjectingExpressionVisitor) + = typeof(ShapedQueryCompilingExpressionVisitor) .GetTypeInfo().GetDeclaredMethod(nameof(CreateNullKeyValueInNoTrackingQuery))!; 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) @@ -384,7 +537,14 @@ private Expression ProcessEntityShaper(StructuralTypeShaperExpression shaper) Call( QueryCompilationContext.QueryContextParameter, TryGetEntryMethodInfo, - Constant(primaryKey), + _supportsPrecompiledQuery + ? _liftableConstantFactory.CreateLiftableConstant( + Constant(primaryKey), + // TODO: Owned, STET + c => c.Dependencies.Model.FindEntityType(typeBase.Name)!.FindPrimaryKey()!, + typeBase.Name + "Key", + typeof(IKey)) + : Constant(primaryKey), NewArrayInit( typeof(object), primaryKey.Properties @@ -454,8 +614,20 @@ 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), + x => x.Dependencies.Model.FindEntityType(typeBase.Name)!.FindPrimaryKey()!.Properties, + typeBase.Name + "PrimaryKeyProperties", + typeof(IReadOnlyList)) + : Constant(primaryKey.Properties), keyValuesVariable)))); } } @@ -472,6 +644,30 @@ private Expression ProcessEntityShaper(StructuralTypeShaperExpression shaper) return Block(variables, expressions); } + 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), + _ => base.VisitConstant(constantExpression) + }; + } + private Expression MaterializeEntity( StructuralTypeShaperExpression shaper, ParameterExpression materializationContextVariable, @@ -491,18 +687,29 @@ private Expression ProcessEntityShaper(StructuralTypeShaperExpression shaper) 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); + + if (_supportsPrecompiledQuery) + { + materializationConditionBody = _materializationConditionConstantLifter.Visit(materializationConditionBody); + } + 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 +718,16 @@ private Expression ProcessEntityShaper(StructuralTypeShaperExpression shaper) 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 +819,5 @@ private Expression ProcessEntityShaper(StructuralTypeShaperExpression shaper) 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 sealed record 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 sealed record 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 @@ private static Exception CreateUnableToDiscriminateException(ITypeBase type, obj /// 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..710cadc5d0f 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; } + + /// + /// TODO + /// + 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 class BoolToStringConverter : BoolToTwoValuesConverter 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 class StringToBytesConverter : ValueConverter Encoding encoding, ConverterMappingHints? mappingHints = null) : base( - v => encoding.GetBytes(v!), - v => encoding.GetString(v!), + FromProvider(encoding), + ToProvider(encoding), mappingHints) { } @@ -39,4 +42,26 @@ public class StringToBytesConverter : ValueConverter /// 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..177b46d7576 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))!; } + + /// + /// TODO + /// + 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..8f8ef92b8b1 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,10 @@ 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!, + LiftableConstantExpression liftableConstantExpression => (T)liftableConstantExpression.OriginalExpression.Value!, + _ => throw new InvalidOperationException() + }; } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/NorthwindAggregateOperatorsQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/NorthwindAggregateOperatorsQueryInMemoryTest.cs index 738efad295a..8c3ae73b76b 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/NorthwindAggregateOperatorsQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/NorthwindAggregateOperatorsQueryInMemoryTest.cs @@ -47,4 +47,9 @@ public override async Task Contains_with_local_enumerable_inline_closure_mix(boo => await Assert.ThrowsAsync( async () => await base.Contains_with_local_enumerable_inline_closure_mix(async)); + + + [ConditionalTheory(Skip = "AOT exception, issue #33383")] + public override Task Contains_with_local_ordered_enumerable_inline(bool async) + => base.Contains_with_local_ordered_enumerable_inline(async); } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/NorthwindMiscellaneousQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/NorthwindMiscellaneousQueryInMemoryTest.cs index ef7b6c60356..9e555ad25ef 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/NorthwindMiscellaneousQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/NorthwindMiscellaneousQueryInMemoryTest.cs @@ -55,4 +55,13 @@ public override Task Collection_navigation_equal_to_null_for_subquery_using_Elem public override Task Collection_navigation_equal_to_null_for_subquery_using_ElementAtOrDefault_parameter(bool async) => Task.CompletedTask; + + [ConditionalTheory(Skip = "AOT: executing client code is not supported, in AOT mode it will throw different exception than expected")] + public override Task Client_code_using_instance_in_anonymous_type(bool async) => base.Client_code_using_instance_in_anonymous_type(async); + + [ConditionalTheory(Skip = "AOT: executing client code is not supported, in AOT mode it will throw different exception than expected")] + public override Task Client_code_using_instance_in_static_method(bool async) => base.Client_code_using_instance_in_static_method(async); + + [ConditionalTheory(Skip = "AOT: executing client code is not supported, in AOT mode it will throw different exception than expected")] + public override Task Client_code_using_instance_method_throws(bool async) => base.Client_code_using_instance_method_throws(async); } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/SharedTypeQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/SharedTypeQueryInMemoryTest.cs index 00af8f46592..8366664973b 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/SharedTypeQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/SharedTypeQueryInMemoryTest.cs @@ -12,17 +12,17 @@ protected override ITestStoreFactory TestStoreFactory [MemberData(nameof(IsAsyncData))] public virtual async Task Can_use_shared_type_entity_type_in_ToInMemoryQuery(bool async) { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: c => c.SeedAsync()); using var context = contextFactory.CreateContext(); - var data = context.Set(); + var data = context.Set(); Assert.Equal("Maumar", Assert.Single(data).Value); } - private class MyContextInMemory24601(DbContextOptions options) : MyContext24601(options) + private class ContextInMemory24601(DbContextOptions options) : Context24601(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -34,9 +34,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) b.IndexerProperty("Value"); }); - modelBuilder.Entity().HasNoKey() + modelBuilder.Entity().HasNoKey() .ToInMemoryQuery( - () => Set>("STET").Select(e => new ViewQuery24601 { Value = (string)e["Value"] })); + () => Set>("STET").Select(e => new ViewQuery { Value = (string)e["Value"] })); } } } diff --git a/test/EFCore.OData.FunctionalTests/Query/NorthwindODataQueryTests.cs b/test/EFCore.OData.FunctionalTests/Query/NorthwindODataQueryTests.cs index e05ec472681..e69076e114e 100644 --- a/test/EFCore.OData.FunctionalTests/Query/NorthwindODataQueryTests.cs +++ b/test/EFCore.OData.FunctionalTests/Query/NorthwindODataQueryTests.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Query; public class NorthwindODataQueryTests(NorthwindODataQueryTestFixture fixture) : ODataQueryTestBase(fixture), IClassFixture { - [ConditionalFact] + [ConditionalFact(Skip = "AOT see issue #33383")] public async Task Basic_query_customers() { var requestUri = $"{BaseAddress}/odata/Customers"; @@ -25,7 +25,7 @@ public async Task Basic_query_customers() Assert.Equal(91, customers.Count); } - [ConditionalFact] + [ConditionalFact(Skip = "AOT see issue #33383")] public async Task Basic_query_select_single_customer() { var requestUri = string.Format(@"{0}/odata/Customers('ALFKI')", BaseAddress); @@ -39,7 +39,7 @@ public async Task Basic_query_select_single_customer() Assert.Equal("ALFKI", result["CustomerID"].ToString()); } - [ConditionalFact] + [ConditionalFact(Skip = "AOT see issue #33383")] public async Task Query_for_alfki_expand_orders() { var requestUri = string.Format(@"{0}/odata/Customers?$filter=CustomerID eq 'ALFKI'&$expand=Orders", BaseAddress); @@ -58,7 +58,7 @@ public async Task Query_for_alfki_expand_orders() Assert.Equal(6, orders.Count); } - [ConditionalFact] + [ConditionalFact(Skip = "AOT see issue #33383")] public async Task Basic_query_orders() { var requestUri = $"{BaseAddress}/odata/Orders"; @@ -74,7 +74,7 @@ public async Task Basic_query_orders() Assert.Equal(830, orders.Count); } - [ConditionalFact] + [ConditionalFact(Skip = "AOT see issue #33383")] public async Task Query_orders_select_single_property() { var requestUri = $"{BaseAddress}/odata/Orders?$select=OrderDate"; @@ -90,7 +90,7 @@ public async Task Query_orders_select_single_property() Assert.Equal(830, orderDates.Count); } - [ConditionalFact] + [ConditionalFact(Skip = "AOT see issue #33383")] public async Task Basic_query_order_details() { var requestUri = $"{BaseAddress}/odata/Order Details"; @@ -103,7 +103,7 @@ public async Task Basic_query_order_details() Assert.Contains("$metadata#Order%20Details", result["@odata.context"].ToString()); } - [ConditionalFact] + [ConditionalFact(Skip = "AOT see issue #33383")] public async Task Basic_query_order_details_single_element_composite_key() { var requestUri = $"{BaseAddress}/odata/Order Details(OrderID=10248,ProductID=11)"; diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocAdvancedMappingsQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocAdvancedMappingsQueryRelationalTestBase.cs index 79a4d7cf64c..2594fef65e1 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/AdHocAdvancedMappingsQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocAdvancedMappingsQueryRelationalTestBase.cs @@ -77,7 +77,7 @@ public virtual async Task Projecting_one_of_two_similar_complex_types_picks_the_ Assert.Equal(new DateTime(2000, 1, 1), query[0].Created); } - protected class Context32911(DbContextOptions options) : DbContext(options) + public class Context32911(DbContextOptions options) : DbContext(options) { public DbSet Offers { get; set; } diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryTestBase.cs index b51ac0f8a32..2ae42c95bc3 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryTestBase.cs @@ -16,7 +16,7 @@ protected override string StoreName [MemberData(nameof(IsAsyncData))] public virtual async Task Contains_on_nested_collection_with_init_only_navigation(bool async) { - var contextFactory = await InitializeAsync(seed: Seed32310); + var contextFactory = await InitializeAsync(seed: Seed32310); await using var context = contextFactory.CreateContext(); var query = context.Pubs @@ -30,34 +30,42 @@ public virtual async Task Contains_on_nested_collection_with_init_only_navigatio Assert.Equal(new DateOnly(2023, 1, 1), result.Visits.DaysVisited.Single()); } - protected virtual async Task Seed32310(MyContext32310 context) + protected virtual async Task Seed32310(Context32310 context) { - var user = new Pub32310 { Name = "FBI", Visits = new Visits32310 { LocationTag = "tag", DaysVisited = [new(2023, 1, 1)] } }; + var user = new Context32310.Pub + { + Name = "FBI", + Visits = new Context32310.Visits + { + LocationTag = "tag", + DaysVisited = [new(2023, 1, 1)] + } + }; context.Add(user); await context.SaveChangesAsync(); } - protected class MyContext32310(DbContextOptions options) : DbContext(options) + public class Context32310(DbContextOptions options) : DbContext(options) { - public DbSet Pubs - => Set(); + public DbSet Pubs + => Set(); protected override void OnModelCreating(ModelBuilder modelBuilder) - => modelBuilder.Entity(b => { b.OwnsOne(e => e.Visits).ToJson(); }); - } + => modelBuilder.Entity(b => { b.OwnsOne(e => e.Visits).ToJson(); }); - public class Pub32310 - { - public int Id { get; set; } - public required string Name { get; set; } - public Visits32310 Visits { get; set; } = null!; - } + public class Pub + { + public int Id { get; set; } + public required string Name { get; set; } + public Visits Visits { get; set; } = null!; + } - public class Visits32310 - { - public string LocationTag { get; set; } - public required List DaysVisited { get; init; } + public class Visits + { + public string LocationTag { get; set; } + public required List DaysVisited { get; init; } + } } #endregion @@ -68,7 +76,7 @@ public class Visits32310 [MemberData(nameof(IsAsyncData))] public virtual async Task Optional_json_properties_materialized_as_null_when_the_element_in_json_is_not_present(bool async) { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: Seed29219); using (var context = contextFactory.CreateContext()) @@ -89,7 +97,7 @@ public virtual async Task Optional_json_properties_materialized_as_null_when_the [MemberData(nameof(IsAsyncData))] public virtual async Task Can_project_nullable_json_property_when_the_element_in_json_is_not_present(bool async) { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: Seed29219); using (var context = contextFactory.CreateContext()) @@ -107,90 +115,42 @@ public virtual async Task Can_project_nullable_json_property_when_the_element_in } } - protected abstract Task Seed29219(MyContext29219 ctx); + protected abstract Task Seed29219(Context29219 ctx); - protected class MyContext29219(DbContextOptions options) : DbContext(options) + public class Context29219(DbContextOptions options) : DbContext(options) { - public DbSet Entities { get; set; } + public DbSet Entities { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); - modelBuilder.Entity().OwnsOne(x => x.Reference).ToJson(); - modelBuilder.Entity().OwnsMany(x => x.Collection).ToJson(); + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().OwnsOne(x => x.Reference).ToJson(); + modelBuilder.Entity().OwnsMany(x => x.Collection).ToJson(); } - } - public class MyEntity29219 - { - public int Id { get; set; } - public MyJsonEntity29219 Reference { get; set; } - public List Collection { get; set; } - } + public class MyEntity + { + public int Id { get; set; } + public MyJsonEntity Reference { get; set; } + public List Collection { get; set; } + } - public class MyJsonEntity29219 - { - public int NonNullableScalar { get; set; } - public int? NullableScalar { get; set; } + public class MyJsonEntity + { + public int NonNullableScalar { get; set; } + public int? NullableScalar { get; set; } + } } #endregion #region 30028 - protected abstract Task Seed30028(MyContext30028 ctx); - - protected class MyContext30028(DbContextOptions options) : DbContext(options) - { - public DbSet Entities { get; set; } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - => modelBuilder.Entity( - b => - { - b.Property(x => x.Id).ValueGeneratedNever(); - b.OwnsOne( - x => x.Json, nb => - { - nb.ToJson(); - nb.OwnsMany(x => x.Collection, nnb => nnb.OwnsOne(x => x.Nested)); - nb.OwnsOne(x => x.OptionalReference, nnb => nnb.OwnsOne(x => x.Nested)); - nb.OwnsOne(x => x.RequiredReference, nnb => nnb.OwnsOne(x => x.Nested)); - nb.Navigation(x => x.RequiredReference).IsRequired(); - }); - }); - } - - public class MyEntity30028 - { - public int Id { get; set; } - public MyJsonRootEntity30028 Json { get; set; } - } - - public class MyJsonRootEntity30028 - { - public string RootName { get; set; } - public MyJsonBranchEntity30028 RequiredReference { get; set; } - public MyJsonBranchEntity30028 OptionalReference { get; set; } - public List Collection { get; set; } - } - - public class MyJsonBranchEntity30028 - { - public string BranchName { get; set; } - public MyJsonLeafEntity30028 Nested { get; set; } - } - - public class MyJsonLeafEntity30028 - { - public string LeafName { get; set; } - } - [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual async Task Accessing_missing_navigation_works(bool async) { - var contextFactory = await InitializeAsync(seed: Seed30028); + var contextFactory = await InitializeAsync(seed: Seed30028); using (var context = contextFactory.CreateContext()) { var result = context.Entities.OrderBy(x => x.Id).ToList(); @@ -217,7 +177,7 @@ public virtual async Task Accessing_missing_navigation_works(bool async) [MemberData(nameof(IsAsyncData))] public virtual async Task Missing_navigation_works_with_deduplication(bool async) { - var contextFactory = await InitializeAsync(seed: Seed30028); + var contextFactory = await InitializeAsync(seed: Seed30028); using (var context = contextFactory.CreateContext()) { var result = context.Entities.OrderBy(x => x.Id).Select( @@ -259,6 +219,54 @@ public virtual async Task Missing_navigation_works_with_deduplication(bool async } } + protected abstract Task Seed30028(Context30028 ctx); + + public class Context30028(DbContextOptions options) : DbContext(options) + { + public DbSet Entities { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + => modelBuilder.Entity( + b => + { + b.Property(x => x.Id).ValueGeneratedNever(); + b.OwnsOne( + x => x.Json, nb => + { + nb.ToJson(); + nb.OwnsMany(x => x.Collection, nnb => nnb.OwnsOne(x => x.Nested)); + nb.OwnsOne(x => x.OptionalReference, nnb => nnb.OwnsOne(x => x.Nested)); + nb.OwnsOne(x => x.RequiredReference, nnb => nnb.OwnsOne(x => x.Nested)); + nb.Navigation(x => x.RequiredReference).IsRequired(); + }); + }); + + public class MyEntity + { + public int Id { get; set; } + public MyJsonRootEntity Json { get; set; } + } + + public class MyJsonRootEntity + { + public string RootName { get; set; } + public MyJsonBranchEntity RequiredReference { get; set; } + public MyJsonBranchEntity OptionalReference { get; set; } + public List Collection { get; set; } + } + + public class MyJsonBranchEntity + { + public string BranchName { get; set; } + public MyJsonLeafEntity Nested { get; set; } + } + + public class MyJsonLeafEntity + { + public string LeafName { get; set; } + } + } + #endregion #region 32939 @@ -273,39 +281,39 @@ public virtual async Task Project_json_with_no_properties() protected Task Seed30028(Context32939 ctx) { - var entity = new Context32939.Entity32939 + var entity = new Context32939.Entity { - Empty = new Context32939.JsonEmpty32939(), FieldOnly = new Context32939.JsonFieldOnly32939() + Empty = new Context32939.JsonEmpty(), FieldOnly = new Context32939.JsonFieldOnly() }; ctx.Entities.Add(entity); return ctx.SaveChangesAsync(); } - protected class Context32939(DbContextOptions options) : DbContext(options) + public class Context32939(DbContextOptions options) : DbContext(options) { - public DbSet Entities { get; set; } + public DbSet Entities { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); - modelBuilder.Entity().OwnsOne(x => x.Empty, b => b.ToJson()); - modelBuilder.Entity().OwnsOne(x => x.FieldOnly, b => b.ToJson()); + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().OwnsOne(x => x.Empty, b => b.ToJson()); + modelBuilder.Entity().OwnsOne(x => x.FieldOnly, b => b.ToJson()); } - public class Entity32939 + public class Entity { public int Id { get; set; } - public JsonEmpty32939 Empty { get; set; } - public JsonFieldOnly32939 FieldOnly { get; set; } + public JsonEmpty Empty { get; set; } + public JsonFieldOnly FieldOnly { get; set; } } - public class JsonEmpty32939 + public class JsonEmpty { } - public class JsonFieldOnly32939 + public class JsonFieldOnly { public int Field; } @@ -326,7 +334,7 @@ public virtual async Task Query_with_nested_json_collection_mapped_to_private_fi Assert.Equal(1, query.Count); } - protected class Context33046(DbContextOptions options) : DbContext(options) + public class Context33046(DbContextOptions options) : DbContext(options) { public DbSet Reviews { get; set; } @@ -377,7 +385,7 @@ public class SubRound [MemberData(nameof(IsAsyncData))] public virtual async Task Project_json_array_of_primitives_on_reference(bool async) { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: SeedArrayOfPrimitives); using (var context = contextFactory.CreateContext()) @@ -400,7 +408,7 @@ public virtual async Task Project_json_array_of_primitives_on_reference(bool asy [MemberData(nameof(IsAsyncData))] public virtual async Task Project_json_array_of_primitives_on_collection(bool async) { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: SeedArrayOfPrimitives); using (var context = contextFactory.CreateContext()) @@ -423,7 +431,7 @@ public virtual async Task Project_json_array_of_primitives_on_collection(bool as [MemberData(nameof(IsAsyncData))] public virtual async Task Project_element_of_json_array_of_primitives(bool async) { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: SeedArrayOfPrimitives); using (var context = contextFactory.CreateContext()) @@ -441,7 +449,7 @@ public virtual async Task Project_element_of_json_array_of_primitives(bool async [MemberData(nameof(IsAsyncData))] public virtual async Task Predicate_based_on_element_of_json_array_of_primitives1(bool async) { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: SeedArrayOfPrimitives); using (var context = contextFactory.CreateContext()) @@ -461,7 +469,7 @@ public virtual async Task Predicate_based_on_element_of_json_array_of_primitives [MemberData(nameof(IsAsyncData))] public virtual async Task Predicate_based_on_element_of_json_array_of_primitives2(bool async) { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: SeedArrayOfPrimitives); using (var context = contextFactory.CreateContext()) @@ -481,7 +489,7 @@ public virtual async Task Predicate_based_on_element_of_json_array_of_primitives [MemberData(nameof(IsAsyncData))] public virtual async Task Predicate_based_on_element_of_json_array_of_primitives3(bool async) { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: SeedArrayOfPrimitives); using (var context = contextFactory.CreateContext()) @@ -501,34 +509,34 @@ public virtual async Task Predicate_based_on_element_of_json_array_of_primitives } } - protected abstract Task SeedArrayOfPrimitives(MyContextArrayOfPrimitives ctx); + protected abstract Task SeedArrayOfPrimitives(ContextArrayOfPrimitives ctx); - protected class MyContextArrayOfPrimitives(DbContextOptions options) : DbContext(options) + public class ContextArrayOfPrimitives(DbContextOptions options) : DbContext(options) { - public DbSet Entities { get; set; } + public DbSet Entities { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); - modelBuilder.Entity().OwnsOne( + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().OwnsOne( x => x.Reference, b => b.ToJson()); - modelBuilder.Entity().OwnsMany( + modelBuilder.Entity().OwnsMany( x => x.Collection, b => b.ToJson()); } - } - public class MyEntityArrayOfPrimitives - { - public int Id { get; set; } - public MyJsonEntityArrayOfPrimitives Reference { get; set; } - public List Collection { get; set; } - } + public class MyEntity + { + public int Id { get; set; } + public MyJsonEntity Reference { get; set; } + public List Collection { get; set; } + } - public class MyJsonEntityArrayOfPrimitives - { - public int[] IntArray { get; set; } - public List ListOfString { get; set; } + public class MyJsonEntity + { + public int[] IntArray { get; set; } + public List ListOfString { get; set; } + } } #endregion @@ -539,7 +547,7 @@ public class MyJsonEntityArrayOfPrimitives [MemberData(nameof(IsAsyncData))] public virtual async Task Junk_in_json_basic_tracking(bool async) { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: SeedJunkInJson); using (var context = contextFactory.CreateContext()) @@ -564,7 +572,7 @@ public virtual async Task Junk_in_json_basic_tracking(bool async) [MemberData(nameof(IsAsyncData))] public virtual async Task Junk_in_json_basic_no_tracking(bool async) { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: SeedJunkInJson); using (var context = contextFactory.CreateContext()) @@ -585,37 +593,37 @@ public virtual async Task Junk_in_json_basic_no_tracking(bool async) } } - protected abstract Task SeedJunkInJson(MyContextJunkInJson ctx); + protected abstract Task SeedJunkInJson(ContextJunkInJson ctx); - protected class MyContextJunkInJson(DbContextOptions options) : DbContext(options) + public class ContextJunkInJson(DbContextOptions options) : DbContext(options) { - public DbSet Entities { get; set; } + public DbSet Entities { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); - modelBuilder.Entity().OwnsOne( + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().OwnsOne( x => x.Reference, b => { b.ToJson(); b.OwnsOne(x => x.NestedReference); b.OwnsMany(x => x.NestedCollection); }); - modelBuilder.Entity().OwnsOne( + modelBuilder.Entity().OwnsOne( x => x.ReferenceWithCtor, b => { b.ToJson(); b.OwnsOne(x => x.NestedReference); b.OwnsMany(x => x.NestedCollection); }); - modelBuilder.Entity().OwnsMany( + modelBuilder.Entity().OwnsMany( x => x.Collection, b => { b.ToJson(); b.OwnsOne(x => x.NestedReference); b.OwnsMany(x => x.NestedCollection); }); - modelBuilder.Entity().OwnsMany( + modelBuilder.Entity().OwnsMany( x => x.CollectionWithCtor, b => { b.ToJson(); @@ -623,43 +631,43 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) b.OwnsMany(x => x.NestedCollection); }); } - } - public class MyEntityJunkInJson - { - public int Id { get; set; } - public MyJsonEntityJunkInJson Reference { get; set; } - public MyJsonEntityJunkInJsonWithCtor ReferenceWithCtor { get; set; } - public List Collection { get; set; } - public List CollectionWithCtor { get; set; } - } + public class MyEntity + { + public int Id { get; set; } + public MyJsonEntity Reference { get; set; } + public MyJsonEntityWithCtor ReferenceWithCtor { get; set; } + public List Collection { get; set; } + public List CollectionWithCtor { get; set; } + } - public class MyJsonEntityJunkInJson - { - public string Name { get; set; } - public double Number { get; set; } + public class MyJsonEntity + { + public string Name { get; set; } + public double Number { get; set; } - public MyJsonEntityJunkInJsonNested NestedReference { get; set; } - public List NestedCollection { get; set; } - } + public MyJsonEntityNested NestedReference { get; set; } + public List NestedCollection { get; set; } + } - public class MyJsonEntityJunkInJsonNested - { - public DateTime DoB { get; set; } - } + public class MyJsonEntityNested + { + public DateTime DoB { get; set; } + } - public class MyJsonEntityJunkInJsonWithCtor(bool myBool, string name) - { - public bool MyBool { get; set; } = myBool; - public string Name { get; set; } = name; + public class MyJsonEntityWithCtor(bool myBool, string name) + { + public bool MyBool { get; set; } = myBool; + public string Name { get; set; } = name; - public MyJsonEntityJunkInJsonWithCtorNested NestedReference { get; set; } - public List NestedCollection { get; set; } - } + public MyJsonEntityWithCtorNested NestedReference { get; set; } + public List NestedCollection { get; set; } + } - public class MyJsonEntityJunkInJsonWithCtorNested(DateTime doB) - { - public DateTime DoB { get; set; } = doB; + public class MyJsonEntityWithCtorNested(DateTime doB) + { + public DateTime DoB { get; set; } = doB; + } } #endregion @@ -670,7 +678,7 @@ public class MyJsonEntityJunkInJsonWithCtorNested(DateTime doB) [MemberData(nameof(IsAsyncData))] public virtual async Task Tricky_buffering_basic(bool async) { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: SeedTrickyBuffering); using (var context = contextFactory.CreateContext()) @@ -689,16 +697,16 @@ public virtual async Task Tricky_buffering_basic(bool async) } } - protected abstract Task SeedTrickyBuffering(MyContextTrickyBuffering ctx); + protected abstract Task SeedTrickyBuffering(ContextTrickyBuffering ctx); - protected class MyContextTrickyBuffering(DbContextOptions options) : DbContext(options) + public class ContextTrickyBuffering(DbContextOptions options) : DbContext(options) { - public DbSet Entities { get; set; } + public DbSet Entities { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); - modelBuilder.Entity().OwnsOne( + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().OwnsOne( x => x.Reference, b => { b.ToJson(); @@ -706,25 +714,25 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) b.OwnsMany(x => x.NestedCollection); }); } - } - public class MyEntityTrickyBuffering - { - public int Id { get; set; } - public MyJsonEntityTrickyBuffering Reference { get; set; } - } + public class MyEntity + { + public int Id { get; set; } + public MyJsonEntity Reference { get; set; } + } - public class MyJsonEntityTrickyBuffering - { - public string Name { get; set; } - public int Number { get; set; } - public MyJsonEntityJunkInJsonNested NestedReference { get; set; } - public List NestedCollection { get; set; } - } + public class MyJsonEntity + { + public string Name { get; set; } + public int Number { get; set; } + public MyJsonEntityNested NestedReference { get; set; } + public List NestedCollection { get; set; } + } - public class MyJsonEntityTrickyBufferingNested - { - public DateTime DoB { get; set; } + public class MyJsonEntityNested + { + public DateTime DoB { get; set; } + } } #endregion @@ -735,7 +743,7 @@ public class MyJsonEntityTrickyBufferingNested [MemberData(nameof(IsAsyncData))] public virtual async Task Shadow_properties_basic_tracking(bool async) { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: SeedShadowProperties); using (var context = contextFactory.CreateContext()) @@ -774,7 +782,7 @@ public virtual async Task Shadow_properties_basic_tracking(bool async) [MemberData(nameof(IsAsyncData))] public virtual async Task Shadow_properties_basic_no_tracking(bool async) { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: SeedShadowProperties); using (var context = contextFactory.CreateContext()) @@ -797,7 +805,7 @@ public virtual async Task Shadow_properties_basic_no_tracking(bool async) [MemberData(nameof(IsAsyncData))] public virtual async Task Project_shadow_properties_from_json_entity(bool async) { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: SeedShadowProperties); using (var context = contextFactory.CreateContext()) @@ -819,61 +827,61 @@ public virtual async Task Project_shadow_properties_from_json_entity(bool async) } } - protected abstract Task SeedShadowProperties(MyContextShadowProperties ctx); + protected abstract Task SeedShadowProperties(ContextShadowProperties ctx); - protected class MyContextShadowProperties(DbContextOptions options) : DbContext(options) + public class ContextShadowProperties(DbContextOptions options) : DbContext(options) { - public DbSet Entities { get; set; } + public DbSet Entities { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); - modelBuilder.Entity().OwnsOne( + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().OwnsOne( x => x.Reference, b => { b.ToJson(); b.Property("ShadowString"); }); - modelBuilder.Entity().OwnsOne( + modelBuilder.Entity().OwnsOne( x => x.ReferenceWithCtor, b => { b.ToJson(); b.Property("Shadow_Int").HasJsonPropertyName("ShadowInt"); }); - modelBuilder.Entity().OwnsMany( + modelBuilder.Entity().OwnsMany( x => x.Collection, b => { b.ToJson(); b.Property("ShadowDouble"); }); - modelBuilder.Entity().OwnsMany( + modelBuilder.Entity().OwnsMany( x => x.CollectionWithCtor, b => { b.ToJson(); b.Property("ShadowNullableByte"); }); } - } - public class MyEntityShadowProperties - { - public int Id { get; set; } - public string Name { get; set; } + public class MyEntity + { + public int Id { get; set; } + public string Name { get; set; } - public MyJsonEntityShadowProperties Reference { get; set; } - public List Collection { get; set; } - public MyJsonEntityShadowPropertiesWithCtor ReferenceWithCtor { get; set; } - public List CollectionWithCtor { get; set; } - } + public MyJsonEntity Reference { get; set; } + public List Collection { get; set; } + public MyJsonEntityWithCtor ReferenceWithCtor { get; set; } + public List CollectionWithCtor { get; set; } + } - public class MyJsonEntityShadowProperties - { - public string Name { get; set; } - } + public class MyJsonEntity + { + public string Name { get; set; } + } - public class MyJsonEntityShadowPropertiesWithCtor(string name) - { - public string Name { get; set; } = name; + public class MyJsonEntityWithCtor(string name) + { + public string Name { get; set; } = name; + } } #endregion @@ -884,7 +892,7 @@ public class MyJsonEntityShadowPropertiesWithCtor(string name) [MemberData(nameof(IsAsyncData))] public virtual async Task Project_proxies_entity_with_json(bool async) { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: SeedLazyLoadingProxies, onConfiguring: OnConfiguringLazyLoadingProxies, addServices: AddServicesLazyLoadingProxies); @@ -907,18 +915,18 @@ protected void OnConfiguringLazyLoadingProxies(DbContextOptionsBuilder optionsBu protected IServiceCollection AddServicesLazyLoadingProxies(IServiceCollection addServices) => addServices.AddEntityFrameworkProxies(); - private Task SeedLazyLoadingProxies(MyContextLazyLoadingProxies ctx) + private Task SeedLazyLoadingProxies(ContextLazyLoadingProxies ctx) { - var r1 = new MyJsonEntityLazyLoadingProxiesWithCtor("r1", 1); - var c11 = new MyJsonEntityLazyLoadingProxies { Name = "c11", Number = 11 }; - var c12 = new MyJsonEntityLazyLoadingProxies { Name = "c12", Number = 12 }; - var c13 = new MyJsonEntityLazyLoadingProxies { Name = "c13", Number = 13 }; + var r1 = new ContextLazyLoadingProxies.MyJsonEntityWithCtor("r1", 1); + var c11 = new ContextLazyLoadingProxies.MyJsonEntity { Name = "c11", Number = 11 }; + var c12 = new ContextLazyLoadingProxies.MyJsonEntity { Name = "c12", Number = 12 }; + var c13 = new ContextLazyLoadingProxies.MyJsonEntity { Name = "c13", Number = 13 }; - var r2 = new MyJsonEntityLazyLoadingProxiesWithCtor("r2", 2); - var c21 = new MyJsonEntityLazyLoadingProxies { Name = "c21", Number = 21 }; - var c22 = new MyJsonEntityLazyLoadingProxies { Name = "c22", Number = 22 }; + var r2 = new ContextLazyLoadingProxies.MyJsonEntityWithCtor("r2", 2); + var c21 = new ContextLazyLoadingProxies.MyJsonEntity { Name = "c21", Number = 21 }; + var c22 = new ContextLazyLoadingProxies.MyJsonEntity { Name = "c22", Number = 22 }; - var e1 = new MyEntityLazyLoadingProxies + var e1 = new ContextLazyLoadingProxies.MyEntity { Id = 1, Name = "e1", @@ -931,7 +939,7 @@ private Task SeedLazyLoadingProxies(MyContextLazyLoadingProxies ctx) ] }; - var e2 = new MyEntityLazyLoadingProxies + var e2 = new ContextLazyLoadingProxies.MyEntity { Id = 2, Name = "e2", @@ -943,37 +951,37 @@ private Task SeedLazyLoadingProxies(MyContextLazyLoadingProxies ctx) return ctx.SaveChangesAsync(); } - protected class MyContextLazyLoadingProxies(DbContextOptions options) : DbContext(options) + public class ContextLazyLoadingProxies(DbContextOptions options) : DbContext(options) { - public DbSet Entities { get; set; } + public DbSet Entities { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); - modelBuilder.Entity().OwnsOne(x => x.Reference, b => b.ToJson()); - modelBuilder.Entity().OwnsMany(x => x.Collection, b => b.ToJson()); + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().OwnsOne(x => x.Reference, b => b.ToJson()); + modelBuilder.Entity().OwnsMany(x => x.Collection, b => b.ToJson()); } - } - public class MyEntityLazyLoadingProxies - { - public int Id { get; set; } - public string Name { get; set; } + public class MyEntity + { + public int Id { get; set; } + public string Name { get; set; } - public virtual MyJsonEntityLazyLoadingProxiesWithCtor Reference { get; set; } - public virtual List Collection { get; set; } - } + public virtual MyJsonEntityWithCtor Reference { get; set; } + public virtual List Collection { get; set; } + } - public class MyJsonEntityLazyLoadingProxiesWithCtor(string name, int number) - { - public string Name { get; set; } = name; - public int Number { get; set; } = number; - } + public class MyJsonEntityWithCtor(string name, int number) + { + public string Name { get; set; } = name; + public int Number { get; set; } = number; + } - public class MyJsonEntityLazyLoadingProxies - { - public string Name { get; set; } - public int Number { get; set; } + public class MyJsonEntity + { + public string Name { get; set; } + public int Number { get; set; } + } } #endregion @@ -984,7 +992,7 @@ public class MyJsonEntityLazyLoadingProxies [MemberData(nameof(IsAsyncData))] public virtual async Task Not_ICollection_basic_projection(bool async) { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: SeedNotICollection); using (var context = contextFactory.CreateContext()) @@ -999,42 +1007,40 @@ public virtual async Task Not_ICollection_basic_projection(bool async) } } - protected abstract Task SeedNotICollection(MyContextNotICollection ctx); + protected abstract Task SeedNotICollection(ContextNotICollection ctx); - public class MyEntityNotICollection + public class ContextNotICollection(DbContextOptions options) : DbContext(options) { - public int Id { get; set; } + public DbSet Entities { get; set; } - public MyJsonEntityNotICollection Json { get; set; } - } + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().OwnsOne(cr => cr.Json, nb => + { + nb.ToJson(); + nb.OwnsMany(x => x.Collection); + }); + } - public class MyJsonEntityNotICollection - { - private readonly List _collection = []; + public class MyEntity + { + public int Id { get; set; } - public IEnumerable Collection - => _collection.AsReadOnly(); - } + public MyJsonEntity Json { get; set; } + } - public class MyJsonNestedEntityNotICollection - { - public string Foo { get; set; } - public int Bar { get; set; } - } + public class MyJsonEntity + { + private readonly List _collection = []; - public class MyContextNotICollection(DbContextOptions options) : DbContext(options) - { - public DbSet Entities { get; set; } + public IEnumerable Collection => _collection.AsReadOnly(); + } - protected override void OnModelCreating(ModelBuilder modelBuilder) + public class MyJsonNested { - modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); - modelBuilder.Entity().OwnsOne( - cr => cr.Json, nb => - { - nb.ToJson(); - nb.OwnsMany(x => x.Collection); - }); + public string Foo { get; set; } + public int Bar { get; set; } } } diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocMiscellaneousQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocMiscellaneousQueryRelationalTestBase.cs index d27d4c9d37c..0cb0bc0b751 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/AdHocMiscellaneousQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocMiscellaneousQueryRelationalTestBase.cs @@ -37,7 +37,7 @@ public async Task Query_when_null_key_in_database_should_throw() protected abstract Task Seed2951(Context2951 context); - protected class Context2951(DbContextOptions options) : DbContext(options) + public class Context2951(DbContextOptions options) : DbContext(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) => modelBuilder.Entity().ToTable("ZeroKey", t => t.ExcludeFromMigrations()) @@ -114,7 +114,7 @@ public virtual async Task GroupJoin_Anonymous_projection_GroupBy_Aggregate_join_ } } - protected class Context11818(DbContextOptions options) : DbContext(options) + public class Context11818(DbContextOptions options) : DbContext(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -169,7 +169,7 @@ public virtual async Task Multiple_different_entity_type_from_different_namespac var bad = context.Set().FromSqlRaw(@"SELECT cast(null as int) AS MyValue").ToList(); // Exception } - protected class Context23981(DbContextOptions options) : DbContext(options) + public class Context23981(DbContextOptions options) : DbContext(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -214,7 +214,7 @@ public virtual async Task StoreType_for_UDF_used(bool async) } } - protected class Context27954(DbContextOptions options) : DbContext(options) + public class Context27954(DbContextOptions options) : DbContext(options) { public DbSet MyEntities { get; set; } diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocNavigationsQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocNavigationsQueryRelationalTestBase.cs index 31fa22cdf6c..f5ffd3772f0 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/AdHocNavigationsQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocNavigationsQueryRelationalTestBase.cs @@ -44,7 +44,7 @@ public virtual async Task Select_enumerable_navigation_backed_by_collection(bool } } - private class Context21803(DbContextOptions options) : DbContext(options) + public class Context21803(DbContextOptions options) : DbContext(options) { public DbSet Entities { get; set; } diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocQuerySplittingQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocQuerySplittingQueryTestBase.cs index 06c6fa948b5..7fa562ba48a 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/AdHocQuerySplittingQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocQuerySplittingQueryTestBase.cs @@ -142,7 +142,7 @@ public virtual async Task SplitQuery_disposes_inner_data_readers() } } - protected class Context21355(DbContextOptions options) : DbContext(options) + public class Context21355(DbContextOptions options) : DbContext(options) { public DbSet Parents { get; set; } @@ -263,7 +263,7 @@ private static void AssertParent25225(Guid expectedParentId, Guid expectedCollec ); } - protected class Context25225(DbContextOptions options) : DbContext(options) + public class Context25225(DbContextOptions options) : DbContext(options) { public static readonly Guid Parent1Id = new("d6457b52-690a-419e-8982-a1a8551b4572"); public static readonly Guid Parent2Id = new("e79c82f4-3ae7-4c65-85db-04e08cba6fa7"); @@ -329,7 +329,7 @@ public virtual async Task NoTracking_split_query_creates_only_required_instances Assert.Equal(1, Context25400.Test.ConstructorCallCount); } - private class Context25400(DbContextOptions options) : DbContext(options) + public class Context25400(DbContextOptions options) : DbContext(options) { public DbSet Tests { get; set; } 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/MappingQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/MappingQueryTestBase.cs index 894960e2467..c0613b7b7c3 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/MappingQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/MappingQueryTestBase.cs @@ -66,22 +66,22 @@ var orders protected virtual DbContext CreateContext() => Fixture.CreateContext(); - protected class MappedCustomer : Customer + public class MappedCustomer : Customer { public string CompanyName2 { get; set; } } - protected class MappedEmployee : Employee + public class MappedEmployee : Employee { public string City2 { get; set; } } - protected class MappedOrder : Order + public class MappedOrder : Order { public ShipVia? ShipVia2 { get; set; } } - protected enum ShipVia + public enum ShipVia { One = 1, Two, diff --git a/test/EFCore.Relational.Specification.Tests/Query/OperatorsQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/OperatorsQueryTestBase.cs index 818554d1d94..c67311f03ae 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/OperatorsQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/OperatorsQueryTestBase.cs @@ -334,13 +334,13 @@ public virtual async Task Concat_and_json_scalar(bool async) Assert.Equal("Bar", result.Owned.SomeProperty); } - private class Owner + public class Owner { public int Id { get; set; } public Owned Owned { get; set; } = null!; } - private class Owned + public class Owned { public string SomeProperty { get; set; } = ""; } diff --git a/test/EFCore.Relational.Specification.Tests/Query/OwnedEntityQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/OwnedEntityQueryRelationalTestBase.cs index 55789c12376..c3024353d8e 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/OwnedEntityQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/OwnedEntityQueryRelationalTestBase.cs @@ -28,7 +28,7 @@ public virtual async Task An_optional_dependent_without_any_columns_and_nested_d message); } - private class Context23198(DbContextOptions options) : DbContext(options) + public class Context23198(DbContextOptions options) : DbContext(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) => modelBuilder.Entity().OwnsOne( @@ -81,7 +81,7 @@ public virtual async Task Multiple_owned_reference_mapped_to_own_table_containin Assert.Equal(2, root3.ModdleA.Leaves.Count); } - private class Context24777(DbContextOptions options) : DbContext(options) + public class Context24777(DbContextOptions options) : DbContext(options) { public DbSet Roots { get; set; } @@ -184,16 +184,16 @@ public virtual async Task Owned_collection_basic_split_query(bool async) using var context = contextFactory.CreateContext(); var id = new Guid("6c1ae3e5-30b9-4c77-8d98-f02075974a0a"); - var query = context.Set().Where(e => e.Id == id).AsSplitQuery(); + var query = context.Set().Where(e => e.Id == id).AsSplitQuery(); var result = async ? await query.FirstOrDefaultAsync() : query.FirstOrDefault(); } - protected class Context25680(DbContextOptions options) : DbContext(options) + public class Context25680(DbContextOptions options) : DbContext(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) - => modelBuilder.Entity().OwnsMany( + => modelBuilder.Entity().OwnsMany( e => e.PublishTokenTypes, b => { @@ -207,23 +207,23 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) e.TokenGroupId }); }); - } - protected class Location25680 - { - public Guid Id { get; set; } - public ICollection PublishTokenTypes { get; set; } - } + public class Location + { + public Guid Id { get; set; } + public ICollection PublishTokenTypes { get; set; } + } - protected class PublishTokenType25680 - { - public Location25680 Location { get; set; } - public Guid LocationId { get; set; } + public class PublishTokenType + { + public Location Location { get; set; } + public Guid LocationId { get; set; } - public string ExternalId { get; set; } - public string VisualNumber { get; set; } - public string TokenGroupId { get; set; } - public string IssuerName { get; set; } + public string ExternalId { get; set; } + public string VisualNumber { get; set; } + public string TokenGroupId { get; set; } + public string IssuerName { get; set; } + } } #endregion @@ -250,7 +250,7 @@ public virtual async Task Owned_reference_mapped_to_different_table_nested_updat await base.Owned_references_on_same_level_nested_expanded_at_different_times_around_take_helper(context, async); } - protected class MyContext26592(DbContextOptions options) : MyContext26592Base(options) + public class MyContext26592(DbContextOptions options) : MyContext26592Base(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -422,7 +422,7 @@ public virtual async Task Owned_entity_with_all_null_properties_property_access_ }); } - private class Context28247(DbContextOptions options) : DbContext(options) + public class Context28247(DbContextOptions options) : DbContext(options) { public DbSet RotRutCases { get; set; } @@ -505,7 +505,7 @@ public virtual async Task Join_selects_with_duplicating_aliases_and_owned_expans Assert.Equal("The Divider", result[0].magus.ToolUsed.Name); } - private class Context30358(DbContextOptions options) : DbContext(options) + public class Context30358(DbContextOptions options) : DbContext(options) { public DbSet Monarchs { get; set; } public DbSet Magi { get; set; } @@ -579,7 +579,7 @@ public async Task Can_have_required_owned_type_on_derived_type() context.Set().ToList(); } - private class Context31107(DbContextOptions options) : DbContext(options) + public class Context31107(DbContextOptions options) : DbContext(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) { diff --git a/test/EFCore.Relational.Specification.Tests/Query/SharedTypeQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/SharedTypeQueryRelationalTestBase.cs index 0d2c0d4da7c..86fb69f6b1a 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/SharedTypeQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/SharedTypeQueryRelationalTestBase.cs @@ -20,11 +20,11 @@ protected void AssertSql(params string[] expected) [MemberData(nameof(IsAsyncData))] public virtual async Task Can_use_shared_type_entity_type_in_query_filter_with_from_sql(bool async) { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: c => c.SeedAsync()); using var context = contextFactory.CreateContext(); - var query = context.Set(); + var query = context.Set(); var result = async ? await query.ToListAsync() : query.ToList(); @@ -35,13 +35,13 @@ public virtual async Task Can_use_shared_type_entity_type_in_query_filter_with_f [ConditionalFact] public virtual async Task Ad_hoc_query_for_shared_type_entity_type_works() { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: c => c.SeedAsync()); using var context = contextFactory.CreateContext(); - var result = context.Database.SqlQueryRaw( - ((RelationalTestStore)TestStore).NormalizeDelimitersInRawString(@"SELECT * FROM [ViewQuery24601]")); + var result = context.Database.SqlQueryRaw( + ((RelationalTestStore)TestStore).NormalizeDelimitersInRawString(@"SELECT * FROM [ViewQuery]")); Assert.Empty(await result.ToListAsync()); } @@ -49,7 +49,7 @@ public virtual async Task Ad_hoc_query_for_shared_type_entity_type_works() [ConditionalFact] public virtual async Task Ad_hoc_query_for_default_shared_type_entity_type_throws() { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: c => c.SeedAsync()); using var context = contextFactory.CreateContext(); @@ -60,12 +60,12 @@ public virtual async Task Ad_hoc_query_for_default_shared_type_entity_type_throw () => context.Database.SqlQueryRaw>(@"SELECT * FROM X")).Message); } - protected class MyContextRelational24601(DbContextOptions options) : MyContext24601(options) + protected class ContextRelational24601(DbContextOptions options) : Context24601(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); - modelBuilder.Entity() + modelBuilder.Entity() .HasQueryFilter( e => Set>("STET") .FromSqlRaw("Select * from STET").Select(i => (string)i["Value"]).Contains(e.Value)); diff --git a/test/EFCore.Relational.Specification.Tests/Query/SqlExecutorTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/SqlExecutorTestBase.cs index 076c4ce1188..70c0fbc70e2 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/SqlExecutorTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/SqlExecutorTestBase.cs @@ -306,7 +306,7 @@ public virtual async Task Query_with_parameters_custom_converter(bool async) Assert.Equal(-1, actual); } - private static Customer Process(Customer c, ManualResetEventSlim e, SemaphoreSlim s) + public static Customer Process(Customer c, ManualResetEventSlim e, SemaphoreSlim s) { e.Set(); s.Wait(); diff --git a/test/EFCore.Relational.Specification.Tests/Query/ToSqlQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/ToSqlQueryTestBase.cs index 516f1b8511f..3bedb1520bc 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/ToSqlQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/ToSqlQueryTestBase.cs @@ -36,7 +36,7 @@ public virtual async Task Entity_type_with_navigation_mapped_to_SqlQuery(bool as Assert.Equal(10, authors[0].PostCount); } - protected class Context27629(DbContextOptions options) : DbContext(options) + public class Context27629(DbContextOptions options) : DbContext(options) { public DbSet Authors => Set(); @@ -85,7 +85,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } } - protected class Author + public class Author { public long Id { get; set; } public string Name { get; set; } = null!; @@ -93,7 +93,7 @@ protected class Author public PostStat? PostStat { get; set; } } - protected class Post + public class Post { public long Id { get; set; } public long AuthorId { get; set; } @@ -102,7 +102,7 @@ protected class Post public string? Content { get; set; } } - protected class PostStat + public class PostStat { public long AuthorId { get; set; } public Author Author { get; set; } = null!; diff --git a/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs index c43c07a2258..b92f6e223f0 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs @@ -104,7 +104,7 @@ public class CustomerData public string LastName { get; set; } } - protected class UDFSqlContext(DbContextOptions options) : PoolableDbContext(options) + public class UDFSqlContext(DbContextOptions options) : PoolableDbContext(options) { #region DbSets 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 @@ public async Task Data_methods_return_expected_results(bool async) 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..ec3b4ebbcea 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,15 @@ public class RelationalApiConsistencyFixture : ApiConsistencyFixtureBase typeof(IMutableStoredProcedure).GetMethod(nameof(IMutableStoredProcedure.AddResultColumn)) ]; + public override HashSet VirtualMethodExceptions { get; } = + [ + // non-sealed record + 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()]), + ]; + 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 5113a87a6e3..7bf233951af 100644 --- a/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs +++ b/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs @@ -1062,6 +1062,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) @@ -1070,6 +1071,10 @@ where type.IsVisible select type.FullName + "." + method.Name) .ToList(); + foreach (var nvm in nonVirtualMethods) + { + } + Assert.False( nonVirtualMethods.Count > 0, "\r\n-- Missing virtual APIs --\r\n" + string.Join(Environment.NewLine, nonVirtualMethods)); @@ -1273,6 +1278,13 @@ protected ApiConsistencyFixtureBase() public virtual Dictionary> UnmatchedMirrorMethods { get; } = new(); public virtual Dictionary MetadataMethodNameTransformers { get; } = new(); public virtual HashSet MetadataMethodExceptions { get; } = []; + public virtual HashSet VirtualMethodExceptions { get; } = + [ + // non-sealed record + typeof(MaterializerLiftableConstantContext).GetMethod("get_Dependencies"), + typeof(MaterializerLiftableConstantContext).GetMethod("set_Dependencies"), + typeof(MaterializerLiftableConstantContext).GetMethod("Deconstruct"), + ]; 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..cd59f7da060 100644 --- a/test/EFCore.Specification.Tests/MaterializationInterceptionTestBase.cs +++ b/test/EFCore.Specification.Tests/MaterializationInterceptionTestBase.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.Proxies.Internal; + namespace Microsoft.EntityFrameworkCore; public abstract class MaterializationInterceptionTestBase : SingletonInterceptorsTestBase @@ -494,6 +496,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/AdHocAdvancedMappingsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AdHocAdvancedMappingsQueryTestBase.cs index dcba2641818..a1605e25ca7 100644 --- a/test/EFCore.Specification.Tests/Query/AdHocAdvancedMappingsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/AdHocAdvancedMappingsQueryTestBase.cs @@ -24,7 +24,7 @@ public virtual async Task Setting_IsUnicode_generates_unicode_literal_in_SQL() var query = context.Set().Where(xx => xx.Nombre.Contains("lla")).ToList(); } - private class Context9582(DbContextOptions options) : DbContext(options) + public class Context9582(DbContextOptions options) : DbContext(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -82,7 +82,7 @@ public virtual async Task Projecting_correlated_collection_along_with_non_mapped } } - private class Context11835(DbContextOptions options) : DbContext(options) + public class Context11835(DbContextOptions options) : DbContext(options) { public DbSet Blogs { get; set; } public DbSet Posts { get; set; } @@ -144,7 +144,7 @@ public virtual async Task Projection_failing_with_EnumToStringConverter() Assert.Equal(2, result.Count); } - private class Context15684(DbContextOptions options) : DbContext(options) + public class Context15684(DbContextOptions options) : DbContext(options) { public DbSet Categories { get; set; } public DbSet Products { get; set; } @@ -238,7 +238,7 @@ public virtual async Task Expression_tree_constructed_via_interface_works() } } - private class Context17276(DbContextOptions options) : DbContext(options) + public class Context17276(DbContextOptions options) : DbContext(options) { public DbSet RemovableEntities { get; set; } public DbSet Parents { get; set; } @@ -311,7 +311,7 @@ public virtual async Task Double_convert_interface_created_expression_tree() Assert.Equal(1, query); } - private class Context17794(DbContextOptions options) : DbContext(options) + public class Context17794(DbContextOptions options) : DbContext(options) { public DbSet Offers { get; set; } public DbSet OfferActions { get; set; } @@ -407,7 +407,7 @@ public virtual async Task Casts_are_removed_from_expression_tree_when_redundant( } } - private class Context18087(DbContextOptions options) : DbContext(options) + public class Context18087(DbContextOptions options) : DbContext(options) { public DbSet MockEntities { get; set; } @@ -453,7 +453,7 @@ public virtual async Task Can_query_hierarchy_with_non_nullable_property_on_deri Assert.Equal(3, query.Count); } - private class Context18346(DbContextOptions options) : DbContext(options) + public class Context18346(DbContextOptions options) : DbContext(options) { public DbSet Businesses { get; set; } @@ -575,7 +575,7 @@ public virtual async Task Query_generates_correct_timespan_parameter_definition( _ = context.Entities.Where(x => x.TimeSpan == parameter).Select(e => e.TimeSpan).FirstOrDefault(); } - private class Context26742(DbContextOptions options) : DbContext(options) + public class Context26742(DbContextOptions options) : DbContext(options) { public DbSet Entities { get; set; } @@ -607,7 +607,7 @@ public virtual async Task Hierarchy_query_with_abstract_type_sibling_helper(bool : query.ToList(); } - protected class Context28196(DbContextOptions options) : DbContext(options) + public class Context28196(DbContextOptions options) : DbContext(options) { public DbSet Animals { get; set; } diff --git a/test/EFCore.Specification.Tests/Query/AdHocMiscellaneousQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AdHocMiscellaneousQueryTestBase.cs index 22223eb470e..def5d8dd823 100644 --- a/test/EFCore.Specification.Tests/Query/AdHocMiscellaneousQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/AdHocMiscellaneousQueryTestBase.cs @@ -41,7 +41,7 @@ public virtual async Task First_FirstOrDefault_ix_async() } } - private class Context603(DbContextOptions options) : DbContext(options) + public class Context603(DbContextOptions options) : DbContext(options) { public DbSet Products { get; set; } @@ -87,7 +87,7 @@ select new Assert.True(results[3].CustomerName != results[4].CustomerName); } - private class Context6901(DbContextOptions options) : DbContext(options) + public class Context6901(DbContextOptions options) : DbContext(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -218,7 +218,7 @@ public virtual async Task Shadow_property_with_inheritance() } } - private class Context6986(DbContextOptions options) : DbContext(options) + public class Context6986(DbContextOptions options) : DbContext(options) { public DbSet Contacts { get; set; } public DbSet EmployerContacts { get; set; } @@ -305,7 +305,7 @@ public virtual async Task Inlined_dbcontext_is_not_leaking() } } - private class Context7222(DbContextOptions options) : DbContext(options) + public class Context7222(DbContextOptions options) : DbContext(options) { public DbSet Blogs { get; set; } @@ -345,7 +345,7 @@ public virtual async Task Discriminator_type_is_handled_correctly() } } - private class Context7359(DbContextOptions options) : DbContext(options) + public class Context7359(DbContextOptions options) : DbContext(options) { public DbSet Products { get; set; } @@ -390,7 +390,7 @@ public virtual async Task New_instances_in_projection_are_not_shared_across_resu Assert.Equal(new[] { "First", "Second", "Third" }, list.Select(dto => dto.Title)); } - private class Context7983(DbContextOptions options) : DbContext(options) + public class Context7983(DbContextOptions options) : DbContext(options) { public DbSet Blogs { get; set; } public DbSet Posts { get; set; } @@ -480,7 +480,7 @@ public virtual async Task Enum_has_flag_does_not_apply_explicit_cast_for_non_con } } - private class Context8538(DbContextOptions options) : DbContext(options) + public class Context8538(DbContextOptions options) : DbContext(options) { public DbSet Entities { get; set; } @@ -668,7 +668,7 @@ public virtual async Task Explicitly_compiled_query_does_not_add_cache_entry() } } - private class Context8909(DbContextOptions options) : DbContext(options) + public class Context8909(DbContextOptions options) : DbContext(options) { public DbSet Entities { get; set; } @@ -708,7 +708,7 @@ public virtual async Task Conditional_expression_with_conditions_does_not_collap Assert.Single(query.Where(t => t.Processing == false)); } - private class Context9468(DbContextOptions options) : DbContext(options) + public class Context9468(DbContextOptions options) : DbContext(options) { public DbSet Carts { get; set; } @@ -752,7 +752,7 @@ public virtual async Task QueryBuffer_requirement_is_computed_when_querying_base Assert.Equal(typeof(Context11104.Derived1), derived1.GetType()); } - private class Context11104(DbContextOptions options) : DbContext(options) + public class Context11104(DbContextOptions options) : DbContext(options) { public DbSet Bases { get; set; } @@ -811,7 +811,7 @@ public virtual async Task Average_with_cast() Assert.Equal(prices.Average(e => e.NullableDecimalColumn), context.Prices.Average(e => e.NullableDecimalColumn)); } - private class Context11885(DbContextOptions options) : DbContext(options) + public class Context11885(DbContextOptions options) : DbContext(options) { public DbSet Prices { get; set; } @@ -909,7 +909,7 @@ public virtual async Task Parameterless_ctor_on_inner_DTO_gets_called_for_every_ Assert.False(ReferenceEquals(results[2].Inner, results[3].Inner)); } - private class Context12274(DbContextOptions options) : DbContext(options) + public class Context12274(DbContextOptions options) : DbContext(options) { public DbSet Entities { get; set; } @@ -974,7 +974,7 @@ public virtual async Task Union_and_insert_works_correctly_together() } } - private class Context12549(DbContextOptions options) : DbContext(options) + public class Context12549(DbContextOptions options) : DbContext(options) { public DbSet Tables1 { get; set; } public DbSet Tables2 { get; set; } @@ -1011,7 +1011,7 @@ public virtual async Task Repeated_parameters_in_generated_query_sql() Assert.Single(equalQuery); } - private class Context15215(DbContextOptions options) : DbContext(options) + public class Context15215(DbContextOptions options) : DbContext(options) { public DbSet Autos { get; set; } public DbSet EqualAutos { get; set; } @@ -1048,6 +1048,64 @@ public class EqualAuto #endregion + #region 15518 + + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Nested_queries_does_not_cause_concurrency_exception_sync(bool tracking) + { + var contextFactory = await InitializeAsync(seed: c => c.SeedAsync()); + + using (var context = contextFactory.CreateContext()) + { + var query = context.Repos.OrderBy(r => r.Id).Where(r => r.Id > 0); + query = tracking ? query.AsTracking() : query.AsNoTracking(); + + foreach (var a in query) + { + foreach (var b in query) + { + } + } + } + + using (var context = contextFactory.CreateContext()) + { + var query = context.Repos.OrderBy(r => r.Id).Where(r => r.Id > 0); + query = tracking ? query.AsTracking() : query.AsNoTracking(); + + await foreach (var a in query.AsAsyncEnumerable()) + { + await foreach (var b in query.AsAsyncEnumerable()) + { + } + } + } + } + + public class Context15518(DbContextOptions options) : DbContext(options) + { + public DbSet Repos { get; set; } + + public async Task SeedAsync() + { + await AddRangeAsync( + new Repo { Name = "London" }, + new Repo { Name = "New York" }); + + await SaveChangesAsync(); + } + + public class Repo + { + public int Id { get; set; } + public string Name { get; set; } + } + } + + #endregion + #region 19253 [ConditionalFact] @@ -1166,7 +1224,7 @@ public virtual async Task Operators_combine_nullability_of_entity_shapers() } } - private class Context19253(DbContextOptions options) : DbContext(options) + public class Context19253(DbContextOptions options) : DbContext(options) { public DbSet As { get; set; } public DbSet Bs { get; set; } @@ -1303,7 +1361,7 @@ public virtual async Task Null_check_removal_in_ternary_maintain_appropriate_cas : query.ToList(); } - private class Context21770(DbContextOptions options) : DbContext(options) + public class Context21770(DbContextOptions options) : DbContext(options) { public DbSet IceCreams { get; set; } public DbSet Foods { get; set; } @@ -1401,7 +1459,7 @@ public async virtual Task SaveChangesAsync_accepts_changes_with_ConfigureAwait_t Assert.True(isMySyncContext); } - private class Context22841(DbContextOptions options) : DbContext(options) + public class Context22841(DbContextOptions options) : DbContext(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) => modelBuilder @@ -1449,7 +1507,7 @@ public virtual async Task Bool_discriminator_column_works(bool async) Assert.Equal(2, authors.Count); } - private class Context24657(DbContextOptions options) : DbContext(options) + public class Context24657(DbContextOptions options) : DbContext(options) { public DbSet Authors { get; set; } @@ -1578,7 +1636,7 @@ public virtual async Task Unwrap_convert_node_over_projection_when_translating_c : query.ToList(); } - private class Context26593(DbContextOptions options) : DbContext(options) + public class Context26593(DbContextOptions options) : DbContext(options) { public DbSet Users { get; set; } public DbSet Groups { get; set; } @@ -1650,7 +1708,7 @@ public virtual async Task GroupBy_aggregate_on_right_side_of_join(bool async) : query.ToList(); } - private class Context26587(DbContextOptions options) : DbContext(options) + public class Context26587(DbContextOptions options) : DbContext(options) { public DbSet OrderItems { get; set; } @@ -1691,7 +1749,7 @@ public virtual async Task Enum_with_value_converter_matching_take_value(bool asy : query.ToList(); } - private class Context26472(DbContextOptions options) : DbContext(options) + public class Context26472(DbContextOptions options) : DbContext(options) { public virtual DbSet Orders { get; set; } public virtual DbSet OrderItems { get; set; } @@ -1797,7 +1855,7 @@ public virtual async Task Aggregate_over_subquery_in_group_by_projection(bool as }); } - private class Context27083(DbContextOptions options) : DbContext(options) + public class Context27083(DbContextOptions options) : DbContext(options) { public DbSet TimeSheets { get; set; } public DbSet Customers { get; set; } @@ -1930,7 +1988,7 @@ select tg.Sum() + tg2.Sum() : query.ToList(); } - private class Context27094(DbContextOptions options) : DbContext(options) + public class Context27094(DbContextOptions options) : DbContext(options) { public DbSet Tables { get; set; } @@ -1994,7 +2052,7 @@ public virtual async Task SelectMany_where_Select(bool async) Assert.Single(result); } - private class Context26744(DbContextOptions options) : DbContext(options) + public class Context26744(DbContextOptions options) : DbContext(options) { public DbSet Parents { get; set; } @@ -2047,7 +2105,7 @@ public virtual async Task Flattened_GroupJoin_on_interface_generic(bool async) Assert.Empty(result); } - private class Context27343(DbContextOptions options) : DbContext(options) + public class Context27343(DbContextOptions options) : DbContext(options) { public DbSet Parents { get; set; } @@ -2104,7 +2162,7 @@ orderby dcv.ParcelNumber : jsonLookup.ToList(); } - private class Context28039(DbContextOptions options) : DbContext(options) + public class Context28039(DbContextOptions options) : DbContext(options) { public DbSet IndexDatas { get; set; } public DbSet TableDatas { get; set; } @@ -2171,7 +2229,7 @@ public virtual async Task Filter_on_nested_DTO_with_interface_gets_simplified_co .ToListAsync(); } - private class Context31961(DbContextOptions options) : DbContext(options) + public class Context31961(DbContextOptions options) : DbContext(options) { public DbSet Customers { get; set; } diff --git a/test/EFCore.Specification.Tests/Query/AdHocNavigationsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AdHocNavigationsQueryTestBase.cs index c0d03f86d60..229904ee520 100644 --- a/test/EFCore.Specification.Tests/Query/AdHocNavigationsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/AdHocNavigationsQueryTestBase.cs @@ -79,7 +79,7 @@ public virtual async Task ThenInclude_with_interface_navigations() } } - private class Context3409(DbContextOptions options) : DbContext(options) + public class Context3409(DbContextOptions options) : DbContext(options) { public DbSet Parents { get; set; } public DbSet Children { get; set; } @@ -195,7 +195,7 @@ public virtual async Task Customer_collections_materialize_properly() Assert.Throws(() => query4.ToList()).Message); } - protected class Context3758(DbContextOptions options) : DbContext(options) + public class Context3758(DbContextOptions options) : DbContext(options) { public DbSet Customers { get; set; } public DbSet Orders { get; set; } @@ -313,7 +313,7 @@ public virtual async Task Reference_include_on_derived_type_with_sibling_works() } } - private class Context7312(DbContextOptions options) : DbContext(options) + public class Context7312(DbContextOptions options) : DbContext(options) { public DbSet Proposals { get; set; } public DbSet ProposalCustoms { get; set; } @@ -387,7 +387,7 @@ public virtual async Task Include_collection_optional_reference_collection() } } - private class Context9038(DbContextOptions options) : DbContext(options) + public class Context9038(DbContextOptions options) : DbContext(options) { public DbSet People { get; set; } @@ -493,7 +493,7 @@ public virtual async Task Include_with_order_by_on_interface_key() } } - private class Context10635(DbContextOptions options) : DbContext(options) + public class Context10635(DbContextOptions options) : DbContext(options) { public DbSet Parents { get; set; } public DbSet Children { get; set; } @@ -570,7 +570,7 @@ public virtual async Task Collection_without_setter_materialized_correctly() }).ToList()); } - private class Context11923(DbContextOptions options) : DbContext(options) + public class Context11923(DbContextOptions options) : DbContext(options) { public DbSet Blogs { get; set; } public DbSet Posts { get; set; } @@ -689,7 +689,7 @@ public virtual async Task Include_collection_works_when_defined_on_intermediate_ } } - protected class Context11944(DbContextOptions options) : DbContext(options) + public class Context11944(DbContextOptions options) : DbContext(options) { public DbSet Students { get; set; } public DbSet Schools { get; set; } @@ -769,7 +769,7 @@ public virtual async Task Let_multiple_references_with_reference_to_outer() } } - private class Context12456(DbContextOptions options) : DbContext(options) + public class Context12456(DbContextOptions options) : DbContext(options) { public DbSet Activities { get; set; } public DbSet CompetitionSeasons { get; set; } @@ -850,7 +850,7 @@ public virtual async Task Include_collection_with_OfType_base() } } - private class Context12582(DbContextOptions options) : DbContext(options) + public class Context12582(DbContextOptions options) : DbContext(options) { public DbSet Employees { get; set; } public DbSet Devices { get; set; } @@ -912,7 +912,7 @@ select new Assert.Single(result[0].Comments); } - private class Context12748(DbContextOptions options) : DbContext(options) + public class Context12748(DbContextOptions options) : DbContext(options) { public DbSet Blogs { get; set; } public DbSet Comments { get; set; } @@ -954,7 +954,7 @@ public virtual async Task Can_ignore_invalid_include_path_error() var result = context.Set().Include("SubB").ToList(); } - protected class Context20609(DbContextOptions options) : DbContext(options) + public class Context20609(DbContextOptions options) : DbContext(options) { public DbSet BaseClasses { get; set; } public DbSet SubAs { get; set; } @@ -1019,7 +1019,7 @@ public virtual async Task SelectMany_and_collection_in_projection_in_FirstOrDefa }).SingleOrDefault(); } - private class Context20813(DbContextOptions options) : DbContext(options) + public class Context20813(DbContextOptions options) : DbContext(options) { public DbSet Orders { get; set; } @@ -1097,7 +1097,7 @@ public virtual async Task Using_explicit_interface_implementation_as_navigation_ var result = context.Books.Where(b => b.Id == 1).Select(projection).SingleOrDefault(); } - private class Context21768(DbContextOptions options) : DbContext(options) + public class Context21768(DbContextOptions options) : DbContext(options) { public DbSet Books { get; set; } public DbSet BookCovers { get; set; } @@ -1263,7 +1263,7 @@ public virtual async Task Cycles_in_auto_include() } } - protected class Context22568(DbContextOptions options) : DbContext(options) + public class Context22568(DbContextOptions options) : DbContext(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -1440,7 +1440,7 @@ public virtual async Task Walking_back_include_tree_is_not_allowed_4() } } - private class Context23674(DbContextOptions options) : DbContext(options) + public class Context23674(DbContextOptions options) : DbContext(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) => modelBuilder.Entity(); @@ -1525,7 +1525,7 @@ public virtual async Task Projection_with_multiple_includes_and_subquery_with_se .FirstOrDefaultAsync(x => x.Id == id); } - private class Context23676(DbContextOptions options) : DbContext(options) + public class Context23676(DbContextOptions options) : DbContext(options) { public DbSet Persons { get; set; } @@ -1633,7 +1633,7 @@ public virtual async Task Count_member_over_IReadOnlyCollection_works(bool async Assert.Equal(3, Assert.Single(authors).BooksCount); } - protected class Context26433(DbContextOptions options) : DbContext(options) + public class Context26433(DbContextOptions options) : DbContext(options) { public DbSet Books { get; set; } public DbSet Authors { get; set; } diff --git a/test/EFCore.Specification.Tests/Query/AdHocQueryFiltersQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AdHocQueryFiltersQueryTestBase.cs index c9cd6ab37f2..61776ad116f 100644 --- a/test/EFCore.Specification.Tests/Query/AdHocQueryFiltersQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/AdHocQueryFiltersQueryTestBase.cs @@ -21,7 +21,7 @@ public virtual async Task Query_filter_with_contains_evaluates_correctly() Assert.Single(result); } - protected class Context10295(DbContextOptions options) : DbContext(options) + public class Context10295(DbContextOptions options) : DbContext(options) { private readonly List _ids = [1, 7]; @@ -66,7 +66,7 @@ public virtual async Task MultiContext_query_filter_test() } } - protected class FilterContextBase10301(DbContextOptions options) : DbContext(options) + public class FilterContextBase10301(DbContextOptions options) : DbContext(options) { public int Tenant { get; set; } @@ -109,7 +109,7 @@ public virtual async Task Weak_entities_with_query_filter_subquery_flattening() Assert.False(result); } - protected class Context12170(DbContextOptions options) : DbContext(options) + public class Context12170(DbContextOptions options) : DbContext(options) { public virtual DbSet Definitions { get; set; } public virtual DbSet DefinitionHistories { get; set; } @@ -177,7 +177,7 @@ public virtual async Task Query_filter_with_pk_fk_optimization() }).Single(p => p.Id == 1); } - protected class Context13517(DbContextOptions options) : DbContext(options) + public class Context13517(DbContextOptions options) : DbContext(options) { public DbSet Entities { get; set; } public DbSet RefEntities { get; set; } @@ -242,7 +242,7 @@ public virtual async Task Self_reference_in_query_filter_works() } } - protected class Context17253(DbContextOptions options) : DbContext(options) + public class Context17253(DbContextOptions options) : DbContext(options) { public DbSet EntitiesWithQueryFilterSelfReference { get; set; } @@ -334,7 +334,7 @@ public virtual async Task Invoke_inside_query_filter_gets_correctly_evaluated_du Assert.True(query2.All(x => x.TenantId == 2)); } - protected class Context18510(DbContextOptions options) : DbContext(options) + public class Context18510(DbContextOptions options) : DbContext(options) { public DbSet Entities { get; set; } @@ -396,7 +396,7 @@ public virtual async Task Query_filter_with_null_constant() var people = context.People.ToList(); } - protected class Context18759(DbContextOptions options) : DbContext(options) + public class Context18759(DbContextOptions options) : DbContext(options) { public DbSet People { get; set; } @@ -454,7 +454,7 @@ public virtual async Task GroupJoin_SelectMany_gets_flattened() } } - protected class Context19708(DbContextOptions options) : DbContext(options) + public class Context19708(DbContextOptions options) : DbContext(options) { public DbSet Customers { get; set; } public DbSet CustomerMemberships { get; set; } @@ -564,7 +564,7 @@ public virtual async Task IsDeleted_query_filter_with_conversion_to_int_works(bo Assert.Single(suppliers.Where(e => e.Location != null)); } - protected class Context26428(DbContextOptions options) : DbContext(options) + public class Context26428(DbContextOptions options) : DbContext(options) { public DbSet Suppliers => Set(); @@ -606,21 +606,21 @@ public Task SeedAsync() return SaveChangesAsync(); } - } - protected class Supplier - { - public Guid SupplierId { get; set; } - public string Name { get; set; } = null!; - public Location? Location { get; set; } - public bool IsDeleted { get; set; } - } + public class Supplier + { + public Guid SupplierId { get; set; } + public string Name { get; set; } = null!; + public Location? Location { get; set; } + public bool IsDeleted { get; set; } + } - protected class Location - { - public Guid LocationId { get; set; } - public string Address { get; set; } = null!; - public bool IsDeleted { get; set; } + public class Location + { + public Guid LocationId { get; set; } + public string Address { get; set; } = null!; + public bool IsDeleted { get; set; } + } } #nullable disable @@ -683,7 +683,7 @@ public virtual async Task Group_by_multiple_aggregate_joining_different_tables_w : query.ToList(); } - protected class Context27163(DbContextOptions options) : DbContext(options) + public class Context27163(DbContextOptions options) : DbContext(options) { public DbSet Parents { get; set; } @@ -692,41 +692,41 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().HasQueryFilter(e => e.Filter1 == "Filter1"); modelBuilder.Entity().HasQueryFilter(e => e.Filter2 == "Filter2"); } - } - public class Parent - { - public int Id { get; set; } - public Child1 Child1 { get; set; } - public Child2 Child2 { get; set; } - public ChildFilter1 ChildFilter1 { get; set; } - public ChildFilter2 ChildFilter2 { get; set; } - } + public class Parent + { + public int Id { get; set; } + public Child1 Child1 { get; set; } + public Child2 Child2 { get; set; } + public ChildFilter1 ChildFilter1 { get; set; } + public ChildFilter2 ChildFilter2 { get; set; } + } - public class Child1 - { - public int Id { get; set; } - public string Value1 { get; set; } - } + public class Child1 + { + public int Id { get; set; } + public string Value1 { get; set; } + } - public class Child2 - { - public int Id { get; set; } - public string Value2 { get; set; } - } + public class Child2 + { + public int Id { get; set; } + public string Value2 { get; set; } + } - public class ChildFilter1 - { - public int Id { get; set; } - public string Filter1 { get; set; } - public string Value1 { get; set; } - } + public class ChildFilter1 + { + public int Id { get; set; } + public string Filter1 { get; set; } + public string Value1 { get; set; } + } - public class ChildFilter2 - { - public int Id { get; set; } - public string Filter2 { get; set; } - public string Value2 { get; set; } + public class ChildFilter2 + { + public int Id { get; set; } + public string Filter2 { get; set; } + public string Value2 { get; set; } + } } #endregion diff --git a/test/EFCore.Specification.Tests/Query/ComplexNavigationsCollectionsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ComplexNavigationsCollectionsQueryTestBase.cs index cfce35aead0..5e99f51e477 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexNavigationsCollectionsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexNavigationsCollectionsQueryTestBase.cs @@ -256,7 +256,7 @@ public virtual Task Null_check_in_Dto_projection_should_not_be_removed(bool asyn elementSorter: ee => ee.Level3?.Value, elementAsserter: (ee, aa) => Assert.Equal(ee.Level3?.Value, aa.Level3?.Value))); - private class ProjectedDto + public class ProjectedDto { public T Value { get; set; } } diff --git a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs index c7deae9cbcc..9959844d550 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs @@ -1815,7 +1815,7 @@ select l2_inner Assert.Equal(e.property, a.property); }); - private static TResult ClientMethodReturnSelf(TResult element) + public static TResult ClientMethodReturnSelf(TResult element) => element; [ConditionalTheory] @@ -2080,7 +2080,7 @@ orderby ClientMethodNullableInt(l1.Id), ClientMethodNullableInt(l2 != null ? l2. select l1.Id, assertOrder: true); - private static int ClientMethodNullableInt(int? id) + public static int ClientMethodNullableInt(int? id) => id ?? 0; [ConditionalTheory] @@ -3322,14 +3322,14 @@ public virtual Task Member_over_null_check_ternary_and_nested_dto_type(bool asyn } }); - private class Level1Dto + public class Level1Dto { public int Id { get; set; } public string Name { get; set; } public Level2Dto Level2 { get; set; } } - private class Level2Dto + public class Level2Dto { public int Id { get; set; } public string Name { get; set; } diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index c27b941fb35..9611194bc8e 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -4468,7 +4468,7 @@ public virtual Task Project_one_value_type_with_client_projection_from_empty_col }), elementSorter: s => s.Name); - private static int ClientFunction(int a, int b) + public static int ClientFunction(int a, int b) => a + b + 1; [ConditionalTheory] @@ -8386,7 +8386,8 @@ public virtual Task Find_underlying_property_after_GroupJoin_DefaultIfEmpty(bool Assert.Equal(e.ThreatLevel, a.ThreatLevel); }); - private class GearLocustLeaderDto + // public because AOT + public class GearLocustLeaderDto { public string FullName { get; set; } public int? ThreatLevel { get; set; } diff --git a/test/EFCore.Specification.Tests/Query/IncludeOneToOneTestBase.cs b/test/EFCore.Specification.Tests/Query/IncludeOneToOneTestBase.cs index 5e7ffac6e56..9c7d268832d 100644 --- a/test/EFCore.Specification.Tests/Query/IncludeOneToOneTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/IncludeOneToOneTestBase.cs @@ -266,14 +266,14 @@ protected override Task SeedAsync(PoolableDbContext context) } } - protected class Person + public class Person { public int Id { get; set; } public string Name { get; set; } public Address Address { get; set; } } - protected class Address + public class Address { public int Id { get; set; } public string Street { get; set; } @@ -281,7 +281,7 @@ protected class Address public Person Resident { get; set; } } - protected class Person2 + public class Person2 { public int Id { get; set; } public string Name { get; set; } @@ -290,7 +290,7 @@ protected class Person2 public Address2 Address { get; set; } } - protected class Address2 + public class Address2 { public string Id { get; set; } public string Street { get; set; } diff --git a/test/EFCore.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryTestBase.cs index 6357dfe2c77..fe308cd7a3b 100644 --- a/test/EFCore.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryTestBase.cs @@ -107,17 +107,17 @@ public virtual async Task Multidimensional_array_is_not_supported() { var exception = await Assert.ThrowsAsync( () => InitializeAsync( - onModelCreating: mb => mb.Entity().Property(typeof(int[,]), "MultidimensionalArray"))); + onModelCreating: mb => mb.Entity().Property(typeof(int[,]), "MultidimensionalArray"))); Assert.Equal(CoreStrings.PropertyNotMapped("int[,]", "TestEntity", "MultidimensionalArray"), exception.Message); } #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( - onModelCreating: mb => mb.Entity() + onModelCreating: mb => mb.Entity() .Property(m => m.Ints) .HasConversion( i => string.Join(",", i!), @@ -126,19 +126,19 @@ public virtual async Task Column_with_custom_converter() seed: context => { context.AddRange( - new TestEntity { Id = 1, Ints = [1, 2, 3] }, - new TestEntity { Id = 2, Ints = [1, 2, 4] }); + new TestContext.TestEntity { Id = 1, Ints = [1, 2, 3] }, + new TestContext.TestEntity { Id = 2, Ints = [1, 2, 4] }); return context.SaveChangesAsync(); }); await using var context = contextFactory.CreateContext(); var ints = new[] { 1, 2, 3 }; - var result = await context.Set().SingleAsync(m => m.Ints == ints); + var result = await context.Set().SingleAsync(m => m.Ints == ints); Assert.Equal(1, result.Id); // Custom converters allow reading/writing, but not querying, as we have no idea about the internal representation - await AssertTranslationFailed(() => context.Set().SingleAsync(m => m.Ints!.Length == 2)); + await AssertTranslationFailed(() => context.Set().SingleAsync(m => m.Ints!.Length == 2)); } [ConditionalFact( @@ -148,14 +148,14 @@ public virtual async Task Parameter_with_inferred_value_converter() { var contextFactory = await InitializeAsync( onModelCreating: mb => mb - .Entity() + .Entity() .Property("PropertyWithValueConverter") .HasConversion(w => w.Value, i => new IntWrapper(i)), seed: context => { - var entry1 = context.Add(new TestEntity { Id = 1 }); + var entry1 = context.Add(new TestContext.TestEntity { Id = 1 }); entry1.Property("PropertyWithValueConverter").CurrentValue = new IntWrapper(8); - var entry2 = context.Add(new TestEntity { Id = 2 }); + var entry2 = context.Add(new TestContext.TestEntity { Id = 2 }); entry2.Property("PropertyWithValueConverter").CurrentValue = new IntWrapper(9); return context.SaveChangesAsync(); }); @@ -163,7 +163,7 @@ public virtual async Task Parameter_with_inferred_value_converter() await using var context = contextFactory.CreateContext(); var ints = new IntWrapper[] { new(1), new(8) }; - var result = await context.Set() + var result = await context.Set() .SingleAsync(m => ints.Count(i => i == EF.Property(m, "PropertyWithValueConverter")) == 1); Assert.Equal(1, result.Id); } @@ -173,21 +173,21 @@ public virtual async Task Constant_with_inferred_value_converter() { var contextFactory = await InitializeAsync( onModelCreating: mb => mb - .Entity() + .Entity() .Property("PropertyWithValueConverter") .HasConversion(w => w.Value, i => new IntWrapper(i)), seed: context => { - var entry1 = context.Add(new TestEntity { Id = 1 }); + var entry1 = context.Add(new TestContext.TestEntity { Id = 1 }); entry1.Property("PropertyWithValueConverter").CurrentValue = new IntWrapper(8); - var entry2 = context.Add(new TestEntity { Id = 2 }); + var entry2 = context.Add(new TestContext.TestEntity { Id = 2 }); entry2.Property("PropertyWithValueConverter").CurrentValue = new IntWrapper(9); return context.SaveChangesAsync(); }); await using var context = contextFactory.CreateContext(); - var result = await context.Set() + var result = await context.Set() .SingleAsync( m => new IntWrapper[] { new(1), new(8) }.Count(i => i == EF.Property(m, "PropertyWithValueConverter")) == 1); Assert.Equal(1, result.Id); @@ -202,18 +202,18 @@ private class IntWrapper(int value) public virtual async Task Inline_collection_in_query_filter() { var contextFactory = await InitializeAsync( - onModelCreating: mb => mb.Entity().HasQueryFilter(t => new[] { 1, 2, 3 }.Count(i => i > t.Id) == 1), + onModelCreating: mb => mb.Entity().HasQueryFilter(t => new[] { 1, 2, 3 }.Count(i => i > t.Id) == 1), seed: context => { context.AddRange( - new TestEntity { Id = 1 }, - new TestEntity { Id = 2 }); + new TestContext.TestEntity { Id = 1 }, + new TestContext.TestEntity { Id = 2 }); return context.SaveChangesAsync(); }); await using var context = contextFactory.CreateContext(); - var result = await context.Set().SingleAsync(); + var result = await context.Set().SingleAsync(); Assert.Equal(2, result.Id); } @@ -229,17 +229,17 @@ public virtual async Task Inline_collection_in_query_filter() var arrayClrType = typeof(TElement).MakeArrayType(); var contextFactory = await InitializeAsync( - onModelCreating: onModelCreating ?? (mb => mb.Entity().Property(arrayClrType, "SomeArray")), + onModelCreating: onModelCreating ?? (mb => mb.Entity().Property(arrayClrType, "SomeArray")), seed: context => { - var instance1 = new TestEntity { Id = 1 }; + var instance1 = new TestContext.TestEntity { Id = 1 }; context.Add(instance1); var array1 = new TElement[2]; array1.SetValue(value1, 0); array1.SetValue(value1, 1); context.Entry(instance1).Property("SomeArray").CurrentValue = array1; - var instance2 = new TestEntity { Id = 2 }; + var instance2 = new TestContext.TestEntity { Id = 2 }; context.Add(instance2); var array2 = new TElement[2]; array2.SetValue(value1, 0); @@ -251,14 +251,14 @@ public virtual async Task Inline_collection_in_query_filter() await using var context = contextFactory.CreateContext(); - var entityParam = Parameter(typeof(TestEntity), "m"); + var entityParam = Parameter(typeof(TestContext.TestEntity), "m"); var efPropertyCall = Call( typeof(EF).GetMethod(nameof(EF.Property), BindingFlags.Public | BindingFlags.Static)!.MakeGenericMethod(arrayClrType), entityParam, Constant("SomeArray")); var elementParam = Parameter(typeof(TElement), "a"); - var predicate = Lambda>( + var predicate = Lambda>( Equal( Call( EnumerableMethods.CountWithPredicate.MakeGenericMethod(typeof(TElement)), @@ -270,20 +270,20 @@ public virtual async Task Inline_collection_in_query_filter() entityParam); // context.Set().SingleAsync(m => EF.Property(m, "SomeArray").Count(a => a == ) == 2) - var result = await context.Set().SingleAsync(predicate); + var result = await context.Set().SingleAsync(predicate); Assert.Equal(1, result.Id); } - protected class TestContext(DbContextOptions options) : DbContext(options) + public class TestContext(DbContextOptions options) : DbContext(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) => modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever(); - } - protected class TestEntity - { - public int Id { get; set; } - public int[]? Ints { get; set; } + public class TestEntity + { + public int Id { get; set; } + public int[]? Ints { get; set; } + } } protected override string StoreName diff --git a/test/EFCore.Specification.Tests/Query/NorthwindAggregateOperatorsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindAggregateOperatorsQueryTestBase.cs index 0f5317eea88..e80afe54246 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindAggregateOperatorsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindAggregateOperatorsQueryTestBase.cs @@ -2072,12 +2072,12 @@ public virtual Task Type_casting_inside_sum(bool async) ss => ss.Set(), x => (decimal)x.Discount); - private class CustomerIdDto + public class CustomerIdDto { public string CustomerId { get; set; } } - private class CustomerIdAndCityDto : CustomerIdDto + public class CustomerIdAndCityDto : CustomerIdDto { public string City { get; set; } } diff --git a/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs index a19ff9c3cd8..0b140d38e3e 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs @@ -342,7 +342,7 @@ public virtual Task Group_by_with_projection_into_DTO(bool async) Assert.Equal(e.Count, a.Count); }); - private class LongIntDto + public class LongIntDto { public long Id { get; set; } public int Count { get; set; } @@ -698,7 +698,7 @@ public virtual Task GroupBy_Dto_as_element_selector_Select_Sum(bool async) g => new { Sum = g.Sum(o => o.EmployeeID), g.Key })); - protected class NominalType + public class NominalType { public string CustomerID { get; set; } public uint? EmployeeID { get; set; } @@ -737,7 +737,7 @@ public virtual Task GroupBy_Composite_Select_Dto_Sum_Min_Key_flattened_Max_Avg(b }), e => e.CustomerId + " " + e.EmployeeId); - protected class CompositeDto + public class CompositeDto { public int Sum { get; set; } public int Min { get; set; } @@ -1019,7 +1019,7 @@ public virtual Task Odata_groupby_empty_key(bool async) Assert.Equal(e.Container.Value, a.Container.Value); }); - private class NoGroupByWrapper + public class NoGroupByWrapper { public override bool Equals(object obj) => obj != null @@ -1030,12 +1030,12 @@ public override int GetHashCode() => 0; } - private class NoGroupByAggregationWrapper + public class NoGroupByAggregationWrapper { public LastInChain Container { get; set; } } - protected class LastInChain + public class LastInChain { public string Name { get; set; } public object Value { get; set; } @@ -2662,13 +2662,13 @@ public virtual Task Final_GroupBy_nominal_type_entity(bool async) elementSorter: e => e.Key.City, elementAsserter: (e, a) => AssertGrouping(e, a, keyAsserter: (ee, aa) => AssertEqual(ee.City, aa.City))); - protected class RandomClass + public class RandomClass { public string City { get; set; } public int Constant { get; set; } } - protected class RandomClassEqualityComparer : IEqualityComparer + public class RandomClassEqualityComparer : IEqualityComparer { public bool Equals(RandomClass x, RandomClass y) => x.City == y.City && x.Constant == y.Constant; diff --git a/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs index afb30ac6021..3df1f5914ff 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs @@ -1269,7 +1269,7 @@ public virtual Task Include_is_not_ignored_when_projection_contains_client_metho orderby e.EmployeeID select e.Manager != null ? "Employee " + ClientMethod(e) : ""); - private static string ClientMethod(Employee e) + public static string ClientMethod(Employee e) => e.FirstName + " reports to " + e.Manager.FirstName; // Issue#18672 diff --git a/test/EFCore.Specification.Tests/Query/NorthwindJoinQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindJoinQueryTestBase.cs index 509266576f3..2d1a7b16f61 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindJoinQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindJoinQueryTestBase.cs @@ -726,7 +726,7 @@ public virtual Task SelectMany_with_client_eval_with_collection_shaper_ignored(b .Select(e => new { e.OrderProperty, e.CustomerProperty }), elementSorter: e => e.OrderProperty); - private static int ClientMethod(Order order) + public static int ClientMethod(Order order) => order.OrderID; [ConditionalTheory] @@ -756,7 +756,7 @@ public virtual Task SelectMany_with_client_eval_with_constructor(bool async) a.Views.OrderBy(od => od.OrderID).ThenBy(od => od.ProductID)); }); - private class CustomerViewModel(string customerID, string city, OrderDetailViewModel[] views) + public class CustomerViewModel(string customerID, string city, OrderDetailViewModel[] views) { public string CustomerID { get; } = customerID; public string City { get; } = city; @@ -783,7 +783,7 @@ public override int GetHashCode() => HashCode.Combine(CustomerID, City); } - private class OrderDetailViewModel(int orderID, int productID) + public class OrderDetailViewModel(int orderID, int productID) { public int OrderID { get; } = orderID; public int ProductID { get; } = productID; diff --git a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs index a100eae5b3a..f1347de3a67 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs @@ -428,7 +428,7 @@ public virtual Task Entity_equality_through_DTO_projection(bool async) .Select(o => new CustomerWrapper { Customer = o.Customer }) .Where(x => x.Customer != null)); - private class CustomerWrapper + public class CustomerWrapper { public Customer Customer { get; set; } @@ -1360,7 +1360,7 @@ public virtual Task Select_DTO_with_member_init_distinct_in_subquery_used_in_pro Assert.Equal(e.o.Count, a.o.Count); }); - private class OrderCountDTO + public class OrderCountDTO { public string Id { get; set; } public int Count { get; set; } @@ -3974,7 +3974,7 @@ public virtual Task Anonymous_subquery_orderby(bool async) .OrderBy(n => n.A), assertOrder: true); - protected class DTO + public class DTO { public T Property { get; set; } @@ -5093,7 +5093,7 @@ public virtual Task MemberInitExpression_NewExpression_is_funcletized_even_when_ elementAsserter: (e, a) => Assert.Equal(e.CustomerID, a.CustomerID)); } - private class Dto(string value) + public class Dto(string value) { public string Value { get; } = value; public string CustomerID { get; set; } @@ -5250,7 +5250,7 @@ public virtual Task AsEnumerable_over_string(bool async) assertOrder: true, elementAsserter: (e, a) => Assert.True(e.Property.SequenceEqual(a.Property))); - private static int ClientMethod(int s) + public static int ClientMethod(int s) => s; [ConditionalTheory] diff --git a/test/EFCore.Specification.Tests/Query/NorthwindNavigationsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindNavigationsQueryTestBase.cs index 4ca77533613..852cbed7f77 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindNavigationsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindNavigationsQueryTestBase.cs @@ -178,7 +178,7 @@ public virtual Task Select_collection_FirstOrDefault_project_anonymous_type_clie o => new { o.CustomerID, OrderID = ClientFunction(o.OrderID, 5) }).FirstOrDefault()), assertOrder: true); - private static int ClientFunction(int a, int b) + public static int ClientFunction(int a, int b) => a + b + 1; [ConditionalTheory] diff --git a/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs index 232441b4683..cf4b400debb 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs @@ -1249,7 +1249,7 @@ public virtual Task Client_method_in_projection_requiring_materialization_2(bool async, ss => ss.Set().Where(c => c.CustomerID.StartsWith("A")).Select(c => ClientMethod(c))); - private static string ClientMethod(Customer c) + public static string ClientMethod(Customer c) => c.CustomerID; [ConditionalTheory] @@ -1604,7 +1604,7 @@ public virtual Task Projection_with_parameterized_constructor_with_member_assign elementSorter: e => e.Customer.CustomerID, elementAsserter: (e, a) => Assert.Equal(e.Customer, a.Customer)); - private class CustomerWrapper(Customer customer) + public class CustomerWrapper(Customer customer) { public string City { get; set; } public Customer Customer { get; } = customer; @@ -1681,7 +1681,7 @@ public virtual Task Projection_custom_type_in_both_sides_of_ternary(bool async) Assert.Equal(e.Name, a.Name); }); - private class IdName + public class IdName { public T Id { get; set; } public string Name { get; set; } @@ -2084,7 +2084,7 @@ public virtual Task Ternary_in_client_eval_assigns_correct_types(bool async) AssertEqual(e.OrderDate2, a.OrderDate2); }); - private static string ClientMethod(string s) + public static string ClientMethod(string s) => s; [ConditionalTheory] @@ -2252,7 +2252,7 @@ public virtual Task Client_projection_via_ctor_arguments(bool async) Assert.Equal(e.OrderCount, a.OrderCount); }); - private class CustomerDetailsWithCount(string customerID, string city, List orderInfos, int orderCount) + public class CustomerDetailsWithCount(string customerID, string city, List orderInfos, int orderCount) { public string CustomerID { get; } = customerID; public string City { get; } = city; @@ -2260,7 +2260,7 @@ private class CustomerDetailsWithCount(string customerID, string city, List Dependents { get; set; } } - protected class WithStringFk + public class WithStringFk { public string Id { get; set; } @@ -161,14 +161,14 @@ protected class WithStringFk public WithStringFk Self { get; set; } } - protected class WithIntKey + public class WithIntKey { public int Id { get; set; } public ICollection Dependents { get; set; } } - protected class WithNullableIntFk + public class WithNullableIntFk { public int Id { get; set; } @@ -176,14 +176,14 @@ protected class WithNullableIntFk public WithIntKey Principal { get; set; } } - protected class WithNullableIntKey + public class WithNullableIntKey { public int? Id { get; set; } public ICollection Dependents { get; set; } } - protected class WithIntFk + public class WithIntFk { public int Id { get; set; } @@ -191,14 +191,14 @@ protected class WithIntFk public WithNullableIntKey Principal { get; set; } } - protected class WithAllNullableIntKey + public class WithAllNullableIntKey { public int? Id { get; set; } public ICollection Dependents { get; set; } } - protected class WithAllNullableIntFk + public class WithAllNullableIntFk { public int Id { get; set; } diff --git a/test/EFCore.Specification.Tests/Query/OwnedEntityQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/OwnedEntityQueryTestBase.cs index 189928999a3..9454c3d07a5 100644 --- a/test/EFCore.Specification.Tests/Query/OwnedEntityQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/OwnedEntityQueryTestBase.cs @@ -42,7 +42,7 @@ public virtual async Task Include_collection_for_entity_with_owned_type_works() } } - private class Context9202(DbContextOptions options) : DbContext(options) + public class Context9202(DbContextOptions options) : DbContext(options) { public DbSet Movies { get; set; } public DbSet Actors { get; set; } @@ -113,7 +113,7 @@ public virtual async Task Multilevel_owned_entities_determine_correct_nullabilit await context.SaveChangesAsync(); } - private class Context13079(DbContextOptions options) : DbContext(options) + public class Context13079(DbContextOptions options) : DbContext(options) { public virtual DbSet BaseEntities { get; set; } @@ -182,7 +182,7 @@ public virtual async Task Correlated_subquery_with_owned_navigation_being_compar } } - private class Context13157(DbContextOptions options) : DbContext(options) + public class Context13157(DbContextOptions options) : DbContext(options) { public virtual DbSet Partners { get; set; } @@ -237,7 +237,7 @@ public virtual async Task Owned_entity_multiple_level_in_aggregate() 20, aggregate.FirstValueObject.SecondValueObjects[0].ThirdValueObjects[0].FourthValueObject.FifthValueObjects[0].AnyValue); } - protected class Context14911(DbContextOptions options) : DbContext(options) + public class Context14911(DbContextOptions options) : DbContext(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) => modelBuilder.Entity( @@ -375,7 +375,7 @@ public virtual async Task Projecting_correlated_collection_property_for_owned_en Assert.True(new[] { "US", "CA" }.SequenceEqual(warehouseModel.DestinationCountryCodes)); } - private class Context18582(DbContextOptions options) : DbContext(options) + public class Context18582(DbContextOptions options) : DbContext(options) { public DbSet Warehouses { get; set; } @@ -440,7 +440,7 @@ public virtual async Task Accessing_scalar_property_in_derived_type_projection_d Assert.Equal("A", Assert.Single(result).OtherEntityData); } - private class Context19138(DbContextOptions options) : DbContext(options) + public class Context19138(DbContextOptions options) : DbContext(options) { public DbSet BaseEntities { get; set; } public DbSet OtherEntities { get; set; } @@ -522,7 +522,7 @@ public virtual async Task Multiple_single_result_in_projection_containing_owned_ x.Type, }; - private class Context20277(DbContextOptions options) : DbContext(options) + public class Context20277(DbContextOptions options) : DbContext(options) { public DbSet Entities => Set(); @@ -594,7 +594,7 @@ public virtual async Task Can_auto_include_navigation_from_model() } } - private class Context21540(DbContextOptions options) : DbContext(options) + public class Context21540(DbContextOptions options) : DbContext(options) { public DbSet Parents { get; set; } @@ -692,7 +692,7 @@ public virtual async Task Nested_owned_required_dependents_are_materialized() Assert.Equal(12345, result.Contact.Address.Zip); } - private class Context21807(DbContextOptions options) : DbContext(options) + public class Context21807(DbContextOptions options) : DbContext(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) => modelBuilder.Entity( @@ -755,7 +755,7 @@ public virtual async Task OwnsMany_correlated_projection(bool async) .ToListAsync(); } - private class Context22089(DbContextOptions options) : DbContext(options) + public class Context22089(DbContextOptions options) : DbContext(options) { public DbSet Contacts { get; set; } @@ -814,7 +814,7 @@ public virtual async Task Projecting_owned_collection_and_aggregate(bool async) : query.ToList(); } - private class Context24133(DbContextOptions options) : DbContext(options) + public class Context24133(DbContextOptions options) : DbContext(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) => modelBuilder.Entity( @@ -863,6 +863,8 @@ public class PostDto #endregion + #region 26592 + protected virtual async Task Owned_references_on_same_level_expanded_at_different_times_around_take_helper( MyContext26592Base context, bool async) @@ -894,7 +896,7 @@ public class PostDto Assert.Equal("IM Free shipping", owner.OwnedEntity.SupplierData.AdditionalSupplierData); } - protected abstract class MyContext26592Base : DbContext + public abstract class MyContext26592Base : DbContext { protected MyContext26592Base(DbContextOptions options) : base(options) @@ -967,4 +969,6 @@ public class IntermediateOwnedEntity public SupplierData SupplierData { get; set; } } } + + #endregion } diff --git a/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs index 4f8953c0f73..fd49c0259ef 100644 --- a/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs @@ -424,7 +424,7 @@ public virtual Task Client_method_skip_take_loads_owned_navigations(bool async) async, ss => ss.Set().OrderBy(e => e.Id).Select(e => Map(e)).Skip(1).Take(2)); - private static string Map(OwnedPerson person) + public static string Map(OwnedPerson person) => person.PersonAddress.Country.Name; // Issue#18734 @@ -449,7 +449,7 @@ public virtual Task Client_method_skip_take_loads_owned_navigations_variation_2( async, ss => ss.Set().OrderBy(e => e.Id).Select(e => Identity(e)).Skip(1).Take(2)); - private static OwnedPerson Identity(OwnedPerson person) + public static OwnedPerson Identity(OwnedPerson person) => person; [ConditionalTheory] @@ -1858,7 +1858,7 @@ private static IReadOnlyList CreateBartons() } } - protected class OwnedAddress + public class OwnedAddress { private string _addressLine; private int _zipCode; @@ -1914,7 +1914,7 @@ protected class OwnedAddress } } - protected class OwnedCountry + public class OwnedCountry { public string Name { get; set; } @@ -1922,7 +1922,7 @@ protected class OwnedCountry public Planet Planet { get; set; } } - protected class OwnedPerson + public class OwnedPerson { private string _name; @@ -1947,7 +1947,7 @@ public int ReadOnlyProperty public ICollection Orders { get; set; } } - protected class Order + public class Order { private DateTime _orderDate; public int Id { get; set; } @@ -1968,27 +1968,27 @@ protected class Order public List Details { get; set; } } - protected class OrderDetail + public class OrderDetail { public string Detail { get; set; } } - protected class Branch : OwnedPerson + public class Branch : OwnedPerson { public OwnedAddress BranchAddress { get; set; } } - protected class LeafA : Branch + public class LeafA : Branch { public OwnedAddress LeafAAddress { get; set; } } - protected class LeafB : OwnedPerson + public class LeafB : OwnedPerson { public OwnedAddress LeafBAddress { get; set; } } - protected class Planet + public class Planet { public int Id { get; set; } @@ -2000,7 +2000,7 @@ protected class Planet public List Moons { get; set; } } - protected class Moon + public class Moon { public int Id { get; set; } public int Diameter { get; set; } @@ -2008,7 +2008,7 @@ protected class Moon public int PlanetId { get; set; } } - protected class Star + public class Star { public int Id { get; set; } public string Name { get; set; } @@ -2018,7 +2018,7 @@ protected class Star public List Planets { get; set; } } - protected class Element + public class Element { public string Id { get; set; } public string Name { get; set; } @@ -2026,7 +2026,7 @@ protected class Element public int StarId { get; set; } } - protected class Barton + public class Barton { public int Id { get; set; } @@ -2035,40 +2035,40 @@ protected class Barton public string Simple { get; set; } } - protected class Fink + public class Fink { public Barton Barton { get; set; } public int Id { get; set; } } - protected class Throned + public class Throned { public int Value { get; set; } public string Property { get; set; } } - protected abstract class Balloon + public abstract class Balloon { public string Id { get; set; } } - protected class Helium + public class Helium { public int X { get; set; } } - protected class Hydrogen + public class Hydrogen { public int Y { get; set; } } - protected class HeliumBalloon : Balloon + public class HeliumBalloon : Balloon { public Helium Gas { get; set; } } - protected class HydrogenBalloon : Balloon + public class HydrogenBalloon : Balloon { public Hydrogen Gas { get; set; } } diff --git a/test/EFCore.Specification.Tests/Query/SharedTypeQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/SharedTypeQueryTestBase.cs index 6a73dc0d5be..eddf3388246 100644 --- a/test/EFCore.Specification.Tests/Query/SharedTypeQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/SharedTypeQueryTestBase.cs @@ -14,11 +14,11 @@ protected override string StoreName [MemberData(nameof(IsAsyncData))] public virtual async Task Can_use_shared_type_entity_type_in_query_filter(bool async) { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: c => c.SeedAsync()); using var context = contextFactory.CreateContext(); - var query = context.Set(); + var query = context.Set(); var result = async ? await query.ToListAsync() : query.ToList(); @@ -26,7 +26,7 @@ public virtual async Task Can_use_shared_type_entity_type_in_query_filter(bool a Assert.Empty(result); } - protected class MyContext24601(DbContextOptions options) : DbContext(options) + public class Context24601(DbContextOptions options) : DbContext(options) { public Task SeedAsync() { @@ -45,13 +45,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) b.IndexerProperty("Value"); }); - modelBuilder.Entity().HasNoKey() + modelBuilder.Entity().HasNoKey() .HasQueryFilter(e => Set>("STET").Select(i => (string)i["Value"]).Contains(e.Value)); } - } - protected class ViewQuery24601 - { - public string Value { get; set; } + public class ViewQuery + { + public string Value { get; set; } + } } } 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 @@ public virtual async Task SimpleSelect(bool async) 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..b488548092d 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> _instanceLambda = () => new(); + + /// + public override Expression ConstructorExpression => _instanceLambda.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 @@ Polygon CreatePolygon(double y = 2.2) }); } - [ConditionalFact] + [ConditionalFact(Skip = "AOT: NTS is not supported")] public virtual void Translators_handle_static_members() { using var db = Fixture.CreateContext(); @@ -108,7 +108,7 @@ select new }).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/AdHocJsonQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocJsonQuerySqlServerTest.cs index 3f42c52b152..2a5c2f0c8f6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocJsonQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocJsonQuerySqlServerTest.cs @@ -14,12 +14,12 @@ public class AdHocJsonQuerySqlServerTest : AdHocJsonQueryTestBase protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; - protected override async Task Seed29219(MyContext29219 ctx) + protected override async Task Seed29219(Context29219 ctx) { - var entity1 = new MyEntity29219 + var entity1 = new Context29219.MyEntity { Id = 1, - Reference = new MyJsonEntity29219 { NonNullableScalar = 10, NullableScalar = 11 }, + Reference = new Context29219.MyJsonEntity { NonNullableScalar = 10, NullableScalar = 11 }, Collection = [ new() { NonNullableScalar = 100, NullableScalar = 101 }, @@ -28,10 +28,10 @@ protected override async Task Seed29219(MyContext29219 ctx) ] }; - var entity2 = new MyEntity29219 + var entity2 = new Context29219.MyEntity { Id = 2, - Reference = new MyJsonEntity29219 { NonNullableScalar = 20, NullableScalar = null }, + Reference = new Context29219.MyJsonEntity { NonNullableScalar = 20, NullableScalar = null }, Collection = [new() { NonNullableScalar = 1001, NullableScalar = null }] }; @@ -45,7 +45,7 @@ protected override async Task Seed29219(MyContext29219 ctx) """); } - protected override async Task Seed30028(MyContext30028 ctx) + protected override async Task Seed30028(Context30028 ctx) { // complete await ctx.Database.ExecuteSqlAsync( @@ -91,12 +91,12 @@ protected override Task Seed33046(Context33046 ctx) VALUES(N'[{"RoundNumber":11,"SubRounds":[{"SubRoundNumber":111},{"SubRoundNumber":112}]}]', 1) """); - protected override Task SeedArrayOfPrimitives(MyContextArrayOfPrimitives ctx) + protected override Task SeedArrayOfPrimitives(ContextArrayOfPrimitives ctx) { - var entity1 = new MyEntityArrayOfPrimitives + var entity1 = new ContextArrayOfPrimitives.MyEntity { Id = 1, - Reference = new MyJsonEntityArrayOfPrimitives + Reference = new ContextArrayOfPrimitives.MyJsonEntity { IntArray = [1, 2, 3], ListOfString = @@ -113,10 +113,10 @@ protected override Task SeedArrayOfPrimitives(MyContextArrayOfPrimitives ctx) ] }; - var entity2 = new MyEntityArrayOfPrimitives + var entity2 = new ContextArrayOfPrimitives.MyEntity { Id = 2, - Reference = new MyJsonEntityArrayOfPrimitives + Reference = new ContextArrayOfPrimitives.MyJsonEntity { IntArray = [10, 20, 30], ListOfString = @@ -137,7 +137,7 @@ protected override Task SeedArrayOfPrimitives(MyContextArrayOfPrimitives ctx) return ctx.SaveChangesAsync(); } - protected override Task SeedJunkInJson(MyContextJunkInJson ctx) + protected override Task SeedJunkInJson(ContextJunkInJson ctx) => ctx.Database.ExecuteSqlAsync( $$$$""" INSERT INTO [Entities] ([Collection], [CollectionWithCtor], [Reference], [ReferenceWithCtor], [Id]) @@ -149,7 +149,7 @@ protected override Task SeedJunkInJson(MyContextJunkInJson ctx) 1) """); - protected override Task SeedTrickyBuffering(MyContextTrickyBuffering ctx) + protected override Task SeedTrickyBuffering(ContextTrickyBuffering ctx) => ctx.Database.ExecuteSqlAsync( $$$""" INSERT INTO [Entities] ([Reference], [Id]) @@ -157,7 +157,7 @@ protected override Task SeedTrickyBuffering(MyContextTrickyBuffering ctx) N'{"Name": "r1", "Number": 7, "JunkReference":{"Something": "SomeValue" }, "JunkCollection": [{"Foo": "junk value"}], "NestedReference": {"DoB": "2000-01-01T00:00:00"}, "NestedCollection": [{"DoB": "2000-02-01T00:00:00", "JunkReference": {"Something": "SomeValue"}}, {"DoB": "2000-02-02T00:00:00"}]}',1) """); - protected override Task SeedShadowProperties(MyContextShadowProperties ctx) + protected override Task SeedShadowProperties(ContextShadowProperties ctx) => ctx.Database.ExecuteSqlAsync( $$""" INSERT INTO [Entities] ([Collection], [CollectionWithCtor], [Reference], [ReferenceWithCtor], [Id], [Name]) @@ -170,7 +170,7 @@ protected override Task SeedShadowProperties(MyContextShadowProperties ctx) N'e1') """); - protected override async Task SeedNotICollection(MyContextNotICollection ctx) + protected override async Task SeedNotICollection(ContextNotICollection ctx) { await ctx.Database.ExecuteSqlAsync( $$""" @@ -195,7 +195,7 @@ protected override async Task SeedNotICollection(MyContextNotICollection ctx) [MemberData(nameof(IsAsyncData))] public virtual async Task Read_enum_property_with_legacy_values(bool async) { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: SeedEnumLegacyValues); using (var context = contextFactory.CreateContext()) @@ -222,7 +222,7 @@ public virtual async Task Read_enum_property_with_legacy_values(bool async) [MemberData(nameof(IsAsyncData))] public virtual async Task Read_json_entity_with_enum_properties_with_legacy_values(bool async) { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: SeedEnumLegacyValues, shouldLogCategory: c => c == DbLoggerCategory.Query.Name); @@ -235,33 +235,33 @@ public virtual async Task Read_json_entity_with_enum_properties_with_legacy_valu : query.ToList(); Assert.Equal(1, result.Count); - Assert.Equal(ByteEnumLegacyValues.Redmond, result[0].ByteEnum); - Assert.Equal(IntEnumLegacyValues.Foo, result[0].IntEnum); - Assert.Equal(LongEnumLegacyValues.Three, result[0].LongEnum); - Assert.Equal(ULongEnumLegacyValues.Three, result[0].ULongEnum); - Assert.Equal(IntEnumLegacyValues.Bar, result[0].NullableEnum); + Assert.Equal(ContextEnumLegacyValues.ByteEnum.Redmond, result[0].ByteEnum); + Assert.Equal(ContextEnumLegacyValues.IntEnum.Foo, result[0].IntEnum); + Assert.Equal(ContextEnumLegacyValues.LongEnum.Three, result[0].LongEnum); + Assert.Equal(ContextEnumLegacyValues.ULongEnum.Three, result[0].ULongEnum); + Assert.Equal(ContextEnumLegacyValues.IntEnum.Bar, result[0].NullableEnum); } var testLogger = new TestLogger(); Assert.Single( ListLoggerFactory.Log.Where( - l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(ByteEnumLegacyValues)))); + l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(ContextEnumLegacyValues.ByteEnum)))); Assert.Single( ListLoggerFactory.Log.Where( - l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(IntEnumLegacyValues)))); + l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(ContextEnumLegacyValues.IntEnum)))); Assert.Single( ListLoggerFactory.Log.Where( - l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(LongEnumLegacyValues)))); + l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(ContextEnumLegacyValues.LongEnum)))); Assert.Single( ListLoggerFactory.Log.Where( - l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(ULongEnumLegacyValues)))); + l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(ContextEnumLegacyValues.ULongEnum)))); } [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual async Task Read_json_entity_collection_with_enum_properties_with_legacy_values(bool async) { - var contextFactory = await InitializeAsync( + var contextFactory = await InitializeAsync( seed: SeedEnumLegacyValues, shouldLogCategory: c => c == DbLoggerCategory.Query.Name); @@ -275,34 +275,34 @@ public virtual async Task Read_json_entity_collection_with_enum_properties_with_ Assert.Equal(1, result.Count); Assert.Equal(2, result[0].Count); - Assert.Equal(ByteEnumLegacyValues.Bellevue, result[0][0].ByteEnum); - Assert.Equal(IntEnumLegacyValues.Foo, result[0][0].IntEnum); - Assert.Equal(LongEnumLegacyValues.One, result[0][0].LongEnum); - Assert.Equal(ULongEnumLegacyValues.One, result[0][0].ULongEnum); - Assert.Equal(IntEnumLegacyValues.Bar, result[0][0].NullableEnum); - Assert.Equal(ByteEnumLegacyValues.Seattle, result[0][1].ByteEnum); - Assert.Equal(IntEnumLegacyValues.Baz, result[0][1].IntEnum); - Assert.Equal(LongEnumLegacyValues.Two, result[0][1].LongEnum); - Assert.Equal(ULongEnumLegacyValues.Two, result[0][1].ULongEnum); + Assert.Equal(ContextEnumLegacyValues.ByteEnum.Bellevue, result[0][0].ByteEnum); + Assert.Equal(ContextEnumLegacyValues.IntEnum.Foo, result[0][0].IntEnum); + Assert.Equal(ContextEnumLegacyValues.LongEnum.One, result[0][0].LongEnum); + Assert.Equal(ContextEnumLegacyValues.ULongEnum.One, result[0][0].ULongEnum); + Assert.Equal(ContextEnumLegacyValues.IntEnum.Bar, result[0][0].NullableEnum); + Assert.Equal(ContextEnumLegacyValues.ByteEnum.Seattle, result[0][1].ByteEnum); + Assert.Equal(ContextEnumLegacyValues.IntEnum.Baz, result[0][1].IntEnum); + Assert.Equal(ContextEnumLegacyValues.LongEnum.Two, result[0][1].LongEnum); + Assert.Equal(ContextEnumLegacyValues.ULongEnum.Two, result[0][1].ULongEnum); Assert.Null(result[0][1].NullableEnum); } var testLogger = new TestLogger(); Assert.Single( ListLoggerFactory.Log.Where( - l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(ByteEnumLegacyValues)))); + l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(ContextEnumLegacyValues.ByteEnum)))); Assert.Single( ListLoggerFactory.Log.Where( - l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(IntEnumLegacyValues)))); + l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(ContextEnumLegacyValues.IntEnum)))); Assert.Single( ListLoggerFactory.Log.Where( - l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(LongEnumLegacyValues)))); + l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(ContextEnumLegacyValues.LongEnum)))); Assert.Single( ListLoggerFactory.Log.Where( - l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(ULongEnumLegacyValues)))); + l => l.Message == CoreResources.LogStringEnumValueInJson(testLogger).GenerateMessage(nameof(ContextEnumLegacyValues.ULongEnum)))); } - private Task SeedEnumLegacyValues(MyContextEnumLegacyValues ctx) + private Task SeedEnumLegacyValues(ContextEnumLegacyValues ctx) => ctx.Database.ExecuteSqlAsync( $$""" INSERT INTO [Entities] ([Collection], [Reference], [Id], [Name]) @@ -313,71 +313,71 @@ private Task SeedEnumLegacyValues(MyContextEnumLegacyValues ctx) N'e1') """); - private class MyContextEnumLegacyValues(DbContextOptions options) : DbContext((new DbContextOptionsBuilder(options)).ConfigureWarnings(b => b.Log(CoreEventId.StringEnumValueInJson)).Options) + public class ContextEnumLegacyValues(DbContextOptions options) : DbContext((new DbContextOptionsBuilder(options)).ConfigureWarnings(b => b.Log(CoreEventId.StringEnumValueInJson)).Options) { // ReSharper disable once UnusedAutoPropertyAccessor.Local - public DbSet Entities { get; set; } + public DbSet Entities { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); - modelBuilder.Entity().OwnsOne(x => x.Reference, b => b.ToJson()); - modelBuilder.Entity().OwnsMany(x => x.Collection, b => b.ToJson()); + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().OwnsOne(x => x.Reference, b => b.ToJson()); + modelBuilder.Entity().OwnsMany(x => x.Collection, b => b.ToJson()); } - } - - private class MyEntityEnumLegacyValues - { - public int Id { get; set; } - public string Name { get; set; } - public MyJsonEntityEnumLegacyValues Reference { get; set; } - public List Collection { get; set; } - } + public class MyEntity + { + public int Id { get; set; } + public string Name { get; set; } - private class MyJsonEntityEnumLegacyValues - { - public string Name { get; set; } + public MyJsonEntity Reference { get; set; } + public List Collection { get; set; } + } - // ReSharper disable once UnusedAutoPropertyAccessor.Local - public IntEnumLegacyValues IntEnum { get; set; } - // ReSharper disable once UnusedAutoPropertyAccessor.Local - public ByteEnumLegacyValues ByteEnum { get; set; } - // ReSharper disable once UnusedAutoPropertyAccessor.Local - public LongEnumLegacyValues LongEnum { get; set; } - // ReSharper disable once UnusedAutoPropertyAccessor.Local - public ULongEnumLegacyValues ULongEnum { get; set; } - // ReSharper disable once UnusedAutoPropertyAccessor.Local - public IntEnumLegacyValues? NullableEnum { get; set; } - } + public class MyJsonEntity + { + public string Name { get; set; } + + // ReSharper disable once UnusedAutoPropertyAccessor.Local + public IntEnum IntEnum { get; set; } + // ReSharper disable once UnusedAutoPropertyAccessor.Local + public ByteEnum ByteEnum { get; set; } + // ReSharper disable once UnusedAutoPropertyAccessor.Local + public LongEnum LongEnum { get; set; } + // ReSharper disable once UnusedAutoPropertyAccessor.Local + public ULongEnum ULongEnum { get; set; } + // ReSharper disable once UnusedAutoPropertyAccessor.Local + public IntEnum? NullableEnum { get; set; } + } - private enum IntEnumLegacyValues - { - Foo = int.MinValue, - Bar, - Baz = int.MaxValue, - } + public enum IntEnum + { + Foo = int.MinValue, + Bar, + Baz = int.MaxValue, + } - private enum ByteEnumLegacyValues : byte - { - Seattle, - Redmond, - Bellevue = 255, - } + public enum ByteEnum : byte + { + Seattle, + Redmond, + Bellevue = 255, + } - private enum LongEnumLegacyValues : long - { - One = long.MinValue, - Two = 1, - Three = long.MaxValue, - } + public enum LongEnum : long + { + One = long.MinValue, + Two = 1, + Three = long.MaxValue, + } - private enum ULongEnumLegacyValues : ulong - { - One = ulong.MinValue, - Two = 1, - Three = ulong.MaxValue, + public enum ULongEnum : ulong + { + One = ulong.MinValue, + Two = 1, + Three = ulong.MaxValue, + } } #endregion diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs index 7dbdedb982d..34d4c784dc4 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs @@ -100,7 +100,7 @@ public virtual async Task Include_group_join_is_per_query_context_async() }); } - private class Context5456(DbContextOptions options) : DbContext(options) + public class Context5456(DbContextOptions options) : DbContext(options) { public DbSet Blogs { get; set; } public DbSet Posts { get; set; } @@ -191,7 +191,7 @@ public virtual async Task Select_nested_projection() """); } - private class Context8864(DbContextOptions options) : DbContext(options) + public class Context8864(DbContextOptions options) : DbContext(options) { public DbSet Customers { get; set; } @@ -346,7 +346,7 @@ public virtual async Task From_sql_gets_value_of_out_parameter_in_stored_procedu } } - protected class Context9277(DbContextOptions options) : DbContext(options) + public class Context9277(DbContextOptions options) : DbContext(options) { public DbSet Blogs { get; set; } @@ -479,7 +479,7 @@ public virtual async Task Projecting_column_with_value_converter_of_ulong_byte_a """); } - protected class Context12518(DbContextOptions options) : DbContext(options) + public class Context12518(DbContextOptions options) : DbContext(options) { public virtual DbSet Parents { get; set; } public virtual DbSet Children { get; set; } @@ -531,7 +531,7 @@ public virtual async Task DateTime_Contains_with_smalldatetime_generates_correct var contextFactory = await InitializeAsync(seed: c => c.SeedAsync()); using var context = contextFactory.CreateContext(); var testDateList = new List { new(2018, 10, 07) }; - var findRecordsWithDateInList = context.ReproEntity + var findRecordsWithDateInList = context.ReproEntities .Where(a => testDateList.Contains(a.MyTime)) .ToList(); @@ -542,7 +542,7 @@ public virtual async Task DateTime_Contains_with_smalldatetime_generates_correct @__testDateList_0='["2018-10-07T00:00:00"]' (Size = 4000) SELECT [r].[Id], [r].[MyTime] -FROM [ReproEntity] AS [r] +FROM [ReproEntities] AS [r] WHERE [r].[MyTime] IN ( SELECT [t].[value] FROM OPENJSON(@__testDateList_0) WITH ([value] smalldatetime '$') AS [t] @@ -550,27 +550,27 @@ public virtual async Task DateTime_Contains_with_smalldatetime_generates_correct """); } - private class Context13118(DbContextOptions options) : DbContext(options) + public class Context13118(DbContextOptions options) : DbContext(options) { - public virtual DbSet ReproEntity { get; set; } + public virtual DbSet ReproEntities { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) - => modelBuilder.Entity(e => e.Property("MyTime").HasColumnType("smalldatetime")); + => modelBuilder.Entity(e => e.Property("MyTime").HasColumnType("smalldatetime")); public Task SeedAsync() { AddRange( - new ReproEntity13118 { MyTime = new DateTime(2018, 10, 07) }, - new ReproEntity13118 { MyTime = new DateTime(2018, 10, 08) }); + new ReproEntity { MyTime = new DateTime(2018, 10, 07) }, + new ReproEntity { MyTime = new DateTime(2018, 10, 08) }); return SaveChangesAsync(); } - } - private class ReproEntity13118 - { - public Guid Id { get; set; } - public DateTime MyTime { get; set; } + public class ReproEntity + { + public Guid Id { get; set; } + public DateTime MyTime { get; set; } + } } #endregion @@ -763,7 +763,7 @@ public async Task Where_contains_DateTime_literals(bool async) """); } - protected class Context14095(DbContextOptions options) : DbContext(options) + public class Context14095(DbContextOptions options) : DbContext(options) { public DbSet Dates { get; set; } @@ -828,107 +828,6 @@ public class DatesAndPrunes14095 #endregion - #region 15518 - - [ConditionalTheory] - [InlineData(false)] - [InlineData(true)] - public virtual async Task Nested_queries_does_not_cause_concurrency_exception_sync(bool tracking) - { - var contextFactory = await InitializeAsync(seed: c => c.SeedAsync()); - - using (var context = contextFactory.CreateContext()) - { - var query = context.Repos.OrderBy(r => r.Id).Where(r => r.Id > 0); - query = tracking ? query.AsTracking() : query.AsNoTracking(); - - foreach (var a in query) - { - foreach (var b in query) - { - } - } - } - - using (var context = contextFactory.CreateContext()) - { - var query = context.Repos.OrderBy(r => r.Id).Where(r => r.Id > 0); - query = tracking ? query.AsTracking() : query.AsNoTracking(); - - await foreach (var a in query.AsAsyncEnumerable()) - { - await foreach (var b in query.AsAsyncEnumerable()) - { - } - } - } - - AssertSql( - """ -SELECT [r].[Id], [r].[Name] -FROM [Repos] AS [r] -WHERE [r].[Id] > 0 -ORDER BY [r].[Id] -""", - // - """ -SELECT [r].[Id], [r].[Name] -FROM [Repos] AS [r] -WHERE [r].[Id] > 0 -ORDER BY [r].[Id] -""", - // - """ -SELECT [r].[Id], [r].[Name] -FROM [Repos] AS [r] -WHERE [r].[Id] > 0 -ORDER BY [r].[Id] -""", - // - """ -SELECT [r].[Id], [r].[Name] -FROM [Repos] AS [r] -WHERE [r].[Id] > 0 -ORDER BY [r].[Id] -""", - // - """ -SELECT [r].[Id], [r].[Name] -FROM [Repos] AS [r] -WHERE [r].[Id] > 0 -ORDER BY [r].[Id] -""", - // - """ -SELECT [r].[Id], [r].[Name] -FROM [Repos] AS [r] -WHERE [r].[Id] > 0 -ORDER BY [r].[Id] -"""); - } - - private class Context15518(DbContextOptions options) : DbContext(options) - { - public DbSet Repos { get; set; } - - public Task SeedAsync() - { - AddRange( - new Repo { Name = "London" }, - new Repo { Name = "New York" }); - - return SaveChangesAsync(); - } - - public class Repo - { - public int Id { get; set; } - public string Name { get; set; } - } - } - - #endregion - #region 19206 [ConditionalFact] @@ -966,7 +865,7 @@ public virtual async Task From_sql_expression_compares_correctly() } } - private class Context19206(DbContextOptions options) : DbContext(options) + public class Context19206(DbContextOptions options) : DbContext(options) { public DbSet Tests { get; set; } @@ -1018,7 +917,7 @@ public virtual async Task Thread_safety_in_relational_command_cache() }); } - private class Context21666(DbContextOptions options) : DbContext(options) + public class Context21666(DbContextOptions options) : DbContext(options) { public DbSet Lists { get; set; } @@ -1037,7 +936,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() { @@ -1059,7 +958,7 @@ public virtual async Task Can_query_point_with_buffered_data_reader() """); } - private class Context23282(DbContextOptions options) : DbContext(options) + public class Context23282(DbContextOptions options) : DbContext(options) { public DbSet Locations { get; set; } @@ -1361,22 +1260,22 @@ public virtual async Task TemporalAsOf_project_json_entity_collection(bool async """); } - protected class Context30478(DbContextOptions options) : DbContext(options) + public class Context30478(DbContextOptions options) : DbContext(options) { - public DbSet Entities { get; set; } + public DbSet Entities { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); - modelBuilder.Entity().ToTable("Entities", tb => tb.IsTemporal()); - modelBuilder.Entity().OwnsOne( + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().ToTable("Entities", tb => tb.IsTemporal()); + modelBuilder.Entity().OwnsOne( x => x.Reference, nb => { nb.ToJson(); nb.OwnsOne(x => x.Nested); }); - modelBuilder.Entity().OwnsMany( + modelBuilder.Entity().OwnsMany( x => x.Collection, nb => { nb.ToJson(); @@ -1386,31 +1285,28 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) public async Task SeedAsync() { - var e1 = new Entity30478 + var e1 = new Entity { Id = 1, Name = "e1", - Reference = new Json30478 { Name = "r1", Nested = new JsonNested30478 { Number = 1 } }, + Reference = new Json { Name = "r1", Nested = new JsonNested { Number = 1 } }, Collection = [ - new Json30478 { Name = "c11", Nested = new JsonNested30478 { Number = 11 } }, - - new Json30478 { Name = "c12", Nested = new JsonNested30478 { Number = 12 } }, - - new Json30478 { Name = "c13", Nested = new JsonNested30478 { Number = 12 } } + new Json { Name = "c11", Nested = new JsonNested { Number = 11 } }, + new Json { Name = "c12", Nested = new JsonNested { Number = 12 } }, + new Json { Name = "c13", Nested = new JsonNested { Number = 12 } } ] }; - var e2 = new Entity30478 + var e2 = new Entity { Id = 2, Name = "e2", - Reference = new Json30478 { Name = "r2", Nested = new JsonNested30478 { Number = 2 } }, + Reference = new Json { Name = "r2", Nested = new JsonNested { Number = 2 } }, Collection = [ - new Json30478 { Name = "c21", Nested = new JsonNested30478 { Number = 21 } }, - - new Json30478 { Name = "c22", Nested = new JsonNested30478 { Number = 22 } } + new Json { Name = "c21", Nested = new JsonNested { Number = 21 } }, + new Json { Name = "c22", Nested = new JsonNested { Number = 22 } } ] }; @@ -1432,25 +1328,25 @@ public async Task SeedAsync() await Database.ExecuteSqlRawAsync( "ALTER TABLE [Entities] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[EntitiesHistory]))"); } - } - protected class Entity30478 - { - public int Id { get; set; } - public string Name { get; set; } - public Json30478 Reference { get; set; } - public List Collection { get; set; } - } + public class Entity + { + public int Id { get; set; } + public string Name { get; set; } + public Json Reference { get; set; } + public List Collection { get; set; } + } - protected class Json30478 - { - public string Name { get; set; } - public JsonNested30478 Nested { get; set; } - } + public class Json + { + public string Name { get; set; } + public JsonNested Nested { get; set; } + } - protected class JsonNested30478 - { - public int Number { get; set; } + public class JsonNested + { + public int Number { get; set; } + } } #endregion diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocNavigationsQuerySqlServerTest.cs index b265e957cf7..7d87e59dd90 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocNavigationsQuerySqlServerTest.cs @@ -67,7 +67,7 @@ public virtual async Task Nested_include_queries_do_not_populate_navigation_twic """); } - protected class Context10447(DbContextOptions options) : DbContext(options) + public class Context10447(DbContextOptions options) : DbContext(options) { public DbSet Blogs { get; set; } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocQueryFiltersQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocQueryFiltersQuerySqlServerTest.cs index 53d5e14fbe9..90bed320af8 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocQueryFiltersQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocQueryFiltersQuerySqlServerTest.cs @@ -53,7 +53,7 @@ public virtual async Task Keyless_type_used_inside_defining_query() """); } - protected class Context11803(DbContextOptions options) : DbContext(options) + public class Context11803(DbContextOptions options) : DbContext(options) { public DbSet Factions { get; set; } public DbSet Leaders { get; set; } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs index e35cd767d91..3a7b5fe0f4e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs @@ -643,10 +643,10 @@ private enum MyEnum { Label1, Label2 } var arrayClrType = typeof(TElement).MakeArrayType(); var contextFactory = await InitializeAsync( - onModelCreating: onModelCreating ?? (mb => mb.Entity().Property(arrayClrType, "SomeArray")), + onModelCreating: onModelCreating ?? (mb => mb.Entity().Property(arrayClrType, "SomeArray")), seed: context => { - var instance1 = new TestEntity { Id = 1 }; + var instance1 = new TestContext.TestEntity { Id = 1 }; context.Add(instance1); var array1 = new TElement[3]; array1.SetValue(value1, 0); // We have an extra copy of the first value which we'll Skip, to preserve the ordering @@ -654,7 +654,7 @@ private enum MyEnum { Label1, Label2 } array1.SetValue(value1, 2); context.Entry(instance1).Property("SomeArray").CurrentValue = array1; - var instance2 = new TestEntity { Id = 2 }; + var instance2 = new TestContext.TestEntity { Id = 2 }; context.Add(instance2); var array2 = new TElement[3]; array2.SetValue(value1, 0); @@ -667,14 +667,14 @@ private enum MyEnum { Label1, Label2 } await using var context = contextFactory.CreateContext(); - var entityParam = Parameter(typeof(TestEntity), "m"); + var entityParam = Parameter(typeof(TestContext.TestEntity), "m"); var efPropertyCall = Call( typeof(EF).GetMethod(nameof(EF.Property), BindingFlags.Public | BindingFlags.Static)!.MakeGenericMethod(arrayClrType), entityParam, Constant("SomeArray")); var elementParam = Parameter(typeof(TElement), "a"); - var predicate = Lambda>( + var predicate = Lambda>( Equal( Call( CountWithPredicateMethod.MakeGenericMethod(typeof(TElement)), @@ -687,7 +687,7 @@ private enum MyEnum { Label1, Label2 } entityParam); // context.Set().SingleAsync(m => EF.Property(m, "SomeArray").Skip(1).Count(a => a == ) == 2) - var result = await context.Set().SingleAsync(predicate); + var result = await context.Set().SingleAsync(predicate); Assert.Equal(1, result.Id); } @@ -699,7 +699,6 @@ private enum MyEnum { Label1, Label2 } #endregion - [ConditionalFact] public override Task Column_with_custom_converter() => base.Column_with_custom_converter(); @@ -766,7 +765,7 @@ SELECT COUNT(*) public virtual async Task Same_parameter_with_different_type_mappings() { var contextFactory = await InitializeAsync( - onModelCreating: mb => mb.Entity( + onModelCreating: mb => mb.Entity( b => { b.Property(typeof(DateTime), "DateTime").HasColumnType("datetime"); @@ -777,7 +776,7 @@ public virtual async Task Same_parameter_with_different_type_mappings() var dateTimes = new[] { new DateTime(2020, 1, 1, 12, 30, 00), new DateTime(2020, 1, 2, 12, 30, 00) }; - _ = await context.Set() + _ = await context.Set() .Where( m => dateTimes.Contains(EF.Property(m, "DateTime")) @@ -805,13 +804,13 @@ public virtual async Task Same_parameter_with_different_type_mappings() public virtual async Task Same_collection_with_default_type_mapping_and_uninferrable_context() { var contextFactory = await InitializeAsync( - onModelCreating: mb => mb.Entity(b => b.Property(typeof(DateTime), "DateTime"))); + onModelCreating: mb => mb.Entity(b => b.Property(typeof(DateTime), "DateTime"))); await using var context = contextFactory.CreateContext(); var dateTimes = new DateTime?[] { new DateTime(2020, 1, 1, 12, 30, 00), new DateTime(2020, 1, 2, 12, 30, 00), null }; - _ = await context.Set() + _ = await context.Set() .Where(m => dateTimes.Any(d => d == EF.Property(m, "DateTime") && d != null)) .ToArrayAsync(); @@ -832,7 +831,7 @@ SELECT 1 public virtual async Task Same_collection_with_non_default_type_mapping_and_uninferrable_context() { var contextFactory = await InitializeAsync( - onModelCreating: mb => mb.Entity( + onModelCreating: mb => mb.Entity( b => b.Property(typeof(DateTime), "DateTime").HasColumnType("datetime"))); await using var context = contextFactory.CreateContext(); @@ -840,7 +839,7 @@ public virtual async Task Same_collection_with_non_default_type_mapping_and_unin var dateTimes = new DateTime?[] { new DateTime(2020, 1, 1, 12, 30, 00), new DateTime(2020, 1, 2, 12, 30, 00), null }; var exception = await Assert.ThrowsAsync( - () => context.Set() + () => context.Set() .Where( m => dateTimes.Any(d => d == EF.Property(m, "DateTime") && d != null)) .ToArrayAsync()); @@ -851,7 +850,7 @@ public virtual async Task Same_collection_with_non_default_type_mapping_and_unin public virtual async Task Same_collection_with_conflicting_type_mappings_not_supported() { var contextFactory = await InitializeAsync( - onModelCreating: mb => mb.Entity( + onModelCreating: mb => mb.Entity( b => { b.Property(typeof(DateTime), "DateTime").HasColumnType("datetime"); @@ -863,7 +862,7 @@ public virtual async Task Same_collection_with_conflicting_type_mappings_not_sup var dateTimes = new[] { new DateTime(2020, 1, 1, 12, 30, 00), new DateTime(2020, 1, 2, 12, 30, 00) }; var exception = await Assert.ThrowsAsync( - () => context.Set() + () => context.Set() .Where( m => dateTimes .Any(d => d == EF.Property(m, "DateTime") && d == EF.Property(m, "DateTime2"))) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedEntityQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedEntityQuerySqlServerTest.cs index c70570a8c7d..1b4f44051fc 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedEntityQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedEntityQuerySqlServerTest.cs @@ -40,7 +40,7 @@ public virtual async Task Optional_dependent_is_null_when_sharing_required_colum """); } - protected class Context22054(DbContextOptions options) : DbContext(options) + public class Context22054(DbContextOptions options) : DbContext(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) => modelBuilder.Entity( @@ -165,7 +165,7 @@ public virtual async Task Owned_entity_mapped_to_separate_table() """); } - protected class Context22340(DbContextOptions options) : DbContext(options) + public class Context22340(DbContextOptions options) : DbContext(options) { public DbSet MasterTrunk { get; set; } @@ -305,7 +305,7 @@ public virtual async Task Collection_include_on_owner_with_owned_type_mapped_to_ } } - protected class Context23211(DbContextOptions options) : DbContext(options) + public class Context23211(DbContextOptions options) : DbContext(options) { protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -597,7 +597,7 @@ public override async Task Owned_collection_basic_split_query(bool async) @__id_0='6c1ae3e5-30b9-4c77-8d98-f02075974a0a' SELECT TOP(1) [l].[Id] -FROM [Location25680] AS [l] +FROM [Location] AS [l] WHERE [l].[Id] = @__id_0 ORDER BY [l].[Id] """); 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/SharedTypeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SharedTypeQuerySqlServerTest.cs index c71a5cc0058..8ac89a4e3a4 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SharedTypeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SharedTypeQuerySqlServerTest.cs @@ -17,7 +17,7 @@ public override async Task Can_use_shared_type_entity_type_in_query_filter(bool AssertSql( """ SELECT [v].[Value] -FROM [ViewQuery24601] AS [v] +FROM [ViewQuery] AS [v] WHERE EXISTS ( SELECT 1 FROM [STET] AS [s] @@ -32,7 +32,7 @@ public override async Task Can_use_shared_type_entity_type_in_query_filter_with_ AssertSql( """ SELECT [v].[Value] -FROM [ViewQuery24601] AS [v] +FROM [ViewQuery] AS [v] WHERE EXISTS ( SELECT 1 FROM ( 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 @@ public override async Task Crosses(bool async) """); } - [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/Query/AdHocJsonQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocJsonQuerySqliteTest.cs index fcc06b5f823..5e0109b99bf 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocJsonQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocJsonQuerySqliteTest.cs @@ -10,12 +10,12 @@ public class AdHocJsonQuerySqliteTest : AdHocJsonQueryTestBase protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; - protected override async Task Seed29219(MyContext29219 ctx) + protected override async Task Seed29219(Context29219 ctx) { - var entity1 = new MyEntity29219 + var entity1 = new Context29219.MyEntity { Id = 1, - Reference = new MyJsonEntity29219 { NonNullableScalar = 10, NullableScalar = 11 }, + Reference = new Context29219.MyJsonEntity { NonNullableScalar = 10, NullableScalar = 11 }, Collection = [ new() { NonNullableScalar = 100, NullableScalar = 101 }, @@ -24,10 +24,10 @@ protected override async Task Seed29219(MyContext29219 ctx) ] }; - var entity2 = new MyEntity29219 + var entity2 = new Context29219.MyEntity { Id = 2, - Reference = new MyJsonEntity29219 { NonNullableScalar = 20, NullableScalar = null }, + Reference = new Context29219.MyJsonEntity { NonNullableScalar = 20, NullableScalar = null }, Collection = [new() { NonNullableScalar = 1001, NullableScalar = null }] }; @@ -41,7 +41,7 @@ protected override async Task Seed29219(MyContext29219 ctx) """); } - protected override async Task Seed30028(MyContext30028 ctx) + protected override async Task Seed30028(Context30028 ctx) { // complete await ctx.Database.ExecuteSqlAsync( @@ -87,12 +87,12 @@ protected override async Task Seed33046(Context33046 ctx) VALUES('[{"RoundNumber":11,"SubRounds":[{"SubRoundNumber":111},{"SubRoundNumber":112}]}]', 1) """); - protected override Task SeedArrayOfPrimitives(MyContextArrayOfPrimitives ctx) + protected override Task SeedArrayOfPrimitives(ContextArrayOfPrimitives ctx) { - var entity1 = new MyEntityArrayOfPrimitives + var entity1 = new ContextArrayOfPrimitives.MyEntity { Id = 1, - Reference = new MyJsonEntityArrayOfPrimitives + Reference = new ContextArrayOfPrimitives.MyJsonEntity { IntArray = [1, 2, 3], ListOfString = @@ -109,10 +109,10 @@ protected override Task SeedArrayOfPrimitives(MyContextArrayOfPrimitives ctx) ] }; - var entity2 = new MyEntityArrayOfPrimitives + var entity2 = new ContextArrayOfPrimitives.MyEntity { Id = 2, - Reference = new MyJsonEntityArrayOfPrimitives + Reference = new ContextArrayOfPrimitives.MyJsonEntity { IntArray = [10, 20, 30], ListOfString = @@ -133,7 +133,7 @@ protected override Task SeedArrayOfPrimitives(MyContextArrayOfPrimitives ctx) return ctx.SaveChangesAsync(); } - protected override Task SeedJunkInJson(MyContextJunkInJson ctx) + protected override Task SeedJunkInJson(ContextJunkInJson ctx) => ctx.Database.ExecuteSqlAsync( $$$""" INSERT INTO "Entities" ("Collection", "CollectionWithCtor", "Reference", "ReferenceWithCtor", "Id") @@ -145,7 +145,7 @@ protected override Task SeedJunkInJson(MyContextJunkInJson ctx) 1) """); - protected override Task SeedTrickyBuffering(MyContextTrickyBuffering ctx) + protected override Task SeedTrickyBuffering(ContextTrickyBuffering ctx) => ctx.Database.ExecuteSqlAsync( $$$""" INSERT INTO "Entities" ("Reference", "Id") @@ -153,7 +153,7 @@ protected override Task SeedTrickyBuffering(MyContextTrickyBuffering ctx) '{"Name": "r1", "Number": 7, "JunkReference":{"Something": "SomeValue" }, "JunkCollection": [{"Foo": "junk value"}], "NestedReference": {"DoB": "2000-01-01T00:00:00"}, "NestedCollection": [{"DoB": "2000-02-01T00:00:00", "JunkReference": {"Something": "SomeValue"}}, {"DoB": "2000-02-02T00:00:00"}]}',1) """); - protected override Task SeedShadowProperties(MyContextShadowProperties ctx) + protected override Task SeedShadowProperties(ContextShadowProperties ctx) => ctx.Database.ExecuteSqlAsync( $$""" INSERT INTO "Entities" ("Collection", "CollectionWithCtor", "Reference", "ReferenceWithCtor", "Id", "Name") @@ -166,7 +166,7 @@ protected override Task SeedShadowProperties(MyContextShadowProperties ctx) 'e1') """); - protected override async Task SeedNotICollection(MyContextNotICollection ctx) + protected override async Task SeedNotICollection(ContextNotICollection ctx) { await ctx.Database.ExecuteSqlAsync( $$""" 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,