From 17b24cb5fbc57190f1f2b7420b444283ea597ba3 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Mon, 8 Jun 2020 16:03:57 -0700 Subject: [PATCH] Query: Introduce FromSqlQueryingEnumerable for non composed FromSql - Move ReaderColumns to RelationalCommandCache --- .../Internal/FromSqlQueryingEnumerable.cs | 326 ++++++++++++++++++ .../Query/Internal/QueryingEnumerable.cs | 77 +---- .../Query/Internal/RelationalCommandCache.cs | 14 +- ...erExpressionProcessingExpressionVisitor.cs | 37 +- ...alShapedQueryCompilingExpressionVisitor.cs | 123 ++++--- 5 files changed, 438 insertions(+), 139 deletions(-) create mode 100644 src/EFCore.Relational/Query/Internal/FromSqlQueryingEnumerable.cs diff --git a/src/EFCore.Relational/Query/Internal/FromSqlQueryingEnumerable.cs b/src/EFCore.Relational/Query/Internal/FromSqlQueryingEnumerable.cs new file mode 100644 index 00000000000..2e8baaee120 --- /dev/null +++ b/src/EFCore.Relational/Query/Internal/FromSqlQueryingEnumerable.cs @@ -0,0 +1,326 @@ +// 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.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Storage; + +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 FromSqlQueryingEnumerable : IEnumerable, IAsyncEnumerable, IRelationalQueryingEnumerable + { + private readonly RelationalQueryContext _relationalQueryContext; + private readonly RelationalCommandCache _relationalCommandCache; + private readonly IReadOnlyList _columnNames; + private readonly Func _shaper; + private readonly Type _contextType; + private readonly IDiagnosticsLogger _queryLogger; + private readonly bool _performIdentityResolution; + + /// + /// 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 FromSqlQueryingEnumerable( + [NotNull] RelationalQueryContext relationalQueryContext, + [NotNull] RelationalCommandCache relationalCommandCache, + [NotNull] IReadOnlyList columnNames, + [NotNull] Func shaper, + [NotNull] Type contextType, + bool performIdentityResolution) + { + _relationalQueryContext = relationalQueryContext; + _relationalCommandCache = relationalCommandCache; + _columnNames = columnNames; + _shaper = shaper; + _contextType = contextType; + _queryLogger = relationalQueryContext.QueryLogger; + _performIdentityResolution = performIdentityResolution; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + => new AsyncEnumerator(this, cancellationToken); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerator GetEnumerator() => new Enumerator(this); + + /// + /// 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. + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual DbCommand CreateDbCommand() + => _relationalCommandCache + .GetRelationalCommand(_relationalQueryContext.ParameterValues) + .CreateDbCommand( + new RelationalCommandParameterObject( + _relationalQueryContext.Connection, + _relationalQueryContext.ParameterValues, + null, + null, + null), + Guid.Empty, + (DbCommandMethod)(-1)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string ToQueryString() + => _relationalQueryContext.RelationalQueryStringFactory.Create(CreateDbCommand()); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static int[] BuildIndexMap([CanBeNull] IReadOnlyList columnNames, [NotNull] DbDataReader dataReader) + { + var readerColumns = Enumerable.Range(0, dataReader.FieldCount) + .ToDictionary(dataReader.GetName, i => i, StringComparer.OrdinalIgnoreCase); + + var indexMap = new int[columnNames.Count]; + for (var i = 0; i < columnNames.Count; i++) + { + var columnName = columnNames[i]; + if (!readerColumns.TryGetValue(columnName, out var ordinal)) + { + throw new InvalidOperationException(RelationalStrings.FromSqlMissingColumn(columnName)); + } + + indexMap[i] = ordinal; + } + + return indexMap; + } + + private sealed class Enumerator : IEnumerator + { + private readonly RelationalQueryContext _relationalQueryContext; + private readonly RelationalCommandCache _relationalCommandCache; + private readonly IReadOnlyList _columnNames; + private readonly Func _shaper; + private readonly Type _contextType; + private readonly IDiagnosticsLogger _queryLogger; + private readonly bool _performIdentityResolution; + + private RelationalDataReader _dataReader; + private int[] _indexMap; + private IExecutionStrategy _executionStrategy; + + public Enumerator(FromSqlQueryingEnumerable queryingEnumerable) + { + _relationalQueryContext = queryingEnumerable._relationalQueryContext; + _relationalCommandCache = queryingEnumerable._relationalCommandCache; + _columnNames = queryingEnumerable._columnNames; + _shaper = queryingEnumerable._shaper; + _contextType = queryingEnumerable._contextType; + _queryLogger = queryingEnumerable._queryLogger; + _performIdentityResolution = queryingEnumerable._performIdentityResolution; + } + + public T Current { get; private set; } + + object IEnumerator.Current => Current; + + public bool MoveNext() + { + try + { + using (_relationalQueryContext.ConcurrencyDetector.EnterCriticalSection()) + { + if (_dataReader == null) + { + if (_executionStrategy == null) + { + _executionStrategy = _relationalQueryContext.ExecutionStrategyFactory.Create(); + } + + _executionStrategy.Execute(true, InitializeReader, null); + } + + var hasNext = _dataReader.Read(); + + Current = hasNext + ? _shaper(_relationalQueryContext, _dataReader.DbDataReader, _indexMap) + : default; + + return hasNext; + } + } + catch (Exception exception) + { + _queryLogger.QueryIterationFailed(_contextType, exception); + + throw; + } + } + + private bool InitializeReader(DbContext _, bool result) + { + var relationalCommand = _relationalCommandCache.GetRelationalCommand(_relationalQueryContext.ParameterValues); + + _dataReader + = relationalCommand.ExecuteReader( + new RelationalCommandParameterObject( + _relationalQueryContext.Connection, + _relationalQueryContext.ParameterValues, + _relationalCommandCache.ReaderColumns, + _relationalQueryContext.Context, + _relationalQueryContext.CommandLogger)); + + _indexMap = BuildIndexMap(_columnNames, _dataReader.DbDataReader); + + _relationalQueryContext.InitializeStateManager(_performIdentityResolution); + + return result; + } + + public void Dispose() + { + _dataReader?.Dispose(); + _dataReader = null; + } + + public void Reset() => throw new NotImplementedException(); + } + + private sealed class AsyncEnumerator : IAsyncEnumerator + { + private readonly RelationalQueryContext _relationalQueryContext; + private readonly RelationalCommandCache _relationalCommandCache; + private readonly IReadOnlyList _columnNames; + private readonly Func _shaper; + private readonly Type _contextType; + private readonly IDiagnosticsLogger _queryLogger; + private readonly bool _performIdentityResolution; + private readonly CancellationToken _cancellationToken; + + private RelationalDataReader _dataReader; + private int[] _indexMap; + private IExecutionStrategy _executionStrategy; + + public AsyncEnumerator( + FromSqlQueryingEnumerable queryingEnumerable, + CancellationToken cancellationToken) + { + _relationalQueryContext = queryingEnumerable._relationalQueryContext; + _relationalCommandCache = queryingEnumerable._relationalCommandCache; + _columnNames = queryingEnumerable._columnNames; + _shaper = queryingEnumerable._shaper; + _contextType = queryingEnumerable._contextType; + _queryLogger = queryingEnumerable._queryLogger; + _performIdentityResolution = queryingEnumerable._performIdentityResolution; + _cancellationToken = cancellationToken; + } + + public T Current { get; private set; } + + public async ValueTask MoveNextAsync() + { + try + { + using (_relationalQueryContext.ConcurrencyDetector.EnterCriticalSection()) + { + if (_dataReader == null) + { + if (_executionStrategy == null) + { + _executionStrategy = _relationalQueryContext.ExecutionStrategyFactory.Create(); + } + + await _executionStrategy.ExecuteAsync(true, InitializeReaderAsync, null, _cancellationToken).ConfigureAwait(false); + } + + var hasNext = await _dataReader.ReadAsync(_cancellationToken).ConfigureAwait(false); + + Current = hasNext + ? _shaper(_relationalQueryContext, _dataReader.DbDataReader, _indexMap) + : default; + + return hasNext; + } + } + catch (Exception exception) + { + _queryLogger.QueryIterationFailed(_contextType, exception); + + throw; + } + } + + private async Task InitializeReaderAsync(DbContext _, bool result, CancellationToken cancellationToken) + { + var relationalCommand = _relationalCommandCache.GetRelationalCommand(_relationalQueryContext.ParameterValues); + + _dataReader + = await relationalCommand.ExecuteReaderAsync( + new RelationalCommandParameterObject( + _relationalQueryContext.Connection, + _relationalQueryContext.ParameterValues, + _relationalCommandCache.ReaderColumns, + _relationalQueryContext.Context, + _relationalQueryContext.CommandLogger), + cancellationToken) + .ConfigureAwait(false); + + _indexMap = BuildIndexMap(_columnNames, _dataReader.DbDataReader); + + _relationalQueryContext.InitializeStateManager(_performIdentityResolution); + + return result; + } + + public ValueTask DisposeAsync() + { + if (_dataReader != null) + { + var dataReader = _dataReader; + _dataReader = null; + + return dataReader.DisposeAsync(); + } + + return default; + } + } + } +} diff --git a/src/EFCore.Relational/Query/Internal/QueryingEnumerable.cs b/src/EFCore.Relational/Query/Internal/QueryingEnumerable.cs index ea8fef6d89d..b468b55d83f 100644 --- a/src/EFCore.Relational/Query/Internal/QueryingEnumerable.cs +++ b/src/EFCore.Relational/Query/Internal/QueryingEnumerable.cs @@ -5,7 +5,6 @@ using System.Collections; using System.Collections.Generic; using System.Data.Common; -using System.Linq; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -24,9 +23,7 @@ public class QueryingEnumerable : IEnumerable, IAsyncEnumerable, IRelat { private readonly RelationalQueryContext _relationalQueryContext; private readonly RelationalCommandCache _relationalCommandCache; - private readonly IReadOnlyList _columnNames; - private readonly IReadOnlyList _readerColumns; - private readonly Func _shaper; + private readonly Func _shaper; private readonly Type _contextType; private readonly IDiagnosticsLogger _queryLogger; private readonly bool _performIdentityResolution; @@ -40,16 +37,12 @@ public class QueryingEnumerable : IEnumerable, IAsyncEnumerable, IRelat public QueryingEnumerable( [NotNull] RelationalQueryContext relationalQueryContext, [NotNull] RelationalCommandCache relationalCommandCache, - [NotNull] IReadOnlyList columnNames, - [NotNull] IReadOnlyList readerColumns, - [NotNull] Func shaper, + [NotNull] Func shaper, [NotNull] Type contextType, bool performIdentityResolution) { _relationalQueryContext = relationalQueryContext; _relationalCommandCache = relationalCommandCache; - _columnNames = columnNames; - _readerColumns = readerColumns; _shaper = shaper; _contextType = contextType; _queryLogger = relationalQueryContext.QueryLogger; @@ -109,51 +102,16 @@ public virtual DbCommand CreateDbCommand() public virtual string ToQueryString() => _relationalQueryContext.RelationalQueryStringFactory.Create(CreateDbCommand()); - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public static int[] BuildIndexMap([CanBeNull] IReadOnlyList columnNames, [NotNull] DbDataReader dataReader) - { - if (columnNames == null) - { - return null; - } - - // Non-Composed FromSql - var readerColumns = Enumerable.Range(0, dataReader.FieldCount) - .ToDictionary(dataReader.GetName, i => i, StringComparer.OrdinalIgnoreCase); - - var indexMap = new int[columnNames.Count]; - for (var i = 0; i < columnNames.Count; i++) - { - var columnName = columnNames[i]; - if (!readerColumns.TryGetValue(columnName, out var ordinal)) - { - throw new InvalidOperationException(RelationalStrings.FromSqlMissingColumn(columnName)); - } - - indexMap[i] = ordinal; - } - - return indexMap; - } - private sealed class Enumerator : IEnumerator { private readonly RelationalQueryContext _relationalQueryContext; private readonly RelationalCommandCache _relationalCommandCache; - private readonly IReadOnlyList _columnNames; - private readonly IReadOnlyList _readerColumns; - private readonly Func _shaper; + private readonly Func _shaper; private readonly Type _contextType; private readonly IDiagnosticsLogger _queryLogger; private readonly bool _performIdentityResolution; private RelationalDataReader _dataReader; - private int[] _indexMap; private ResultCoordinator _resultCoordinator; private IExecutionStrategy _executionStrategy; @@ -161,8 +119,6 @@ public Enumerator(QueryingEnumerable queryingEnumerable) { _relationalQueryContext = queryingEnumerable._relationalQueryContext; _relationalCommandCache = queryingEnumerable._relationalCommandCache; - _columnNames = queryingEnumerable._columnNames; - _readerColumns = queryingEnumerable._readerColumns; _shaper = queryingEnumerable._shaper; _contextType = queryingEnumerable._contextType; _queryLogger = queryingEnumerable._queryLogger; @@ -199,8 +155,7 @@ public bool MoveNext() _resultCoordinator.ResultReady = true; _resultCoordinator.HasNext = null; Current = _shaper( - _relationalQueryContext, _dataReader.DbDataReader, - _resultCoordinator.ResultContext, _indexMap, _resultCoordinator); + _relationalQueryContext, _dataReader.DbDataReader, _resultCoordinator.ResultContext, _resultCoordinator); if (_resultCoordinator.ResultReady) { // We generated a result so null out previously stored values @@ -214,8 +169,7 @@ public bool MoveNext() // Enumeration has ended, materialize last element _resultCoordinator.ResultReady = true; Current = _shaper( - _relationalQueryContext, _dataReader.DbDataReader, - _resultCoordinator.ResultContext, _indexMap, _resultCoordinator); + _relationalQueryContext, _dataReader.DbDataReader, _resultCoordinator.ResultContext, _resultCoordinator); break; } @@ -242,12 +196,10 @@ private bool InitializeReader(DbContext _, bool result) new RelationalCommandParameterObject( _relationalQueryContext.Connection, _relationalQueryContext.ParameterValues, - _readerColumns, + _relationalCommandCache.ReaderColumns, _relationalQueryContext.Context, _relationalQueryContext.CommandLogger)); - _indexMap = BuildIndexMap(_columnNames, _dataReader.DbDataReader); - _resultCoordinator = new ResultCoordinator(); _relationalQueryContext.InitializeStateManager(_performIdentityResolution); @@ -268,16 +220,13 @@ private sealed class AsyncEnumerator : IAsyncEnumerator { private readonly RelationalQueryContext _relationalQueryContext; private readonly RelationalCommandCache _relationalCommandCache; - private readonly IReadOnlyList _columnNames; - private readonly IReadOnlyList _readerColumns; - private readonly Func _shaper; + private readonly Func _shaper; private readonly Type _contextType; private readonly IDiagnosticsLogger _queryLogger; private readonly bool _performIdentityResolution; private readonly CancellationToken _cancellationToken; private RelationalDataReader _dataReader; - private int[] _indexMap; private ResultCoordinator _resultCoordinator; private IExecutionStrategy _executionStrategy; @@ -287,8 +236,6 @@ public AsyncEnumerator( { _relationalQueryContext = queryingEnumerable._relationalQueryContext; _relationalCommandCache = queryingEnumerable._relationalCommandCache; - _columnNames = queryingEnumerable._columnNames; - _readerColumns = queryingEnumerable._readerColumns; _shaper = queryingEnumerable._shaper; _contextType = queryingEnumerable._contextType; _queryLogger = queryingEnumerable._queryLogger; @@ -325,8 +272,7 @@ await _executionStrategy.ExecuteAsync(true, InitializeReaderAsync, null, _cancel _resultCoordinator.ResultReady = true; _resultCoordinator.HasNext = null; Current = _shaper( - _relationalQueryContext, _dataReader.DbDataReader, - _resultCoordinator.ResultContext, _indexMap, _resultCoordinator); + _relationalQueryContext, _dataReader.DbDataReader, _resultCoordinator.ResultContext, _resultCoordinator); if (_resultCoordinator.ResultReady) { // We generated a result so null out previously stored values @@ -340,8 +286,7 @@ await _executionStrategy.ExecuteAsync(true, InitializeReaderAsync, null, _cancel // Enumeration has ended, materialize last element _resultCoordinator.ResultReady = true; Current = _shaper( - _relationalQueryContext, _dataReader.DbDataReader, - _resultCoordinator.ResultContext, _indexMap, _resultCoordinator); + _relationalQueryContext, _dataReader.DbDataReader, _resultCoordinator.ResultContext, _resultCoordinator); break; } @@ -368,14 +313,12 @@ private async Task InitializeReaderAsync(DbContext _, bool result, Cancell new RelationalCommandParameterObject( _relationalQueryContext.Connection, _relationalQueryContext.ParameterValues, - _readerColumns, + _relationalCommandCache.ReaderColumns, _relationalQueryContext.Context, _relationalQueryContext.CommandLogger), cancellationToken) .ConfigureAwait(false); - _indexMap = BuildIndexMap(_columnNames, _dataReader.DbDataReader); - _resultCoordinator = new ResultCoordinator(); _relationalQueryContext.InitializeStateManager(_performIdentityResolution); diff --git a/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs b/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs index 1b0012567db..5d97ac40683 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs @@ -37,15 +37,25 @@ public RelationalCommandCache( [NotNull] IMemoryCache memoryCache, [NotNull] IQuerySqlGeneratorFactory querySqlGeneratorFactory, [NotNull] IRelationalParameterBasedSqlProcessorFactory relationalParameterBasedSqlProcessorFactory, - bool useRelationalNulls, - [NotNull] SelectExpression selectExpression) + [NotNull] SelectExpression selectExpression, + [NotNull] IReadOnlyList readerColumns, + bool useRelationalNulls) { _memoryCache = memoryCache; _querySqlGeneratorFactory = querySqlGeneratorFactory; _selectExpression = selectExpression; + ReaderColumns = readerColumns; _relationalParameterBasedSqlProcessor = relationalParameterBasedSqlProcessorFactory.Create(useRelationalNulls); } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IReadOnlyList ReaderColumns { get; } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperExpressionProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperExpressionProcessingExpressionVisitor.cs index d6234eb7ea5..54d425e25c5 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperExpressionProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperExpressionProcessingExpressionVisitor.cs @@ -24,7 +24,6 @@ private static readonly MemberInfo _resultCoordinatorResultReadyMemberInfo private readonly ParameterExpression _dataReaderParameter; private readonly ParameterExpression _resultContextParameter; private readonly ParameterExpression _resultCoordinatorParameter; - private readonly ParameterExpression _indexMapParameter; private readonly IDictionary _mapping = new Dictionary(); @@ -45,14 +44,12 @@ private static readonly MemberInfo _resultCoordinatorResultReadyMemberInfo public ShaperExpressionProcessingExpressionVisitor( SelectExpression selectExpression, ParameterExpression dataReaderParameter, - ParameterExpression resultCoordinatorParameter, - ParameterExpression indexMapParameter) + ParameterExpression resultCoordinatorParameter) { _selectExpression = selectExpression; _dataReaderParameter = dataReaderParameter; _resultContextParameter = Expression.Parameter(typeof(ResultContext), "resultContext"); _resultCoordinatorParameter = resultCoordinatorParameter; - _indexMapParameter = indexMapParameter; } private sealed class CollectionShaperFindingExpressionVisitor : ExpressionVisitor @@ -115,12 +112,12 @@ public Expression Inject(Expression expression) _variables, _expressions); - var conditionalMaterializationExpressions = new List(); - - conditionalMaterializationExpressions.Add( + var conditionalMaterializationExpressions = new List + { Expression.IfThen( Expression.Equal(_valuesArrayExpression, Expression.Constant(null, typeof(object[]))), - initializationBlock)); + initializationBlock) + }; conditionalMaterializationExpressions.AddRange(_collectionPopulatingExpressions); @@ -145,20 +142,12 @@ public Expression Inject(Expression expression) } private LambdaExpression ConvertToLambda(Expression result) - => _indexMapParameter != null - ? Expression.Lambda( - result, - QueryCompilationContext.QueryContextParameter, - _dataReaderParameter, - _resultContextParameter, - _indexMapParameter, - _resultCoordinatorParameter) - : Expression.Lambda( - result, - QueryCompilationContext.QueryContextParameter, - _dataReaderParameter, - _resultContextParameter, - _resultCoordinatorParameter); + => Expression.Lambda( + result, + QueryCompilationContext.QueryContextParameter, + _dataReaderParameter, + _resultContextParameter, + _resultCoordinatorParameter); protected override Expression VisitExtension(Expression extensionExpression) { @@ -239,7 +228,7 @@ protected override Expression VisitExtension(Expression extensionExpression) relationalCollectionShaperExpression) { var innerShaper = new ShaperExpressionProcessingExpressionVisitor( - _selectExpression, _dataReaderParameter, _resultCoordinatorParameter, null) + _selectExpression, _dataReaderParameter, _resultCoordinatorParameter) .Inject(relationalCollectionShaperExpression.InnerShaper); _collectionPopulatingExpressions.Add( @@ -278,7 +267,7 @@ protected override Expression VisitExtension(Expression extensionExpression) if (!_mapping.TryGetValue(key, out var accessor)) { var innerShaper = new ShaperExpressionProcessingExpressionVisitor( - _selectExpression, _dataReaderParameter, _resultCoordinatorParameter, null) + _selectExpression, _dataReaderParameter, _resultCoordinatorParameter) .Inject(relationalCollectionShaperExpression.InnerShaper); _collectionPopulatingExpressions.Add( diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs index c59abe53d50..fb9d1695338 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs @@ -59,55 +59,86 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery var selectExpression = (SelectExpression)shapedQueryExpression.QueryExpression; selectExpression.ApplyTags(_tags); - var dataReaderParameter = Expression.Parameter(typeof(DbDataReader), "dataReader"); - var resultCoordinatorParameter = Expression.Parameter(typeof(ResultCoordinator), "resultCoordinator"); - var indexMapParameter = Expression.Parameter(typeof(int[]), "indexMap"); - - var shaper = new ShaperExpressionProcessingExpressionVisitor( - selectExpression, - dataReaderParameter, - resultCoordinatorParameter, - indexMapParameter) - .Inject(shapedQueryExpression.ShaperExpression); - - shaper = InjectEntityMaterializers(shaper); - - var isNonComposedFromSql = selectExpression.IsNonComposedFromSql(); - shaper = new RelationalProjectionBindingRemovingExpressionVisitor( + if (selectExpression.IsNonComposedFromSql()) + { + var dataReaderParameter = Expression.Parameter(typeof(DbDataReader), "dataReader"); + var indexMapParameter = Expression.Parameter(typeof(int[]), "indexMap"); + + Expression shaper = Expression.Lambda( + shapedQueryExpression.ShaperExpression, + QueryCompilationContext.QueryContextParameter, + dataReaderParameter, + indexMapParameter); + shaper = InjectEntityMaterializers(shaper); + + shaper = new RelationalProjectionBindingRemovingExpressionVisitor( + selectExpression, + dataReaderParameter, + indexMapParameter, + _detailedErrorsEnabled, + QueryCompilationContext.IsBuffering) + .Visit(shaper, out var projectionColumns); + + var relationalCommandCache = new RelationalCommandCache( + Dependencies.MemoryCache, + RelationalDependencies.QuerySqlGeneratorFactory, + RelationalDependencies.RelationalParameterBasedSqlProcessorFactory, selectExpression, - dataReaderParameter, - isNonComposedFromSql ? indexMapParameter : null, - _detailedErrorsEnabled, - QueryCompilationContext.IsBuffering) - .Visit(shaper, out var projectionColumns); - - shaper = new CustomShaperCompilingExpressionVisitor(dataReaderParameter, resultCoordinatorParameter, QueryCompilationContext.IsTracking) - .Visit(shaper); - - IReadOnlyList columnNames = null; - if (isNonComposedFromSql) + projectionColumns, + _useRelationalNulls); + + var shaperLambda = (LambdaExpression)shaper; + var columnNames = selectExpression.Projection.Select(pe => ((ColumnExpression)pe.Expression).Name).ToList(); + + return Expression.New( + typeof(FromSqlQueryingEnumerable<>).MakeGenericType(shaperLambda.ReturnType).GetConstructors()[0], + Expression.Convert(QueryCompilationContext.QueryContextParameter, typeof(RelationalQueryContext)), + Expression.Constant(relationalCommandCache), + Expression.Constant(columnNames, typeof(IReadOnlyList)), + Expression.Constant(shaperLambda.Compile()), + Expression.Constant(_contextType), + Expression.Constant(QueryCompilationContext.PerformIdentityResolution)); + } + else { - columnNames = selectExpression.Projection.Select(pe => ((ColumnExpression)pe.Expression).Name).ToList(); + var dataReaderParameter = Expression.Parameter(typeof(DbDataReader), "dataReader"); + var resultCoordinatorParameter = Expression.Parameter(typeof(ResultCoordinator), "resultCoordinator"); + + var shaper = new ShaperExpressionProcessingExpressionVisitor( + selectExpression, + dataReaderParameter, + resultCoordinatorParameter) + .Inject(shapedQueryExpression.ShaperExpression); + shaper = InjectEntityMaterializers(shaper); + + shaper = new RelationalProjectionBindingRemovingExpressionVisitor( + selectExpression, + dataReaderParameter, + indexMapParameter: null, + _detailedErrorsEnabled, + QueryCompilationContext.IsBuffering) + .Visit(shaper, out var projectionColumns); + shaper = new CustomShaperCompilingExpressionVisitor(dataReaderParameter, resultCoordinatorParameter, QueryCompilationContext.IsTracking) + .Visit(shaper); + + var relationalCommandCache = new RelationalCommandCache( + Dependencies.MemoryCache, + RelationalDependencies.QuerySqlGeneratorFactory, + RelationalDependencies.RelationalParameterBasedSqlProcessorFactory, + selectExpression, + projectionColumns, + _useRelationalNulls); + + var shaperLambda = (LambdaExpression)shaper; + + return Expression.New( + typeof(QueryingEnumerable<>).MakeGenericType(shaperLambda.ReturnType).GetConstructors()[0], + Expression.Convert(QueryCompilationContext.QueryContextParameter, typeof(RelationalQueryContext)), + Expression.Constant(relationalCommandCache), + Expression.Constant(shaperLambda.Compile()), + Expression.Constant(_contextType), + Expression.Constant(QueryCompilationContext.PerformIdentityResolution)); } - - var relationalCommandCache = new RelationalCommandCache( - Dependencies.MemoryCache, - RelationalDependencies.QuerySqlGeneratorFactory, - RelationalDependencies.RelationalParameterBasedSqlProcessorFactory, - _useRelationalNulls, - selectExpression); - - var shaperLambda = (LambdaExpression)shaper; - - return Expression.New( - typeof(QueryingEnumerable<>).MakeGenericType(shaperLambda.ReturnType).GetConstructors()[0], - Expression.Convert(QueryCompilationContext.QueryContextParameter, typeof(RelationalQueryContext)), - Expression.Constant(relationalCommandCache), - Expression.Constant(columnNames, typeof(IReadOnlyList)), - Expression.Constant(projectionColumns, typeof(IReadOnlyList)), - Expression.Constant(shaperLambda.Compile()), - Expression.Constant(_contextType), - Expression.Constant(QueryCompilationContext.PerformIdentityResolution)); } } }