diff --git a/All.sln.DotSettings b/All.sln.DotSettings
index a3684295d00..ea433c87d71 100644
--- a/All.sln.DotSettings
+++ b/All.sln.DotSettings
@@ -199,6 +199,7 @@ Licensed under the Apache License, Version 2.0. See License.txt in the project r
True
True
True
+ True
True
True
True
diff --git a/src/EFCore.Relational/Query/Internal/CollectionMaterializationContext.cs b/src/EFCore.Relational/Query/Internal/CollectionMaterializationContext.cs
new file mode 100644
index 00000000000..ea4cdf05e98
--- /dev/null
+++ b/src/EFCore.Relational/Query/Internal/CollectionMaterializationContext.cs
@@ -0,0 +1,35 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.EntityFrameworkCore.Query.Internal
+{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public class CollectionMaterializationContext
+ {
+ public CollectionMaterializationContext(object parent, object collection, object[] parentIdentifier, object[] outerIdentifier)
+ {
+ Parent = parent;
+ Collection = collection;
+ ParentIdentifier = parentIdentifier;
+ OuterIdentifier = outerIdentifier;
+ ResultContext = new ResultContext();
+ }
+
+ public virtual ResultContext ResultContext { get; }
+ public virtual object Parent { get; }
+ public virtual object Collection { get; }
+ public virtual object[] ParentIdentifier { get; }
+ public virtual object[] OuterIdentifier { get; }
+ public virtual object[] SelfIdentifier { get; private set; }
+
+ public virtual void UpdateSelfIdentifier(object[] selfIdentifier)
+ {
+ SelfIdentifier = selfIdentifier;
+ }
+ }
+}
diff --git a/src/EFCore.Relational/Query/Internal/ParameterValueBasedSelectExpressionOptimizer.cs b/src/EFCore.Relational/Query/Internal/ParameterValueBasedSelectExpressionOptimizer.cs
new file mode 100644
index 00000000000..2763811d2d9
--- /dev/null
+++ b/src/EFCore.Relational/Query/Internal/ParameterValueBasedSelectExpressionOptimizer.cs
@@ -0,0 +1,356 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Linq;
+using System.Linq.Expressions;
+using Microsoft.EntityFrameworkCore.Internal;
+using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
+using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Storage.Internal;
+
+namespace Microsoft.EntityFrameworkCore.Query.Internal
+{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public class ParameterValueBasedSelectExpressionOptimizer
+ {
+ private readonly ISqlExpressionFactory _sqlExpressionFactory;
+ private readonly IParameterNameGeneratorFactory _parameterNameGeneratorFactory;
+ private readonly bool _useRelationalNulls;
+
+ public ParameterValueBasedSelectExpressionOptimizer(
+ ISqlExpressionFactory sqlExpressionFactory,
+ IParameterNameGeneratorFactory parameterNameGeneratorFactory,
+ bool useRelationalNulls)
+ {
+ _sqlExpressionFactory = sqlExpressionFactory;
+ _parameterNameGeneratorFactory = parameterNameGeneratorFactory;
+ _useRelationalNulls = useRelationalNulls;
+ }
+
+ public virtual (SelectExpression selectExpression, bool canCache) Optimize(
+ SelectExpression selectExpression, IReadOnlyDictionary parametersValues)
+ {
+ var canCache = true;
+
+ var inExpressionOptimized = new InExpressionValuesExpandingExpressionVisitor(
+ _sqlExpressionFactory, parametersValues).Visit(selectExpression);
+
+ if (!ReferenceEquals(selectExpression, inExpressionOptimized))
+ {
+ canCache = false;
+ }
+
+ var nullParametersOptimized = new ParameterNullabilityBasedSqlExpressionOptimizingExpressionVisitor(
+ _sqlExpressionFactory, _useRelationalNulls, parametersValues).Visit(inExpressionOptimized);
+
+ var fromSqlParameterOptimized = new FromSqlParameterApplyingExpressionVisitor(
+ _sqlExpressionFactory,
+ _parameterNameGeneratorFactory.Create(),
+ parametersValues).Visit(nullParametersOptimized);
+
+ if (!ReferenceEquals(nullParametersOptimized, fromSqlParameterOptimized))
+ {
+ canCache = false;
+ }
+
+ return (selectExpression: (SelectExpression)fromSqlParameterOptimized, canCache);
+ }
+
+ private class ParameterNullabilityBasedSqlExpressionOptimizingExpressionVisitor : SqlExpressionOptimizingExpressionVisitor
+ {
+ private readonly IReadOnlyDictionary _parametersValues;
+
+ public ParameterNullabilityBasedSqlExpressionOptimizingExpressionVisitor(
+ ISqlExpressionFactory sqlExpressionFactory,
+ bool useRelationalNulls,
+ IReadOnlyDictionary parametersValues)
+ : base(sqlExpressionFactory, useRelationalNulls)
+ {
+ _parametersValues = parametersValues;
+ }
+
+ protected override Expression VisitExtension(Expression extensionExpression)
+ {
+ if (extensionExpression is SelectExpression selectExpression)
+ {
+ var newSelectExpression = (SelectExpression)base.VisitExtension(extensionExpression);
+
+ // if predicate is optimized to true, we can simply remove it
+ var newPredicate = newSelectExpression.Predicate is SqlConstantExpression newSelectPredicateConstant
+ && !(selectExpression.Predicate is SqlConstantExpression)
+ ? (bool)newSelectPredicateConstant.Value
+ ? null
+ : SqlExpressionFactory.Equal(
+ newSelectPredicateConstant,
+ SqlExpressionFactory.Constant(true, newSelectPredicateConstant.TypeMapping))
+ : newSelectExpression.Predicate;
+
+ var newHaving = newSelectExpression.Having is SqlConstantExpression newSelectHavingConstant
+ && !(selectExpression.Having is SqlConstantExpression)
+ ? (bool)newSelectHavingConstant.Value
+ ? null
+ : SqlExpressionFactory.Equal(
+ newSelectHavingConstant,
+ SqlExpressionFactory.Constant(true, newSelectHavingConstant.TypeMapping))
+ : newSelectExpression.Having;
+
+ return !ReferenceEquals(newPredicate, newSelectExpression.Predicate)
+ || !ReferenceEquals(newHaving, newSelectExpression.Having)
+ ? newSelectExpression.Update(
+ newSelectExpression.Projection.ToList(),
+ newSelectExpression.Tables.ToList(),
+ newPredicate,
+ newSelectExpression.GroupBy.ToList(),
+ newHaving,
+ newSelectExpression.Orderings.ToList(),
+ newSelectExpression.Limit,
+ newSelectExpression.Offset,
+ newSelectExpression.IsDistinct,
+ newSelectExpression.Alias)
+ : newSelectExpression;
+ }
+
+ return base.VisitExtension(extensionExpression);
+ }
+
+ protected override Expression VisitSqlUnaryExpression(SqlUnaryExpression sqlUnaryExpression)
+ {
+ var result = base.VisitSqlUnaryExpression(sqlUnaryExpression);
+ if (result is SqlUnaryExpression newUnaryExpression
+ && newUnaryExpression.Operand is SqlParameterExpression parameterOperand)
+ {
+ var parameterValue = _parametersValues[parameterOperand.Name];
+ if (sqlUnaryExpression.OperatorType == ExpressionType.Equal)
+ {
+ return SqlExpressionFactory.Constant(parameterValue == null, sqlUnaryExpression.TypeMapping);
+ }
+
+ if (sqlUnaryExpression.OperatorType == ExpressionType.NotEqual)
+ {
+ return SqlExpressionFactory.Constant(parameterValue != null, sqlUnaryExpression.TypeMapping);
+ }
+ }
+
+ return result;
+ }
+ }
+
+ private class InExpressionValuesExpandingExpressionVisitor : ExpressionVisitor
+ {
+ private readonly ISqlExpressionFactory _sqlExpressionFactory;
+ private readonly IReadOnlyDictionary _parametersValues;
+
+ public InExpressionValuesExpandingExpressionVisitor(
+ ISqlExpressionFactory sqlExpressionFactory, IReadOnlyDictionary parametersValues)
+ {
+ _sqlExpressionFactory = sqlExpressionFactory;
+ _parametersValues = parametersValues;
+ }
+
+ public override Expression Visit(Expression expression)
+ {
+ if (expression is InExpression inExpression
+ && inExpression.Values != null)
+ {
+ var inValues = new List