diff --git a/src/EFCore.Relational/Query/Internal/ShaperExpressionProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/ShaperExpressionProcessingExpressionVisitor.cs deleted file mode 100644 index 893ecdef8d2..00000000000 --- a/src/EFCore.Relational/Query/Internal/ShaperExpressionProcessingExpressionVisitor.cs +++ /dev/null @@ -1,188 +0,0 @@ -// 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.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore.Query.SqlExpressions; - -namespace Microsoft.EntityFrameworkCore.Query.Internal -{ - public class ShaperExpressionProcessingExpressionVisitor : ExpressionVisitor - { - private readonly SelectExpression _selectExpression; - private readonly ParameterExpression _dataReaderParameter; - private readonly ParameterExpression _resultCoordinatorParameter; - private readonly ParameterExpression _indexMapParameter; - private readonly IDictionary _mapping = new Dictionary(); - private readonly List _variables = new List(); - private readonly List _expressions = new List(); - private readonly List _collectionPopulatingExpressions = new List(); - - public ShaperExpressionProcessingExpressionVisitor( - SelectExpression selectExpression, - ParameterExpression dataReaderParameter, - ParameterExpression resultCoordinatorParameter, - ParameterExpression indexMapParameter) - { - _selectExpression = selectExpression; - _dataReaderParameter = dataReaderParameter; - _resultCoordinatorParameter = resultCoordinatorParameter; - _indexMapParameter = indexMapParameter; - } - - public virtual Expression Inject(Expression expression) - { - var result = Visit(expression); - - if (_collectionPopulatingExpressions.Count > 0) - { - _expressions.Add(result); - result = Expression.Block(_variables, _expressions); - _expressions.Clear(); - _variables.Clear(); - - var resultParameter = Expression.Parameter(result.Type, "result"); - - _expressions.Add( - Expression.IfThen( - Expression.Equal(resultParameter, Expression.Default(result.Type)), - Expression.Assign(resultParameter, result))); - _expressions.AddRange(_collectionPopulatingExpressions); - _expressions.Add(resultParameter); - - return ConvertToLambda(Expression.Block(_expressions), resultParameter); - } - else if (_expressions.All(e => e.NodeType == ExpressionType.Assign)) - { - result = new ReplacingExpressionVisitor(_expressions.Cast() - .ToDictionary(e => e.Left, e => e.Right)).Visit(result); - } - else - { - _expressions.Add(result); - result = Expression.Block(_variables, _expressions); - } - - return ConvertToLambda(result, Expression.Parameter(result.Type, "result")); - } - - private LambdaExpression ConvertToLambda(Expression result, ParameterExpression resultParameter) - => _indexMapParameter != null - ? Expression.Lambda( - result, - QueryCompilationContext.QueryContextParameter, - _dataReaderParameter, - resultParameter, - _indexMapParameter, - _resultCoordinatorParameter) - : Expression.Lambda( - result, - QueryCompilationContext.QueryContextParameter, - _dataReaderParameter, - resultParameter, - _resultCoordinatorParameter); - - protected override Expression VisitExtension(Expression extensionExpression) - { - switch (extensionExpression) - { - case EntityShaperExpression entityShaperExpression: - { - var key = GenerateKey((ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression); - if (!_mapping.TryGetValue(key, out var variable)) - { - variable = Expression.Parameter(entityShaperExpression.EntityType.ClrType); - _variables.Add(variable); - _expressions.Add(Expression.Assign(variable, entityShaperExpression)); - _mapping[key] = variable; - } - - return variable; - } - - case ProjectionBindingExpression projectionBindingExpression: - { - var key = GenerateKey(projectionBindingExpression); - if (!_mapping.TryGetValue(key, out var variable)) - { - variable = Expression.Parameter(projectionBindingExpression.Type); - _variables.Add(variable); - _expressions.Add(Expression.Assign(variable, projectionBindingExpression)); - _mapping[key] = variable; - } - - return variable; - } - - case IncludeExpression includeExpression: - { - var entity = Visit(includeExpression.EntityExpression); - if (includeExpression.NavigationExpression is RelationalCollectionShaperExpression - relationalCollectionShaperExpression) - { - var innerShaper = new ShaperExpressionProcessingExpressionVisitor( - _selectExpression, _dataReaderParameter, _resultCoordinatorParameter, null) - .Inject(relationalCollectionShaperExpression.InnerShaper); - - _collectionPopulatingExpressions.Add(new CollectionPopulatingExpression( - relationalCollectionShaperExpression.Update( - relationalCollectionShaperExpression.ParentIdentifier, - relationalCollectionShaperExpression.OuterIdentifier, - relationalCollectionShaperExpression.SelfIdentifier, - innerShaper), - includeExpression.Navigation.ClrType, - true)); - - _expressions.Add(new CollectionInitializingExpression( - relationalCollectionShaperExpression.CollectionId, - entity, - relationalCollectionShaperExpression.ParentIdentifier, - relationalCollectionShaperExpression.OuterIdentifier, - includeExpression.Navigation, - includeExpression.Navigation.ClrType)); - } - else - { - _expressions.Add(includeExpression.Update( - entity, - Visit(includeExpression.NavigationExpression))); - } - - return entity; - } - - case RelationalCollectionShaperExpression relationalCollectionShaperExpression2: - { - var innerShaper = new ShaperExpressionProcessingExpressionVisitor( - _selectExpression, _dataReaderParameter, _resultCoordinatorParameter, null) - .Inject(relationalCollectionShaperExpression2.InnerShaper); - - _collectionPopulatingExpressions.Add(new CollectionPopulatingExpression( - relationalCollectionShaperExpression2.Update( - relationalCollectionShaperExpression2.ParentIdentifier, - relationalCollectionShaperExpression2.OuterIdentifier, - relationalCollectionShaperExpression2.SelfIdentifier, - innerShaper), - relationalCollectionShaperExpression2.Type, - false)); - - return new CollectionInitializingExpression( - relationalCollectionShaperExpression2.CollectionId, - null, - relationalCollectionShaperExpression2.ParentIdentifier, - relationalCollectionShaperExpression2.OuterIdentifier, - relationalCollectionShaperExpression2.Navigation, - relationalCollectionShaperExpression2.Type); - } - } - - return base.VisitExtension(extensionExpression); - } - - private Expression GenerateKey(ProjectionBindingExpression projectionBindingExpression) - => projectionBindingExpression.ProjectionMember != null - ? _selectExpression.GetMappedProjection(projectionBindingExpression.ProjectionMember) - : projectionBindingExpression; - } -} diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.AsyncQueryingEnumerable.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.AsyncQueryingEnumerable.cs index ac7f07ec00f..31b0dc5dc5c 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.AsyncQueryingEnumerable.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.AsyncQueryingEnumerable.cs @@ -19,7 +19,7 @@ private class AsyncQueryingEnumerable : IAsyncEnumerable { private readonly RelationalQueryContext _relationalQueryContext; private readonly SelectExpression _selectExpression; - private readonly Func _shaper; + private readonly Func _shaper; private readonly IQuerySqlGeneratorFactory _querySqlGeneratorFactory; private readonly Type _contextType; private readonly IDiagnosticsLogger _logger; @@ -32,7 +32,7 @@ public AsyncQueryingEnumerable( ISqlExpressionFactory sqlExpressionFactory, IParameterNameGeneratorFactory parameterNameGeneratorFactory, SelectExpression selectExpression, - Func shaper, + Func shaper, Type contextType, IDiagnosticsLogger logger) { @@ -56,7 +56,7 @@ private sealed class AsyncEnumerator : IAsyncEnumerator private ResultCoordinator _resultCoordinator; private readonly RelationalQueryContext _relationalQueryContext; private readonly SelectExpression _selectExpression; - private readonly Func _shaper; + private readonly Func _shaper; private readonly IQuerySqlGeneratorFactory _querySqlGeneratorFactory; private readonly Type _contextType; private readonly IDiagnosticsLogger _logger; @@ -147,15 +147,22 @@ public async ValueTask MoveNextAsync() { _resultCoordinator.ResultReady = true; _resultCoordinator.HasNext = null; - Current = _shaper(_relationalQueryContext, _dataReader.DbDataReader, Current, _indexMap, _resultCoordinator); + Current = _shaper(_relationalQueryContext, _dataReader.DbDataReader, + _resultCoordinator.ResultContext, _indexMap, _resultCoordinator); if (_resultCoordinator.ResultReady) { + // We generated a result so null out previously stored values + _resultCoordinator.ResultContext.Values = null; break; } if (!await _dataReader.ReadAsync()) { _resultCoordinator.HasNext = false; + // Enumeration has ended, materialize last element + _resultCoordinator.ResultReady = true; + Current = _shaper(_relationalQueryContext, _dataReader.DbDataReader, + _resultCoordinator.ResultContext, _indexMap, _resultCoordinator); break; } diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs index 027678e5edf..2a928128edc 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs @@ -85,27 +85,57 @@ private static void PopulateCollection( Func parentIdentifier, Func outerIdentifier, Func selfIdentifier, - Func innerShaper) + Func innerShaper) where TRelatedEntity : TElement where TCollection : class, ICollection { var collectionMaterializationContext = resultCoordinator.Collections[collectionId]; - if (collectionMaterializationContext.Collection is null) { - // nothing to include since no collection created + // nothing to materialize since no collection created + return; + } + void processCurrentElementRow() + { + var previousResultReady = resultCoordinator.ResultReady; + resultCoordinator.ResultReady = true; + var element = innerShaper( + queryContext, dbDataReader, collectionMaterializationContext.ResultContext, resultCoordinator); + if (resultCoordinator.ResultReady) + { + // related element is materialized + collectionMaterializationContext.ResultContext.Values = null; + ((TCollection)collectionMaterializationContext.Collection).Add(element); + } + + resultCoordinator.ResultReady &= previousResultReady; + } + + void generateCurrentElementIfPending() + { + if (collectionMaterializationContext.ResultContext.Values != null) + { + resultCoordinator.HasNext = false; + processCurrentElementRow(); + } + collectionMaterializationContext.UpdateSelfIdentifier(null); + } + + if (resultCoordinator.HasNext == false) + { + // Outer Enumerator has ended + generateCurrentElementIfPending(); return; } if (!StructuralComparisons.StructuralEqualityComparer.Equals( outerIdentifier(queryContext, dbDataReader), collectionMaterializationContext.OuterIdentifier)) { - if (StructuralComparisons.StructuralEqualityComparer.Equals( + // 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 (!StructuralComparisons.StructuralEqualityComparer.Equals( parentIdentifier(queryContext, dbDataReader), collectionMaterializationContext.ParentIdentifier)) - { - resultCoordinator.ResultReady = false; - } - else { resultCoordinator.HasNext = true; } @@ -116,24 +146,40 @@ private static void PopulateCollection( var innerKey = selfIdentifier(queryContext, dbDataReader); if (innerKey.Any(e => e == null)) { - // If innerKey was null then return since no related data + // No correlated element return; } - if (StructuralComparisons.StructuralEqualityComparer.Equals( - innerKey, collectionMaterializationContext.SelfIdentifier)) + if (collectionMaterializationContext.SelfIdentifier != null) { - // We don't need to materialize this entity but we may need to populate inner collections if any. - innerShaper(queryContext, dbDataReader, (TRelatedEntity)collectionMaterializationContext.Current, resultCoordinator); - - return; + if (StructuralComparisons.StructuralEqualityComparer.Equals( + innerKey, collectionMaterializationContext.SelfIdentifier)) + { + // repeated row for current element + // If it is pending materialization then it may have nested elements + if (collectionMaterializationContext.ResultContext.Values != null) + { + processCurrentElementRow(); + } + resultCoordinator.ResultReady = false; + return; + } + else + { + // Row for new element which is not first element + // So materialize the element + generateCurrentElementIfPending(); + resultCoordinator.HasNext = null; + collectionMaterializationContext.UpdateSelfIdentifier(innerKey); + } + } + else + { + // First row for current element + collectionMaterializationContext.UpdateSelfIdentifier(innerKey); } - var relatedEntity = innerShaper(queryContext, dbDataReader, default, resultCoordinator); - collectionMaterializationContext.UpdateCurrent(relatedEntity, innerKey); - - ((TCollection)collectionMaterializationContext.Collection).Add(relatedEntity); - + processCurrentElementRow(); resultCoordinator.ResultReady = false; } @@ -149,25 +195,63 @@ private static void PopulateIncludeCollection Func parentIdentifier, Func outerIdentifier, Func selfIdentifier, - Func innerShaper, + Func innerShaper, INavigation inverseNavigation, Action fixup, bool trackingQuery) { var collectionMaterializationContext = resultCoordinator.Collections[collectionId]; - var parentEntity = collectionMaterializationContext.Parent; - - if (parentEntity is TIncludingEntity entity) + if (collectionMaterializationContext.Parent is TIncludingEntity entity) { + void processCurrentElementRow() + { + var previousResultReady = resultCoordinator.ResultReady; + resultCoordinator.ResultReady = true; + var relatedEntity = innerShaper( + queryContext, dbDataReader, collectionMaterializationContext.ResultContext, resultCoordinator); + if (resultCoordinator.ResultReady) + { + // related entity is materialized + collectionMaterializationContext.ResultContext.Values = null; + if (!trackingQuery) + { + fixup(entity, relatedEntity); + if (inverseNavigation != null + && !inverseNavigation.IsCollection()) + { + SetIsLoadedNoTracking(relatedEntity, inverseNavigation); + } + } + } + + resultCoordinator.ResultReady &= previousResultReady; + } + + void generateCurrentElementIfPending() + { + if (collectionMaterializationContext.ResultContext.Values != null) + { + resultCoordinator.HasNext = false; + processCurrentElementRow(); + } + collectionMaterializationContext.UpdateSelfIdentifier(null); + } + + if (resultCoordinator.HasNext == false) + { + // Outer Enumerator has ended + generateCurrentElementIfPending(); + return; + } + if (!StructuralComparisons.StructuralEqualityComparer.Equals( outerIdentifier(queryContext, dbDataReader), collectionMaterializationContext.OuterIdentifier)) { - if (StructuralComparisons.StructuralEqualityComparer.Equals( + // 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 (!StructuralComparisons.StructuralEqualityComparer.Equals( parentIdentifier(queryContext, dbDataReader), collectionMaterializationContext.ParentIdentifier)) - { - resultCoordinator.ResultReady = false; - } - else { resultCoordinator.HasNext = true; } @@ -182,29 +266,36 @@ private static void PopulateIncludeCollection return; } - if (StructuralComparisons.StructuralEqualityComparer.Equals( - innerKey, collectionMaterializationContext.SelfIdentifier)) - { - // We don't need to materialize this entity but we may need to populate inner collections if any. - innerShaper( - queryContext, dbDataReader, (TIncludedEntity)collectionMaterializationContext.Current, resultCoordinator); - - return; - } - - var relatedEntity = innerShaper(queryContext, dbDataReader, default, resultCoordinator); - collectionMaterializationContext.UpdateCurrent(relatedEntity, innerKey); - - if (!trackingQuery) + if (collectionMaterializationContext.SelfIdentifier != null) { - fixup(entity, relatedEntity); - if (inverseNavigation != null - && !inverseNavigation.IsCollection()) + if (StructuralComparisons.StructuralEqualityComparer.Equals( + innerKey, collectionMaterializationContext.SelfIdentifier)) { - SetIsLoadedNoTracking(relatedEntity, inverseNavigation); + // repeated row for current element + // If it is pending materialization then it may have nested elements + if (collectionMaterializationContext.ResultContext.Values != null) + { + processCurrentElementRow(); + } + resultCoordinator.ResultReady = false; + return; } + else + { + // Row for new element which is not first element + // So materialize the element + generateCurrentElementIfPending(); + resultCoordinator.HasNext = null; + collectionMaterializationContext.UpdateSelfIdentifier(innerKey); + } + } + else + { + // First row for current element + collectionMaterializationContext.UpdateSelfIdentifier(innerKey); } + processCurrentElementRow(); resultCoordinator.ResultReady = false; } } diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.QueryingEnumerable.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.QueryingEnumerable.cs index a377eac9f55..6b1e8fcb26a 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.QueryingEnumerable.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.QueryingEnumerable.cs @@ -7,7 +7,6 @@ using System.Data.Common; using System.Linq; using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; using Microsoft.EntityFrameworkCore.Storage; @@ -19,7 +18,7 @@ private class QueryingEnumerable : IEnumerable { private readonly RelationalQueryContext _relationalQueryContext; private readonly SelectExpression _selectExpression; - private readonly Func _shaper; + private readonly Func _shaper; private readonly IQuerySqlGeneratorFactory _querySqlGeneratorFactory; private readonly Type _contextType; private readonly IDiagnosticsLogger _logger; @@ -31,7 +30,7 @@ public QueryingEnumerable(RelationalQueryContext relationalQueryContext, ISqlExpressionFactory sqlExpressionFactory, IParameterNameGeneratorFactory parameterNameGeneratorFactory, SelectExpression selectExpression, - Func shaper, + Func shaper, Type contextType, IDiagnosticsLogger logger) { @@ -55,7 +54,7 @@ private sealed class Enumerator : IEnumerator private ResultCoordinator _resultCoordinator; private readonly RelationalQueryContext _relationalQueryContext; private readonly SelectExpression _selectExpression; - private readonly Func _shaper; + private readonly Func _shaper; private readonly IQuerySqlGeneratorFactory _querySqlGeneratorFactory; private readonly Type _contextType; private readonly IDiagnosticsLogger _logger; @@ -142,15 +141,22 @@ public bool MoveNext() { _resultCoordinator.ResultReady = true; _resultCoordinator.HasNext = null; - Current = _shaper(_relationalQueryContext, _dataReader.DbDataReader, Current, _indexMap, _resultCoordinator); + Current = _shaper(_relationalQueryContext, _dataReader.DbDataReader, + _resultCoordinator.ResultContext, _indexMap, _resultCoordinator); if (_resultCoordinator.ResultReady) { + // We generated a result so null out previously stored values + _resultCoordinator.ResultContext.Values = null; break; } if (!_dataReader.Read()) { _resultCoordinator.HasNext = false; + // Enumeration has ended, materialize last element + _resultCoordinator.ResultReady = true; + Current = _shaper(_relationalQueryContext, _dataReader.DbDataReader, + _resultCoordinator.ResultContext, _indexMap, _resultCoordinator); break; } diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperExpressionProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperExpressionProcessingExpressionVisitor.cs new file mode 100644 index 00000000000..584b54dbd1b --- /dev/null +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperExpressionProcessingExpressionVisitor.cs @@ -0,0 +1,317 @@ +// 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.Generic; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; + +namespace Microsoft.EntityFrameworkCore.Query +{ + public partial class RelationalShapedQueryCompilingExpressionVisitor + { + private class ShaperExpressionProcessingExpressionVisitor : ExpressionVisitor + { + private static readonly MemberInfo _resultContextValuesMemberInfo + = typeof(ResultContext).GetTypeInfo().GetMember(nameof(ResultContext.Values))[0]; + private static readonly MemberInfo _resultCoordinatorResultReadyMemberInfo + = typeof(ResultCoordinator).GetTypeInfo().GetMember(nameof(ResultCoordinator.ResultReady))[0]; + + private readonly SelectExpression _selectExpression; + private readonly ParameterExpression _dataReaderParameter; + private readonly ParameterExpression _resultContextParameter; + private readonly ParameterExpression _resultCoordinatorParameter; + private readonly ParameterExpression _indexMapParameter; + + private readonly IDictionary _mapping = new Dictionary(); + + // There are always entity variables to avoid materializing same entity twice + private readonly List _variables = new List(); + private readonly List _expressions = new List(); + + // IncludeExpressions are added at the end in case they are using ValuesArray + private readonly List _includeExpressions = new List(); + + // If there is collection shaper then we need to construct ValuesArray to store values temporarily in ResultContext + private List _collectionPopulatingExpressions; + private Expression _valuesArrayExpression; + private List _valuesArrayInitializers; + + private bool _containsCollectionMaterialization; + + public ShaperExpressionProcessingExpressionVisitor( + SelectExpression selectExpression, + ParameterExpression dataReaderParameter, + ParameterExpression resultCoordinatorParameter, + ParameterExpression indexMapParameter) + { + _selectExpression = selectExpression; + _dataReaderParameter = dataReaderParameter; + _resultContextParameter = Expression.Parameter(typeof(ResultContext), "resultContext"); + _resultCoordinatorParameter = resultCoordinatorParameter; + _indexMapParameter = indexMapParameter; + } + + private class CollectionShaperFindingExpressionVisitor : ExpressionVisitor + { + private bool _containsCollection; + public bool ContainsCollectionMaterialization(Expression expression) + { + _containsCollection = false; + + Visit(expression); + + return _containsCollection; + } + + public override Expression Visit(Expression expression) + { + if (_containsCollection) + { + return expression; + } + + if (expression is RelationalCollectionShaperExpression) + { + _containsCollection = true; + + return expression; + } + + return base.Visit(expression); + } + } + + public virtual Expression Inject(Expression expression) + { + _containsCollectionMaterialization = new CollectionShaperFindingExpressionVisitor() + .ContainsCollectionMaterialization(expression); + + if (_containsCollectionMaterialization) + { + _valuesArrayExpression = Expression.MakeMemberAccess(_resultContextParameter, _resultContextValuesMemberInfo); + _collectionPopulatingExpressions = new List(); + _valuesArrayInitializers = new List(); + } + + var result = Visit(expression); + + if (_containsCollectionMaterialization) + { + var valueArrayInitializationExpression = Expression.Assign(_valuesArrayExpression, + Expression.NewArrayInit( + typeof(object), + _valuesArrayInitializers)); + + _expressions.Add(valueArrayInitializationExpression); + _expressions.AddRange(_includeExpressions); + + var initializationBlock = Expression.Block( + _variables, + _expressions); + + var conditionalMaterializationExpressions = new List(); + + conditionalMaterializationExpressions.Add(Expression.IfThen( + Expression.Equal(_valuesArrayExpression, Expression.Constant(null, typeof(object[]))), + initializationBlock)); + + conditionalMaterializationExpressions.AddRange(_collectionPopulatingExpressions); + + conditionalMaterializationExpressions.Add( + Expression.Condition( + Expression.IsTrue( + Expression.MakeMemberAccess( + _resultCoordinatorParameter, _resultCoordinatorResultReadyMemberInfo)), + result, + Expression.Default(result.Type))); + + result = Expression.Block(conditionalMaterializationExpressions); + } + else + { + _expressions.AddRange(_includeExpressions); + _expressions.Add(result); + result = Expression.Block(_variables, _expressions); + } + + return ConvertToLambda(result); + } + + private LambdaExpression ConvertToLambda(Expression result) + => _indexMapParameter != null + ? Expression.Lambda( + result, + QueryCompilationContext.QueryContextParameter, + _dataReaderParameter, + _resultContextParameter, + _indexMapParameter, + _resultCoordinatorParameter) + : Expression.Lambda( + result, + QueryCompilationContext.QueryContextParameter, + _dataReaderParameter, + _resultContextParameter, + _resultCoordinatorParameter); + + protected override Expression VisitExtension(Expression extensionExpression) + { + switch (extensionExpression) + { + case EntityShaperExpression entityShaperExpression: + { + var key = GenerateKey((ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression); + if (!_mapping.TryGetValue(key, out var accessor)) + { + var entityParameter = Expression.Parameter(entityShaperExpression.Type); + _variables.Add(entityParameter); + _expressions.Add(Expression.Assign(entityParameter, entityShaperExpression)); + + if (_containsCollectionMaterialization) + { + _valuesArrayInitializers.Add(entityParameter); + accessor = Expression.Convert( + Expression.ArrayIndex( + _valuesArrayExpression, + Expression.Constant(_valuesArrayInitializers.Count - 1)), + entityShaperExpression.Type); + } + else + { + accessor = entityParameter; + } + + _mapping[key] = accessor; + } + + return accessor; + } + + case ProjectionBindingExpression projectionBindingExpression: + { + var key = GenerateKey(projectionBindingExpression); + if (!_mapping.TryGetValue(key, out var accessor)) + { + var valueParameter = Expression.Parameter(projectionBindingExpression.Type); + _variables.Add(valueParameter); + _expressions.Add(Expression.Assign(valueParameter, projectionBindingExpression)); + + if (_containsCollectionMaterialization) + { + var expressionToAdd = (Expression)valueParameter; + if (expressionToAdd.Type.IsValueType) + { + expressionToAdd = Expression.Convert(expressionToAdd, typeof(object)); + } + + _valuesArrayInitializers.Add(expressionToAdd); + accessor = Expression.Convert( + Expression.ArrayIndex( + _valuesArrayExpression, + Expression.Constant(_valuesArrayInitializers.Count - 1)), + projectionBindingExpression.Type); + } + else + { + accessor = valueParameter; + } + + _mapping[key] = accessor; + } + + return accessor; + } + + case IncludeExpression includeExpression: + { + var entity = Visit(includeExpression.EntityExpression); + if (includeExpression.NavigationExpression is RelationalCollectionShaperExpression + relationalCollectionShaperExpression) + { + var innerShaper = new ShaperExpressionProcessingExpressionVisitor( + _selectExpression, _dataReaderParameter, _resultCoordinatorParameter, null) + .Inject(relationalCollectionShaperExpression.InnerShaper); + + _collectionPopulatingExpressions.Add(new CollectionPopulatingExpression( + relationalCollectionShaperExpression.Update( + relationalCollectionShaperExpression.ParentIdentifier, + relationalCollectionShaperExpression.OuterIdentifier, + relationalCollectionShaperExpression.SelfIdentifier, + innerShaper), + includeExpression.Navigation.ClrType, + true)); + + _includeExpressions.Add(new CollectionInitializingExpression( + relationalCollectionShaperExpression.CollectionId, + entity, + relationalCollectionShaperExpression.ParentIdentifier, + relationalCollectionShaperExpression.OuterIdentifier, + includeExpression.Navigation, + includeExpression.Navigation.ClrType)); + } + else + { + _includeExpressions.Add(includeExpression.Update( + entity, + Visit(includeExpression.NavigationExpression))); + } + + return entity; + } + + case RelationalCollectionShaperExpression relationalCollectionShaperExpression2: + { + var key = GenerateKey(relationalCollectionShaperExpression2); + if (!_mapping.TryGetValue(key, out var accessor)) + { + var innerShaper = new ShaperExpressionProcessingExpressionVisitor( + _selectExpression, _dataReaderParameter, _resultCoordinatorParameter, null) + .Inject(relationalCollectionShaperExpression2.InnerShaper); + + _collectionPopulatingExpressions.Add(new CollectionPopulatingExpression( + relationalCollectionShaperExpression2.Update( + relationalCollectionShaperExpression2.ParentIdentifier, + relationalCollectionShaperExpression2.OuterIdentifier, + relationalCollectionShaperExpression2.SelfIdentifier, + innerShaper), + relationalCollectionShaperExpression2.Type, + false)); + + var collectionParameter = Expression.Parameter(relationalCollectionShaperExpression2.Type); + _variables.Add(collectionParameter); + _expressions.Add(Expression.Assign( + collectionParameter, + new CollectionInitializingExpression( + relationalCollectionShaperExpression2.CollectionId, + null, + relationalCollectionShaperExpression2.ParentIdentifier, + relationalCollectionShaperExpression2.OuterIdentifier, + relationalCollectionShaperExpression2.Navigation, + relationalCollectionShaperExpression2.Type))); + + _valuesArrayInitializers.Add(collectionParameter); + accessor = Expression.Convert( + Expression.ArrayIndex( + _valuesArrayExpression, + Expression.Constant(_valuesArrayInitializers.Count - 1)), + relationalCollectionShaperExpression2.Type); + + _mapping[key] = accessor; + } + + return accessor; + } + } + + return base.VisitExtension(extensionExpression); + } + + private Expression GenerateKey(Expression expression) + => expression is ProjectionBindingExpression projectionBindingExpression + && projectionBindingExpression.ProjectionMember != null + ? _selectExpression.GetMappedProjection(projectionBindingExpression.ProjectionMember) + : expression; + } + } +} diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs index b3bdfd7f6eb..f58f05ca88e 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs @@ -6,7 +6,6 @@ using System.Data.Common; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; namespace Microsoft.EntityFrameworkCore.Query @@ -106,6 +105,12 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp private class ResultCoordinator { + public ResultCoordinator() + { + ResultContext = new ResultContext(); + } + + public ResultContext ResultContext { get; } public bool ResultReady { get; set; } public bool? HasNext { get; set; } public IList Collections { get; } = new List(); @@ -120,7 +125,11 @@ public void SetCollectionMaterializationContext( Collections[collectionId] = collectionMaterializationContext; } + } + private class ResultContext + { + public object[] Values { get; set; } } private class CollectionMaterializationContext @@ -131,8 +140,10 @@ public CollectionMaterializationContext(object parent, object collection, object Collection = collection; ParentIdentifier = parentIdentifier; OuterIdentifier = outerIdentifier; + ResultContext = new ResultContext(); } + public ResultContext ResultContext { get; } public object Parent { get; } public object Collection { get; } public object Current { get; private set; } @@ -140,9 +151,8 @@ public CollectionMaterializationContext(object parent, object collection, object public object[] OuterIdentifier { get; } public object[] SelfIdentifier { get; private set; } - public void UpdateCurrent(object current, object[] selfIdentifier) + public void UpdateSelfIdentifier(object[] selfIdentifier) { - Current = current; SelfIdentifier = selfIdentifier; } } diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.Select.cs b/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.Select.cs index fe3ff81016a..8213051793b 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.Select.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.Select.cs @@ -61,6 +61,7 @@ FROM root c WHERE (c[""Discriminator""] = ""Customer"")"); } + [ConditionalTheory(Skip = "Issue$14935")] public override async Task Projection_when_client_evald_subquery(bool isAsync) { await base.Projection_when_client_evald_subquery(isAsync); diff --git a/test/EFCore.InMemory.FunctionalTests/Query/AsyncSimpleQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/AsyncSimpleQueryInMemoryTest.cs index 85fadfb1e0a..3a0863074df 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/AsyncSimpleQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/AsyncSimpleQueryInMemoryTest.cs @@ -37,5 +37,17 @@ public override Task Query_backed_by_database_view() { return base.Query_backed_by_database_view(); } + + [ConditionalFact(Skip = "See issue#16963")] + public override Task ToArray_on_nav_subquery_in_projection() + { + return base.ToArray_on_nav_subquery_in_projection(); + } + + [ConditionalFact(Skip = "See issue#16963")] + public override Task ToArray_on_nav_subquery_in_projection_nested() + { + return base.ToArray_on_nav_subquery_in_projection_nested(); + } } } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs index 47d9b39462c..66934993c12 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs @@ -430,5 +430,11 @@ public override Task SelectMany_correlated_with_outer_4(bool isAsync) } #endregion + + [ConditionalTheory(Skip = "Issue#16963")] + public override Task Projection_when_client_evald_subquery(bool isAsync) + { + return base.Projection_when_client_evald_subquery(isAsync); + } } } diff --git a/test/EFCore.Specification.Tests/ConferencePlannerTestBase.cs b/test/EFCore.Specification.Tests/ConferencePlannerTestBase.cs index 60b3f5e59f0..4c22f153016 100644 --- a/test/EFCore.Specification.Tests/ConferencePlannerTestBase.cs +++ b/test/EFCore.Specification.Tests/ConferencePlannerTestBase.cs @@ -265,16 +265,13 @@ public AttendeesController(ApplicationDbContext db) public async Task> GetSessions(string username) { - var sessions = await _db.Sessions.AsNoTracking() + return await _db.Sessions.AsNoTracking() .Include(s => s.Track) .Include(s => s.SessionSpeakers) .ThenInclude(ss => ss.Speaker) .Where(s => s.SessionAttendees.Any(sa => sa.Attendee.UserName == username)) - //.Select(m => m.MapSessionResponse()) + .Select(m => m.MapSessionResponse()) .ToListAsync(); - - // BUG: Working around EF Core 3.0 issue: https://github.com/aspnet/EntityFrameworkCore/issues/16318 - return sessions.Select(s => s.MapSessionResponse()).ToList(); } public async Task Post(ConferenceDTO.Attendee input) @@ -610,16 +607,12 @@ public SessionsController(ApplicationDbContext db) public async Task> Get() { - var sessions = await _db.Sessions.AsNoTracking() + return await _db.Sessions.AsNoTracking() .Include(s => s.Track) .Include(s => s.SessionSpeakers) .ThenInclude(ss => ss.Speaker) - //.Select(m => m.MapSessionResponse()) + .Select(m => m.MapSessionResponse()) .ToListAsync(); - - // BUG: Working around EF Core 3.0 issue: https://github.com/aspnet/EntityFrameworkCore/issues/16318 - return sessions.Select(s => s.MapSessionResponse()) - .ToList(); } public async Task Get(int id) @@ -746,14 +739,11 @@ public SpeakersController(ApplicationDbContext db) public async Task> GetSpeakers() { - var speakers = await _db.Speakers.AsNoTracking() + return await _db.Speakers.AsNoTracking() .Include(s => s.SessionSpeakers) .ThenInclude(ss => ss.Session) - //.Select(s => s.MapSpeakerResponse()) + .Select(s => s.MapSpeakerResponse()) .ToListAsync(); - - // BUG: Working around EF Core 3.0 issue: https://github.com/aspnet/EntityFrameworkCore/issues/16318 - return speakers.Select(s => s.MapSpeakerResponse()).ToList(); } public async Task GetSpeaker(int id) diff --git a/test/EFCore.Specification.Tests/Query/AsyncSimpleQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AsyncSimpleQueryTestBase.cs index bb5b09712b0..3edd88d8ef1 100644 --- a/test/EFCore.Specification.Tests/Query/AsyncSimpleQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/AsyncSimpleQueryTestBase.cs @@ -78,7 +78,7 @@ public virtual async Task ToList_context_subquery_deadlock_issue() } } - [ConditionalFact(Skip = "Issue#16318")] + [ConditionalFact] public virtual async Task ToArray_on_nav_subquery_in_projection() { using (var context = CreateContext()) @@ -95,7 +95,7 @@ var results } } - [ConditionalFact(Skip = "Issue#16318")] + [ConditionalFact] public virtual async Task ToArray_on_nav_subquery_in_projection_nested() { using (var context = CreateContext()) diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index af2ffaff4b0..41b7372d0ec 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -4320,7 +4320,7 @@ select g.Weapons.ToList()).Select(e => e.Count), assertOrder: true); } - [ConditionalTheory(Skip = "Issue#16318")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Correlated_collections_naked_navigation_with_ToArray(bool isAsync) { @@ -4366,7 +4366,7 @@ orderby g.Nickname elementAsserter: CollectionAsserter(e => e.Id, (e, a) => Assert.Equal(e.Id, a.Id))); } - [ConditionalTheory(Skip = "Issue#16318")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Correlated_collections_basic_projection_explicit_to_array(bool isAsync) { @@ -4399,7 +4399,7 @@ orderby w.Name descending elementAsserter: CollectionAsserter(elementAsserter: (e, a) => Assert.Equal(e.Id, a.Id))); } - [ConditionalTheory(Skip = "Issue#16318")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Correlated_collections_basic_projection_composite_key(bool isAsync) { @@ -4666,7 +4666,7 @@ from g in gs }); } - [ConditionalTheory(Skip = "Issue#16318")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Correlated_collections_similar_collection_projected_multiple_times(bool isAsync) { @@ -4690,7 +4690,7 @@ orderby g.Rank }); } - [ConditionalTheory(Skip = "Issue#16318")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Correlated_collections_different_collections_projected(bool isAsync) { @@ -6712,7 +6712,7 @@ public virtual Task Cast_subquery_to_base_type_using_typed_ToList(bool isAsync) elementAsserter: CollectionAsserter(e => e.Nickname, (e, a) => Assert.Equal(e.Nickname, a.Nickname))); } - [ConditionalTheory(Skip = "Issue#16318")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Cast_ordered_subquery_to_base_type_using_typed_ToArray(bool isAsync) { diff --git a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Select.cs b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Select.cs index 0ff6f16a979..37f3b1388a8 100644 --- a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Select.cs +++ b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.Select.cs @@ -55,7 +55,7 @@ public virtual Task Projection_when_null_value(bool isAsync) cs => cs.Select(c => c.Region)); } - [ConditionalTheory(Skip = "Issue#16318")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Projection_when_client_evald_subquery(bool isAsync) { diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index 521e9b27521..583029bd2d5 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -4390,19 +4390,11 @@ public override async Task Correlated_collections_naked_navigation_with_ToArray( await base.Correlated_collections_naked_navigation_with_ToArray(isAsync); AssertSql( - @"SELECT [g].[FullName] + @"SELECT [g].[Nickname], [g].[SquadId], [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Gears] AS [g] -WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND ([g].[Nickname] <> N'Marcus') -ORDER BY [g].[Nickname], [g].[SquadId], [g].[FullName]", - // - @"SELECT [g.Weapons].[Id], [g.Weapons].[AmmunitionType], [g.Weapons].[IsAutomatic], [g.Weapons].[Name], [g.Weapons].[OwnerFullName], [g.Weapons].[SynergyWithId], [t].[Nickname], [t].[SquadId], [t].[FullName] -FROM [Weapons] AS [g.Weapons] -INNER JOIN ( - SELECT [g0].[Nickname], [g0].[SquadId], [g0].[FullName] - FROM [Gears] AS [g0] - WHERE [g0].[Discriminator] IN (N'Officer', N'Gear') AND ([g0].[Nickname] <> N'Marcus') -) AS [t] ON [g.Weapons].[OwnerFullName] = [t].[FullName] -ORDER BY [t].[Nickname], [t].[SquadId], [t].[FullName]"); +LEFT JOIN [Weapons] AS [w] ON [g].[FullName] = [w].[OwnerFullName] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ([g].[Nickname] <> N'Marcus') +ORDER BY [g].[Nickname], [g].[SquadId], [w].[Id]"); } public override async Task Correlated_collections_basic_projection(bool isAsync) @@ -4442,20 +4434,15 @@ public override async Task Correlated_collections_basic_projection_explicit_to_a await base.Correlated_collections_basic_projection_explicit_to_array(isAsync); AssertSql( - @"SELECT [g].[FullName] + @"SELECT [g].[Nickname], [g].[SquadId], [t].[Id], [t].[AmmunitionType], [t].[IsAutomatic], [t].[Name], [t].[OwnerFullName], [t].[SynergyWithId] FROM [Gears] AS [g] -WHERE [g].[Discriminator] IN (N'Officer', N'Gear') AND ([g].[Nickname] <> N'Marcus') -ORDER BY [g].[Nickname], [g].[SquadId], [g].[FullName]", - // - @"SELECT [g.Weapons].[Id], [g.Weapons].[AmmunitionType], [g.Weapons].[IsAutomatic], [g.Weapons].[Name], [g.Weapons].[OwnerFullName], [g.Weapons].[SynergyWithId], [t].[Nickname], [t].[SquadId], [t].[FullName] -FROM [Weapons] AS [g.Weapons] -INNER JOIN ( - SELECT [g0].[Nickname], [g0].[SquadId], [g0].[FullName] - FROM [Gears] AS [g0] - WHERE [g0].[Discriminator] IN (N'Officer', N'Gear') AND ([g0].[Nickname] <> N'Marcus') -) AS [t] ON [g.Weapons].[OwnerFullName] = [t].[FullName] -WHERE ([g.Weapons].[IsAutomatic] = CAST(1 AS bit)) OR (([g.Weapons].[Name] <> N'foo') OR [g.Weapons].[Name] IS NULL) -ORDER BY [t].[Nickname], [t].[SquadId], [t].[FullName]"); +LEFT JOIN ( + SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] + FROM [Weapons] AS [w] + WHERE ([w].[IsAutomatic] = CAST(1 AS bit)) OR (([w].[Name] <> N'foo') OR [w].[Name] IS NULL) +) AS [t] ON [g].[FullName] = [t].[OwnerFullName] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ([g].[Nickname] <> N'Marcus') +ORDER BY [g].[Nickname], [g].[SquadId], [t].[Id]"); } public override async Task Correlated_collections_basic_projection_ordered(bool isAsync) @@ -4479,20 +4466,15 @@ public override async Task Correlated_collections_basic_projection_composite_key await base.Correlated_collections_basic_projection_composite_key(isAsync); AssertSql( - @"SELECT [o].[Nickname], [o].[SquadId] -FROM [Gears] AS [o] -WHERE ([o].[Discriminator] = N'Officer') AND ([o].[Nickname] <> N'Foo') -ORDER BY [o].[Nickname], [o].[SquadId]", - // - @"SELECT [t].[Nickname], [t].[SquadId], [o.Reports].[Nickname] AS [Nickname0], [o.Reports].[FullName], [o.Reports].[LeaderNickname], [o.Reports].[LeaderSquadId] -FROM [Gears] AS [o.Reports] -INNER JOIN ( - SELECT [o0].[Nickname], [o0].[SquadId] - FROM [Gears] AS [o0] - WHERE ([o0].[Discriminator] = N'Officer') AND ([o0].[Nickname] <> N'Foo') -) AS [t] ON ([o.Reports].[LeaderNickname] = [t].[Nickname]) AND ([o.Reports].[LeaderSquadId] = [t].[SquadId]) -WHERE [o.Reports].[Discriminator] IN (N'Officer', N'Gear') AND ([o.Reports].[HasSoulPatch] = CAST(0 AS bit)) -ORDER BY [t].[Nickname], [t].[SquadId]"); + @"SELECT [g].[Nickname], [g].[SquadId], [t].[Nickname], [t].[FullName], [t].[SquadId] +FROM [Gears] AS [g] +LEFT JOIN ( + SELECT [g0].[Nickname], [g0].[FullName], [g0].[SquadId], [g0].[LeaderNickname], [g0].[LeaderSquadId] + FROM [Gears] AS [g0] + WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') AND ([g0].[HasSoulPatch] <> CAST(1 AS bit)) +) AS [t] ON (([g].[Nickname] = [t].[LeaderNickname]) AND [t].[LeaderNickname] IS NOT NULL) AND ([g].[SquadId] = [t].[LeaderSquadId]) +WHERE ([g].[Discriminator] IN (N'Gear', N'Officer') AND ([g].[Discriminator] = N'Officer')) AND ([g].[Nickname] <> N'Foo') +ORDER BY [g].[Nickname], [g].[SquadId], [t].[Nickname], [t].[SquadId]"); } public override async Task Correlated_collections_basic_projecting_single_property(bool isAsync) @@ -4686,30 +4668,20 @@ public override async Task Correlated_collections_similar_collection_projected_m await base.Correlated_collections_similar_collection_projected_multiple_times(isAsync); AssertSql( - @"SELECT [g].[FullName] + @"SELECT [g].[FullName], [g].[Nickname], [g].[SquadId], [t].[Id], [t].[AmmunitionType], [t].[IsAutomatic], [t].[Name], [t].[OwnerFullName], [t].[SynergyWithId], [t0].[Id], [t0].[AmmunitionType], [t0].[IsAutomatic], [t0].[Name], [t0].[OwnerFullName], [t0].[SynergyWithId] FROM [Gears] AS [g] -WHERE [g].[Discriminator] IN (N'Officer', N'Gear') -ORDER BY [g].[Rank], [g].[Nickname], [g].[SquadId], [g].[FullName]", - // - @"SELECT [g.Weapons].[Id], [g.Weapons].[AmmunitionType], [g.Weapons].[IsAutomatic], [g.Weapons].[Name], [g.Weapons].[OwnerFullName], [g.Weapons].[SynergyWithId], [t].[Rank], [t].[Nickname], [t].[SquadId], [t].[FullName] -FROM [Weapons] AS [g.Weapons] -INNER JOIN ( - SELECT [g0].[Rank], [g0].[Nickname], [g0].[SquadId], [g0].[FullName] - FROM [Gears] AS [g0] - WHERE [g0].[Discriminator] IN (N'Officer', N'Gear') -) AS [t] ON [g.Weapons].[OwnerFullName] = [t].[FullName] -WHERE [g.Weapons].[IsAutomatic] = CAST(1 AS bit) -ORDER BY [t].[Rank], [t].[Nickname], [t].[SquadId], [t].[FullName], [g.Weapons].[OwnerFullName]", - // - @"SELECT [g.Weapons0].[Id], [g.Weapons0].[AmmunitionType], [g.Weapons0].[IsAutomatic], [g.Weapons0].[Name], [g.Weapons0].[OwnerFullName], [g.Weapons0].[SynergyWithId], [t0].[Rank], [t0].[Nickname], [t0].[SquadId], [t0].[FullName] -FROM [Weapons] AS [g.Weapons0] -INNER JOIN ( - SELECT [g1].[Rank], [g1].[Nickname], [g1].[SquadId], [g1].[FullName] - FROM [Gears] AS [g1] - WHERE [g1].[Discriminator] IN (N'Officer', N'Gear') -) AS [t0] ON [g.Weapons0].[OwnerFullName] = [t0].[FullName] -WHERE [g.Weapons0].[IsAutomatic] = CAST(0 AS bit) -ORDER BY [t0].[Rank], [t0].[Nickname], [t0].[SquadId], [t0].[FullName], [g.Weapons0].[IsAutomatic]"); +LEFT JOIN ( + SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] + FROM [Weapons] AS [w] + WHERE [w].[IsAutomatic] = CAST(1 AS bit) +) AS [t] ON [g].[FullName] = [t].[OwnerFullName] +LEFT JOIN ( + SELECT [w0].[Id], [w0].[AmmunitionType], [w0].[IsAutomatic], [w0].[Name], [w0].[OwnerFullName], [w0].[SynergyWithId] + FROM [Weapons] AS [w0] + WHERE [w0].[IsAutomatic] <> CAST(1 AS bit) +) AS [t0] ON [g].[FullName] = [t0].[OwnerFullName] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') +ORDER BY [g].[Rank], [g].[Nickname], [g].[SquadId], [t].[OwnerFullName], [t].[Id], [t0].[IsAutomatic], [t0].[Id]"); } public override async Task Correlated_collections_different_collections_projected(bool isAsync) @@ -4717,30 +4689,20 @@ public override async Task Correlated_collections_different_collections_projecte await base.Correlated_collections_different_collections_projected(isAsync); AssertSql( - @"SELECT [o].[Nickname], [o].[FullName], [o].[SquadId] -FROM [Gears] AS [o] -WHERE [o].[Discriminator] = N'Officer' -ORDER BY [o].[FullName], [o].[Nickname], [o].[SquadId]", - // - @"SELECT [t].[FullName], [t].[Nickname], [t].[SquadId], [o.Weapons].[Name], [o.Weapons].[IsAutomatic], [o.Weapons].[OwnerFullName] -FROM [Weapons] AS [o.Weapons] -INNER JOIN ( - SELECT [o0].[FullName], [o0].[Nickname], [o0].[SquadId] - FROM [Gears] AS [o0] - WHERE [o0].[Discriminator] = N'Officer' -) AS [t] ON [o.Weapons].[OwnerFullName] = [t].[FullName] -WHERE [o.Weapons].[IsAutomatic] = CAST(1 AS bit) -ORDER BY [t].[FullName], [t].[Nickname], [t].[SquadId]", - // - @"SELECT [t0].[FullName], [t0].[Nickname], [t0].[SquadId], [o.Reports].[Nickname] AS [Nickname0], [o.Reports].[Rank], [o.Reports].[LeaderNickname], [o.Reports].[LeaderSquadId] -FROM [Gears] AS [o.Reports] -INNER JOIN ( - SELECT [o1].[FullName], [o1].[Nickname], [o1].[SquadId] - FROM [Gears] AS [o1] - WHERE [o1].[Discriminator] = N'Officer' -) AS [t0] ON ([o.Reports].[LeaderNickname] = [t0].[Nickname]) AND ([o.Reports].[LeaderSquadId] = [t0].[SquadId]) -WHERE [o.Reports].[Discriminator] IN (N'Officer', N'Gear') -ORDER BY [t0].[FullName], [t0].[Nickname], [t0].[SquadId], [o.Reports].[FullName]"); + @"SELECT [g].[Nickname], [g].[SquadId], [t].[Name], [t].[IsAutomatic], [t].[Id], [t0].[Nickname], [t0].[Rank], [t0].[SquadId] +FROM [Gears] AS [g] +LEFT JOIN ( + SELECT [w].[Name], [w].[IsAutomatic], [w].[Id], [w].[OwnerFullName] + FROM [Weapons] AS [w] + WHERE [w].[IsAutomatic] = CAST(1 AS bit) +) AS [t] ON [g].[FullName] = [t].[OwnerFullName] +LEFT JOIN ( + SELECT [g0].[Nickname], [g0].[Rank], [g0].[SquadId], [g0].[FullName], [g0].[LeaderNickname], [g0].[LeaderSquadId] + FROM [Gears] AS [g0] + WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') +) AS [t0] ON (([g].[Nickname] = [t0].[LeaderNickname]) AND [t0].[LeaderNickname] IS NOT NULL) AND ([g].[SquadId] = [t0].[LeaderSquadId]) +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ([g].[Discriminator] = N'Officer') +ORDER BY [g].[FullName], [g].[Nickname], [g].[SquadId], [t].[Id], [t0].[FullName], [t0].[Nickname], [t0].[SquadId]"); } public override async Task Multiple_orderby_with_navigation_expansion_on_one_of_the_order_bys(bool isAsync) @@ -6693,20 +6655,15 @@ public override async Task Cast_ordered_subquery_to_base_type_using_typed_ToArra await base.Cast_ordered_subquery_to_base_type_using_typed_ToArray(isAsync); AssertSql( - @"SELECT [c].[Name] + @"SELECT [c].[Name], [t].[CityOrBirthName], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Nickname], [t].[Rank], [t].[SquadId] FROM [Cities] AS [c] +LEFT JOIN ( + SELECT [g].[CityOrBirthName], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Nickname], [g].[Rank], [g].[SquadId], [g].[AssignedCityName] + FROM [Gears] AS [g] + WHERE [g].[Discriminator] IN (N'Gear', N'Officer') +) AS [t] ON [c].[Name] = [t].[AssignedCityName] WHERE [c].[Name] = N'Ephyra' -ORDER BY [c].[Name]", - // - @"SELECT [t].[Name], [c.StationedGears].[CityOrBirthName], [c.StationedGears].[FullName], [c.StationedGears].[HasSoulPatch], [c.StationedGears].[LeaderNickname], [c.StationedGears].[LeaderSquadId], [c.StationedGears].[Nickname], [c.StationedGears].[Rank], [c.StationedGears].[SquadId], [c.StationedGears].[AssignedCityName] -FROM [Gears] AS [c.StationedGears] -INNER JOIN ( - SELECT [c0].[Name] - FROM [Cities] AS [c0] - WHERE [c0].[Name] = N'Ephyra' -) AS [t] ON [c.StationedGears].[AssignedCityName] = [t].[Name] -WHERE [c.StationedGears].[Discriminator] IN (N'Officer', N'Gear') -ORDER BY [t].[Name], [c.StationedGears].[Nickname] DESC"); +ORDER BY [c].[Name], [t].[Nickname] DESC, [t].[SquadId]"); } public override async Task Correlated_collection_with_complex_order_by_funcletized_to_constant_bool(bool isAsync) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs index f0c401ef85b..5d07d1d1b10 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -4741,7 +4741,7 @@ public MyContext12582(DbContextOptions options) #region Bug12748 - [ConditionalFact(Skip = "Issue#16318")] + [ConditionalFact] public virtual void Correlated_collection_correctly_associates_entities_with_byte_array_keys() { using (CreateDatabase12748()) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Select.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Select.cs index dc87617b8c6..a918cf74db7 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Select.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Select.cs @@ -88,17 +88,10 @@ public override async Task Projection_when_client_evald_subquery(bool isAsync) await base.Projection_when_client_evald_subquery(isAsync); AssertSql( - @"SELECT [c].[CustomerID] + @"SELECT [c].[CustomerID], [o].[CustomerID], [o].[OrderID] FROM [Customers] AS [c] -ORDER BY [c].[CustomerID]", - // - @"SELECT [t].[CustomerID], [c.Orders].[CustomerID] -FROM [Orders] AS [c.Orders] -INNER JOIN ( - SELECT [c0].[CustomerID] - FROM [Customers] AS [c0] -) AS [t] ON [c.Orders].[CustomerID] = [t].[CustomerID] -ORDER BY [t].[CustomerID]"); +LEFT JOIN [Orders] AS [o] ON [c].[CustomerID] = [o].[CustomerID] +ORDER BY [c].[CustomerID], [o].[OrderID]"); } public override async Task Project_to_object_array(bool isAsync)