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 dotnet#25009
  • Loading branch information
roji committed Jan 4, 2024
1 parent d5a2e08 commit fcf28ac
Show file tree
Hide file tree
Showing 31 changed files with 1,318 additions and 172 deletions.
1 change: 1 addition & 0 deletions EFCore.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,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
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public class EntityFrameworkRelationalServicesBuilder : EntityFrameworkServicesB
{ typeof(IRawSqlCommandBuilder), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IQuerySqlGeneratorFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IModificationCommandFactory), 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 @@ -185,6 +186,9 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices()
TryAdd<IRelationalParameterBasedSqlProcessorFactory, RelationalParameterBasedSqlProcessorFactory>();
TryAdd<IRelationalQueryStringFactory, RelationalQueryStringFactory>();
TryAdd<IQueryCompilationContextFactory, RelationalQueryCompilationContextFactory>();
TryAdd<ILiftableConstantFactory>(p => p.GetRequiredService<IRelationalLiftableConstantFactory>());
TryAdd<IRelationalLiftableConstantFactory, RelationalLiftableConstantFactory>();
TryAdd<ILiftableConstantProcessor, RelationalLiftableConstantProcessor>();

ServiceCollectionMap.GetInfrastructure()
.AddDependencySingleton<RelationalSqlGenerationHelperDependencies>()
Expand Down
12 changes: 12 additions & 0 deletions src/EFCore.Relational/Query/IRelationalLiftableConstantFactory.cs
Original file line number Diff line number Diff line change
@@ -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;

public interface IRelationalLiftableConstantFactory : ILiftableConstantFactory
{
LiftableConstantExpression CreateLiftableConstant(
Expression<Func<RelationalMaterializerLiftableConstantContext, object>> resolverExpression,
string variableName,
Type type);
}
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,8 @@ private ProjectionBindingExpression AddClientProjection(Expression expression, T
return new ProjectionBindingExpression(_selectExpression, existingIndex, type);
}

private static T GetParameterValue<T>(QueryContext queryContext, string parameterName)
// Public because can get referenced by precompiled shaper code
public static T GetParameterValue<T>(QueryContext queryContext, string parameterName)
#pragma warning restore IDE0052 // Remove unread private members
=> (T)queryContext.ParameterValues[parameterName]!;

Expand Down
22 changes: 22 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,28 @@

namespace Microsoft.EntityFrameworkCore.Query.Internal;

public static class SingleQueryingEnumerable
{
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
13 changes: 13 additions & 0 deletions src/EFCore.Relational/Query/RelationalLiftableConstantFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// 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;

public class RelationalLiftableConstantFactory : LiftableConstantFactory, IRelationalLiftableConstantFactory
{
public virtual LiftableConstantExpression CreateLiftableConstant(
Expression<Func<RelationalMaterializerLiftableConstantContext, object>> resolverExpression,
string variableName,
Type type)
=> new(resolverExpression, variableName, type);
}
32 changes: 32 additions & 0 deletions src/EFCore.Relational/Query/RelationalLiftableConstantProcessor.cs
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.

using Microsoft.EntityFrameworkCore.Query.Internal;

namespace Microsoft.EntityFrameworkCore.Query;

#pragma warning disable EF1001 // LiftableConstantProcessor is internal

public class RelationalLiftableConstantProcessor : LiftableConstantProcessor
{
private RelationalMaterializerLiftableConstantContext _relationalMaterializerLiftableConstantContext;

public RelationalLiftableConstantProcessor(
ShapedQueryCompilingExpressionVisitorDependencies dependencies,
RelationalShapedQueryCompilingExpressionVisitorDependencies relationalDependencies)
: base(dependencies)
=> _relationalMaterializerLiftableConstantContext = new(dependencies, relationalDependencies);

protected override ConstantExpression InlineConstant(LiftableConstantExpression liftableConstant)
{
if (liftableConstant.ResolverExpression is Expression<Func<RelationalMaterializerLiftableConstantContext, object>>
resolverExpression)
{
var resolver = resolverExpression.Compile(preferInterpretation: true);
var value = resolver(_relationalMaterializerLiftableConstantContext);
return Expression.Constant(value, liftableConstant.Type);
}

return base.InlineConstant(liftableConstant);
}
}
Original file line number Diff line number Diff line change
@@ -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;

public record RelationalMaterializerLiftableConstantContext(
ShapedQueryCompilingExpressionVisitorDependencies Dependencies,
RelationalShapedQueryCompilingExpressionVisitorDependencies RelationalDependencies)
: MaterializerLiftableConstantContext(Dependencies);
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@ namespace Microsoft.EntityFrameworkCore.Query;

public partial class RelationalShapedQueryCompilingExpressionVisitor
{
private sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisitor
/// <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>
[EntityFrameworkInternal]
public sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisitor
{
private static readonly MethodInfo ThrowReadValueExceptionMethod =
typeof(ShaperProcessingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(ThrowReadValueException))!;
Expand Down Expand Up @@ -160,7 +167,14 @@ private static TValue ThrowExtractJsonPropertyException<TValue>(Exception except
}
}

private static void InitializeIncludeCollection<TParent, TNavigationEntity>(
/// <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>
[EntityFrameworkInternal]
public static void InitializeIncludeCollection<TParent, TNavigationEntity>(
int collectionId,
QueryContext queryContext,
DbDataReader dbDataReader,
Expand Down Expand Up @@ -201,17 +215,27 @@ private static TValue ThrowExtractJsonPropertyException<TValue>(Exception except
resultCoordinator.SetSingleQueryCollectionContext(collectionId, collectionMaterializationContext);
}

private static void PopulateIncludeCollection<TIncludingEntity, TIncludedEntity>(
/// <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>
[EntityFrameworkInternal]
public static void PopulateIncludeCollection<TIncludingEntity, TIncludedEntity>(
int collectionId,
QueryContext queryContext,
DbDataReader dbDataReader,
SingleQueryResultCoordinator resultCoordinator,
Func<QueryContext, DbDataReader, object[]> parentIdentifier,
Func<QueryContext, DbDataReader, object[]> outerIdentifier,
Func<QueryContext, DbDataReader, object[]> selfIdentifier,
IReadOnlyList<ValueComparer> parentIdentifierValueComparers,
IReadOnlyList<ValueComparer> outerIdentifierValueComparers,
IReadOnlyList<ValueComparer> selfIdentifierValueComparers,
IReadOnlyList<Func<object, object, bool>> parentIdentifierValueComparers,
IReadOnlyList<Func<object, object, bool>> outerIdentifierValueComparers,
IReadOnlyList<Func<object, object, bool>> selfIdentifierValueComparers,
// IReadOnlyList<ValueComparer> parentIdentifierValueComparers,
// IReadOnlyList<ValueComparer> outerIdentifierValueComparers,
// IReadOnlyList<ValueComparer> selfIdentifierValueComparers,
Func<QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator, TIncludedEntity> innerShaper,
INavigationBase? inverseNavigation,
Action<TIncludingEntity, TIncludedEntity> fixup,
Expand All @@ -229,14 +253,14 @@ private static TValue ThrowExtractJsonPropertyException<TValue>(Exception except
return;
}

if (!CompareIdentifiers(
if (!CompareIdentifiers2(
outerIdentifierValueComparers,
outerIdentifier(queryContext, dbDataReader), collectionMaterializationContext.OuterIdentifier))
{
// Outer changed so collection has ended. Materialize last element.
GenerateCurrentElementIfPending();
// If parent also changed then this row is now pointing to element of next collection
if (!CompareIdentifiers(
if (!CompareIdentifiers2(
parentIdentifierValueComparers,
parentIdentifier(queryContext, dbDataReader), collectionMaterializationContext.ParentIdentifier))
{
Expand All @@ -255,7 +279,7 @@ private static TValue ThrowExtractJsonPropertyException<TValue>(Exception except

if (collectionMaterializationContext.SelfIdentifier != null)
{
if (CompareIdentifiers(selfIdentifierValueComparers, innerKey, collectionMaterializationContext.SelfIdentifier))
if (CompareIdentifiers2(selfIdentifierValueComparers, innerKey, collectionMaterializationContext.SelfIdentifier))
{
// repeated row for current element
// If it is pending materialization then it may have nested elements
Expand Down Expand Up @@ -319,7 +343,14 @@ void GenerateCurrentElementIfPending()
}
}

private static void InitializeSplitIncludeCollection<TParent, TNavigationEntity>(
/// <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>
[EntityFrameworkInternal]
public static void InitializeSplitIncludeCollection<TParent, TNavigationEntity>(
int collectionId,
QueryContext queryContext,
DbDataReader parentDataReader,
Expand Down Expand Up @@ -358,7 +389,14 @@ void GenerateCurrentElementIfPending()
resultCoordinator.SetSplitQueryCollectionContext(collectionId, splitQueryCollectionContext);
}

private static void PopulateSplitIncludeCollection<TIncludingEntity, TIncludedEntity>(
/// <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>
[EntityFrameworkInternal]
public static void PopulateSplitIncludeCollection<TIncludingEntity, TIncludedEntity>(
int collectionId,
RelationalQueryContext queryContext,
IExecutionStrategy executionStrategy,
Expand All @@ -367,7 +405,8 @@ void GenerateCurrentElementIfPending()
bool detailedErrorsEnabled,
SplitQueryResultCoordinator resultCoordinator,
Func<QueryContext, DbDataReader, object[]> childIdentifier,
IReadOnlyList<ValueComparer> identifierValueComparers,
IReadOnlyList<Func<object, object, bool>> identifierValueComparers,
// IReadOnlyList<ValueComparer> identifierValueComparers,
Func<QueryContext, DbDataReader, ResultContext, SplitQueryResultCoordinator, TIncludedEntity> innerShaper,
Action<QueryContext, IExecutionStrategy, SplitQueryResultCoordinator>? relatedDataLoaders,
INavigationBase? inverseNavigation,
Expand Down Expand Up @@ -414,7 +453,7 @@ void GenerateCurrentElementIfPending()
{
while (dataReaderContext.HasNext ?? dbDataReader.Read())
{
if (!CompareIdentifiers(
if (!CompareIdentifiers2(
identifierValueComparers,
splitQueryCollectionContext.ParentIdentifier, childIdentifier(queryContext, dbDataReader)))
{
Expand Down Expand Up @@ -451,7 +490,8 @@ void GenerateCurrentElementIfPending()
bool detailedErrorsEnabled,
SplitQueryResultCoordinator resultCoordinator,
Func<QueryContext, DbDataReader, object[]> childIdentifier,
IReadOnlyList<ValueComparer> identifierValueComparers,
IReadOnlyList<Func<object, object, bool>> identifierValueComparers,
// IReadOnlyList<ValueComparer> identifierValueComparers,
Func<QueryContext, DbDataReader, ResultContext, SplitQueryResultCoordinator, TIncludedEntity> innerShaper,
Func<QueryContext, IExecutionStrategy, SplitQueryResultCoordinator, Task>? relatedDataLoaders,
INavigationBase? inverseNavigation,
Expand Down Expand Up @@ -506,7 +546,7 @@ void GenerateCurrentElementIfPending()
{
while (dataReaderContext.HasNext ?? await dbDataReader.ReadAsync(queryContext.CancellationToken).ConfigureAwait(false))
{
if (!CompareIdentifiers(
if (!CompareIdentifiers2(
identifierValueComparers,
splitQueryCollectionContext.ParentIdentifier, childIdentifier(queryContext, dbDataReader)))
{
Expand Down Expand Up @@ -538,7 +578,8 @@ void GenerateCurrentElementIfPending()
}
}

private static TCollection InitializeCollection<TElement, TCollection>(
[EntityFrameworkInternal]
public static TCollection InitializeCollection<TElement, TCollection>(
int collectionId,
QueryContext queryContext,
DbDataReader dbDataReader,
Expand All @@ -560,7 +601,8 @@ void GenerateCurrentElementIfPending()
return (TCollection)collection;
}

private static void PopulateCollection<TCollection, TElement, TRelatedEntity>(
[EntityFrameworkInternal]
public static void PopulateCollection<TCollection, TElement, TRelatedEntity>(
int collectionId,
QueryContext queryContext,
DbDataReader dbDataReader,
Expand Down Expand Up @@ -1066,6 +1108,20 @@ private static async Task TaskAwaiter(Func<Task>[] taskFactories)
}
}

private static bool CompareIdentifiers2(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](left[i], right[i]))
{
return false;
}
}

return true;
}

private static bool CompareIdentifiers(IReadOnlyList<ValueComparer> valueComparers, object[] left, object[] right)
{
// Ignoring size check on all for perf as they should be same unless bug in code.
Expand Down

0 comments on commit fcf28ac

Please sign in to comment.