Skip to content

Commit

Permalink
Introduce liftable constants to shaper to prepare for precompilation
Browse files Browse the repository at this point in the history
Part of #25009
  • Loading branch information
roji authored and maumar committed Mar 27, 2024
1 parent f99afbb commit cd6f35a
Show file tree
Hide file tree
Showing 170 changed files with 4,061 additions and 1,341 deletions.
1 change: 1 addition & 0 deletions EFCore.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ The .NET Foundation licenses this file to you under the MIT license.
<s:Boolean x:Key="/Default/UserDictionary/Words/=Includable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=initializers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=keyless/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=liftable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Lite_0027s/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=materializer/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=materializers/@EntryIndexedValue">True</s:Boolean>
Expand Down
14 changes: 14 additions & 0 deletions src/EFCore.Analyzers/EFDiagnostics.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Contains IDs of diagnostics created by EF analyzers and other mechanisms.
/// </summary>
public static class EFDiagnostics
{
public const string InternalUsage = "EF1001";
public const string SuppressUninitializedDbSetRule = "EFSPR1001";
public const string PrecompiledQueryExperimental = "EF2001";
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -896,6 +895,9 @@ private static Expression GetGroupingKey(Expression key, List<Expression> groupi
}
}

//private static readonly MethodInfo PropertyGetValueConverterMethod
// = typeof(IReadOnlyProperty).GetMethod(nameof(IReadOnlyProperty.GetValueComparer))!;

private Expression AddJoin(
InMemoryQueryExpression innerQueryExpression,
LambdaExpression? outerKeySelector,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public virtual InstantiationBinding ModifyBinding(InstantiationBindingIntercepti

return new FactoryMethodBinding(
_proxyFactory,
Expression.Constant(_proxyFactory, typeof(IProxyFactory)),
CreateLazyLoadingProxyMethod,
new List<ParameterBinding>
{
Expand All @@ -67,6 +68,7 @@ public virtual InstantiationBinding ModifyBinding(InstantiationBindingIntercepti
{
return new FactoryMethodBinding(
_proxyFactory,
Expression.Constant(_proxyFactory, typeof(IProxyFactory)),
CreateProxyMethod,
new List<ParameterBinding>
{
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore.Relational/EFCore.Relational.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<RootNamespace>Microsoft.EntityFrameworkCore</RootNamespace>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ImplicitUsings>true</ImplicitUsings>
<NoWarn>$(NoWarn);EF1003</NoWarn> <!-- Precomiled query is experimental -->
<NoWarn>$(NoWarn);EF2001</NoWarn> <!-- Precomiled query is experimental -->
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) },
Expand Down Expand Up @@ -189,6 +190,9 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices()
TryAdd<IQueryCompilationContextFactory, RelationalQueryCompilationContextFactory>();
TryAdd<IAdHocMapper, RelationalAdHocMapper>();
TryAdd<ISqlAliasManagerFactory, SqlAliasManagerFactory>();
TryAdd<ILiftableConstantFactory>(p => p.GetRequiredService<IRelationalLiftableConstantFactory>());
TryAdd<IRelationalLiftableConstantFactory, RelationalLiftableConstantFactory>();
TryAdd<ILiftableConstantProcessor, RelationalLiftableConstantProcessor>();

ServiceCollectionMap.GetInfrastructure()
.AddDependencySingleton<RelationalSqlGenerationHelperDependencies>()
Expand All @@ -204,6 +208,7 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices()
.AddDependencySingleton<RelationalEvaluatableExpressionFilterDependencies>()
.AddDependencySingleton<RelationalModelDependencies>()
.AddDependencySingleton<RelationalModelRuntimeInitializerDependencies>()
.AddDependencySingleton<RelationalLiftableConstantExpressionDependencies>()
.AddDependencyScoped<MigrationsSqlGeneratorDependencies>()
.AddDependencyScoped<RelationalConventionSetBuilderDependencies>()
.AddDependencyScoped<ModificationCommandBatchFactoryDependencies>()
Expand Down
19 changes: 19 additions & 0 deletions src/EFCore.Relational/Query/IRelationalLiftableConstantFactory.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// TODO
/// </summary>
public interface IRelationalLiftableConstantFactory : ILiftableConstantFactory
{
/// <summary>
/// TODO
/// </summary>
LiftableConstantExpression CreateLiftableConstant(
ConstantExpression originalExpression,
Expression<Func<RelationalMaterializerLiftableConstantContext, object>> resolverExpression,
string variableName,
Type type);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
[Experimental("EF1003")]
[Experimental(EFDiagnostics.PrecompiledQueryExperimental)]
public interface IRelationalQuotableExpression
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ public class GroupBySingleQueryingEnumerable<TKey, TElement>
private readonly IReadOnlyList<ReaderColumn?>? _readerColumns;
private readonly Func<QueryContext, DbDataReader, TKey> _keySelector;
private readonly Func<QueryContext, DbDataReader, object[]> _keyIdentifier;
private readonly IReadOnlyList<ValueComparer> _keyIdentifierValueComparers;
//private readonly IReadOnlyList<ValueComparer> _keyIdentifierValueComparers;
private readonly IReadOnlyList<Func<object, object, bool>> _keyIdentifierValueComparers;
private readonly Func<QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator, TElement> _elementSelector;
private readonly Type _contextType;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _queryLogger;
Expand All @@ -40,7 +41,8 @@ public class GroupBySingleQueryingEnumerable<TKey, TElement>
IReadOnlyList<ReaderColumn?>? readerColumns,
Func<QueryContext, DbDataReader, TKey> keySelector,
Func<QueryContext, DbDataReader, object[]> keyIdentifier,
IReadOnlyList<ValueComparer> keyIdentifierValueComparers,
//IReadOnlyList<ValueComparer> keyIdentifierValueComparers,
IReadOnlyList<Func<object, object, bool>> keyIdentifierValueComparers,
Func<QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator, TElement> elementSelector,
Type contextType,
bool standAloneStateManager,
Expand Down Expand Up @@ -139,12 +141,12 @@ IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
}

private static bool CompareIdentifiers(IReadOnlyList<ValueComparer> valueComparers, object[] left, object[] right)
private static bool CompareIdentifiers(IReadOnlyList<Func<object, object, bool>> 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;
}
Expand All @@ -160,7 +162,8 @@ private sealed class Enumerator : IEnumerator<IGrouping<TKey, TElement>>
private readonly IReadOnlyList<ReaderColumn?>? _readerColumns;
private readonly Func<QueryContext, DbDataReader, TKey> _keySelector;
private readonly Func<QueryContext, DbDataReader, object[]> _keyIdentifier;
private readonly IReadOnlyList<ValueComparer> _keyIdentifierValueComparers;
//private readonly IReadOnlyList<ValueComparer> _keyIdentifierValueComparers;
private readonly IReadOnlyList<Func<object, object, bool>> _keyIdentifierValueComparers;
private readonly Func<QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator, TElement> _elementSelector;
private readonly Type _contextType;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _queryLogger;
Expand Down Expand Up @@ -344,7 +347,8 @@ private sealed class AsyncEnumerator : IAsyncEnumerator<IGrouping<TKey, TElement
private readonly IReadOnlyList<ReaderColumn?>? _readerColumns;
private readonly Func<QueryContext, DbDataReader, TKey> _keySelector;
private readonly Func<QueryContext, DbDataReader, object[]> _keyIdentifier;
private readonly IReadOnlyList<ValueComparer> _keyIdentifierValueComparers;
//private readonly IReadOnlyList<ValueComparer> _keyIdentifierValueComparers;
private readonly IReadOnlyList<Func<object, object, bool>> _keyIdentifierValueComparers;
private readonly Func<QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator, TElement> _elementSelector;
private readonly Type _contextType;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _queryLogger;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ public class GroupBySplitQueryingEnumerable<TKey, TElement>
private readonly IReadOnlyList<ReaderColumn?>? _readerColumns;
private readonly Func<QueryContext, DbDataReader, TKey> _keySelector;
private readonly Func<QueryContext, DbDataReader, object[]> _keyIdentifier;
private readonly IReadOnlyList<ValueComparer> _keyIdentifierValueComparers;
//private readonly IReadOnlyList<ValueComparer> _keyIdentifierValueComparers;
private readonly IReadOnlyList<Func<object, object, bool>> _keyIdentifierValueComparers;
private readonly Func<QueryContext, DbDataReader, ResultContext, SplitQueryResultCoordinator, TElement> _elementSelector;
private readonly Action<QueryContext, IExecutionStrategy, SplitQueryResultCoordinator>? _relatedDataLoaders;
private readonly Func<QueryContext, IExecutionStrategy, SplitQueryResultCoordinator, Task>? _relatedDataLoadersAsync;
Expand All @@ -42,7 +43,8 @@ public class GroupBySplitQueryingEnumerable<TKey, TElement>
IReadOnlyList<ReaderColumn?>? readerColumns,
Func<QueryContext, DbDataReader, TKey> keySelector,
Func<QueryContext, DbDataReader, object[]> keyIdentifier,
IReadOnlyList<ValueComparer> keyIdentifierValueComparers,
//IReadOnlyList<ValueComparer> keyIdentifierValueComparers,
IReadOnlyList<Func<object, object, bool>> keyIdentifierValueComparers,
Func<QueryContext, DbDataReader, ResultContext, SplitQueryResultCoordinator, TElement> elementSelector,
Action<QueryContext, IExecutionStrategy, SplitQueryResultCoordinator>? relatedDataLoaders,
Func<QueryContext, IExecutionStrategy, SplitQueryResultCoordinator, Task>? relatedDataLoadersAsync,
Expand Down Expand Up @@ -145,12 +147,12 @@ IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
}

private static bool CompareIdentifiers(IReadOnlyList<ValueComparer> valueComparers, object[] left, object[] right)
private static bool CompareIdentifiers(IReadOnlyList<Func<object, object, bool>> 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;
}
Expand All @@ -166,7 +168,8 @@ private sealed class Enumerator : IEnumerator<IGrouping<TKey, TElement>>
private readonly IReadOnlyList<ReaderColumn?>? _readerColumns;
private readonly Func<QueryContext, DbDataReader, TKey> _keySelector;
private readonly Func<QueryContext, DbDataReader, object[]> _keyIdentifier;
private readonly IReadOnlyList<ValueComparer> _keyIdentifierValueComparers;
//private readonly IReadOnlyList<ValueComparer> _keyIdentifierValueComparers;
private readonly IReadOnlyList<Func<object, object, bool>> _keyIdentifierValueComparers;
private readonly Func<QueryContext, DbDataReader, ResultContext, SplitQueryResultCoordinator, TElement> _elementSelector;
private readonly Action<QueryContext, IExecutionStrategy, SplitQueryResultCoordinator>? _relatedDataLoaders;
private readonly Type _contextType;
Expand Down Expand Up @@ -340,7 +343,8 @@ private sealed class AsyncEnumerator : IAsyncEnumerator<IGrouping<TKey, TElement
private readonly IReadOnlyList<ReaderColumn?>? _readerColumns;
private readonly Func<QueryContext, DbDataReader, TKey> _keySelector;
private readonly Func<QueryContext, DbDataReader, object[]> _keyIdentifier;
private readonly IReadOnlyList<ValueComparer> _keyIdentifierValueComparers;
//private readonly IReadOnlyList<ValueComparer> _keyIdentifierValueComparers;
private readonly IReadOnlyList<Func<object, object, bool>> _keyIdentifierValueComparers;
private readonly Func<QueryContext, DbDataReader, ResultContext, SplitQueryResultCoordinator, TElement> _elementSelector;
private readonly Func<QueryContext, IExecutionStrategy, SplitQueryResultCoordinator, Task>? _relatedDataLoaders;
private readonly Type _contextType;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// <para>
/// Service dependencies parameter class for <see cref="RelationalLiftableConstantFactory" />
/// </para>
/// <para>
/// This type is typically used by database providers (and other extensions). It is generally
/// not used in application code.
/// </para>
/// </summary>
/// <remarks>
/// <para>
/// 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.
/// </para>
/// <para>
/// The service lifetime is <see cref="ServiceLifetime.Singleton" />. This means a single instance
/// is used by many <see cref="DbContext" /> instances. The implementation must be thread-safe.
/// This service cannot depend on services registered as <see cref="ServiceLifetime.Scoped" />.
/// </para>
/// </remarks>
public sealed record RelationalLiftableConstantExpressionDependencies
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,13 @@ private ProjectionBindingExpression AddClientProjection(Expression expression, T
return new ProjectionBindingExpression(_selectExpression, existingIndex, type);
}

private static T GetParameterValue<T>(QueryContext queryContext, string parameterName)
/// <summary>
/// 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.
/// </summary>
public static T GetParameterValue<T>(QueryContext queryContext, string parameterName)
#pragma warning restore IDE0052 // Remove unread private members
=> (T)queryContext.ParameterValues[parameterName]!;

Expand Down
31 changes: 31 additions & 0 deletions src/EFCore.Relational/Query/Internal/SingleQueryingEnumerable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,37 @@

namespace Microsoft.EntityFrameworkCore.Query.Internal;

/// <summary>
/// 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.
/// </summary>
public static class SingleQueryingEnumerable
{
/// <summary>
/// TODO
/// </summary>
public static SingleQueryingEnumerable<T> Create<T>(
RelationalQueryContext relationalQueryContext,
RelationalCommandCache relationalCommandCache,
IReadOnlyList<ReaderColumn?>? readerColumns,
Func<QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator, T> shaper,
Type contextType,
bool standAloneStateManager,
bool detailedErrorsEnabled,
bool threadSafetyChecksEnabled)
=> new(
relationalQueryContext,
relationalCommandCache,
readerColumns,
shaper,
contextType,
standAloneStateManager,
detailedErrorsEnabled,
threadSafetyChecksEnabled);
}

/// <summary>
/// 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Microsoft.EntityFrameworkCore.Query;
/// <summary>
/// Utilities used for implementing <see cref="IRelationalQuotableExpression" />.
/// </summary>
[Experimental("EF1003")]
[Experimental(EFDiagnostics.PrecompiledQueryExperimental)]
public static class RelationalExpressionQuotingUtilities
{
private static readonly ParameterExpression RelationalModelParameter
Expand Down

0 comments on commit cd6f35a

Please sign in to comment.