-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Query: Enable parameter & sproc support for FromSql
Adds optimizer to optimize SelectExpression before printing based on ParameterValues TODO: Add a factory through service to avoid multiple services here. This also serves point for us to determine second level caching Lift FromSqlExpression when not composed over to avoid subquery which is necessary for stored procedures Add additional enumerables which add remapping map to materialize non composed SelectExpression which can have out of order projections Resolves#15750
- Loading branch information
Showing
24 changed files
with
815 additions
and
151 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
177 changes: 177 additions & 0 deletions
177
src/EFCore.Relational/Query/Pipeline/FromSqlNonComposedAsyncQueryingEnumerable.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
// 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.Data.Common; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.EntityFrameworkCore.Diagnostics; | ||
using Microsoft.EntityFrameworkCore.Query; | ||
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions; | ||
using Microsoft.EntityFrameworkCore.Storage; | ||
|
||
namespace Microsoft.EntityFrameworkCore.Relational.Query.Pipeline | ||
{ | ||
public partial class RelationalShapedQueryCompilingExpressionVisitor | ||
{ | ||
private class FromSqlNonComposedAsyncQueryingEnumerable<T> : IAsyncEnumerable<T> | ||
{ | ||
private readonly RelationalQueryContext _relationalQueryContext; | ||
private readonly SelectExpression _selectExpression; | ||
private readonly Func<QueryContext, DbDataReader, int[], Task<T>> _shaper; | ||
private readonly IQuerySqlGeneratorFactory2 _querySqlGeneratorFactory; | ||
private readonly Type _contextType; | ||
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _logger; | ||
private readonly ISqlExpressionFactory _sqlExpressionFactory; | ||
private readonly IParameterNameGeneratorFactory _parameterNameGeneratorFactory; | ||
|
||
public FromSqlNonComposedAsyncQueryingEnumerable( | ||
RelationalQueryContext relationalQueryContext, | ||
IQuerySqlGeneratorFactory2 querySqlGeneratorFactory, | ||
ISqlExpressionFactory sqlExpressionFactory, | ||
IParameterNameGeneratorFactory parameterNameGeneratorFactory, | ||
SelectExpression selectExpression, | ||
Func<QueryContext, DbDataReader, int[], Task<T>> shaper, | ||
Type contextType, | ||
IDiagnosticsLogger<DbLoggerCategory.Query> logger) | ||
{ | ||
_relationalQueryContext = relationalQueryContext; | ||
_querySqlGeneratorFactory = querySqlGeneratorFactory; | ||
_sqlExpressionFactory = sqlExpressionFactory; | ||
_parameterNameGeneratorFactory = parameterNameGeneratorFactory; | ||
_selectExpression = selectExpression; | ||
_shaper = shaper; | ||
_contextType = contextType; | ||
_logger = logger; | ||
} | ||
|
||
public IAsyncEnumerator<T> GetEnumerator() => new AsyncEnumerator(this); | ||
|
||
private sealed class AsyncEnumerator : IAsyncEnumerator<T> | ||
{ | ||
private RelationalDataReader _dataReader; | ||
private int[] _indexMap; | ||
private readonly RelationalQueryContext _relationalQueryContext; | ||
private readonly SelectExpression _selectExpression; | ||
private readonly Func<QueryContext, DbDataReader, int[], Task<T>> _shaper; | ||
private readonly IQuerySqlGeneratorFactory2 _querySqlGeneratorFactory; | ||
private readonly Type _contextType; | ||
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _logger; | ||
private readonly ISqlExpressionFactory _sqlExpressionFactory; | ||
private readonly IParameterNameGeneratorFactory _parameterNameGeneratorFactory; | ||
|
||
public AsyncEnumerator(FromSqlNonComposedAsyncQueryingEnumerable<T> queryingEnumerable) | ||
{ | ||
_relationalQueryContext = queryingEnumerable._relationalQueryContext; | ||
_shaper = queryingEnumerable._shaper; | ||
_selectExpression = queryingEnumerable._selectExpression; | ||
_querySqlGeneratorFactory = queryingEnumerable._querySqlGeneratorFactory; | ||
_contextType = queryingEnumerable._contextType; | ||
_logger = queryingEnumerable._logger; | ||
_sqlExpressionFactory = queryingEnumerable._sqlExpressionFactory; | ||
_parameterNameGeneratorFactory = queryingEnumerable._parameterNameGeneratorFactory; | ||
} | ||
|
||
public T Current { get; private set; } | ||
|
||
|
||
public void Dispose() | ||
{ | ||
_dataReader?.Dispose(); | ||
_dataReader = null; | ||
_relationalQueryContext.Connection.Close(); | ||
} | ||
|
||
public async Task<bool> MoveNext(CancellationToken cancellationToken) | ||
{ | ||
try | ||
{ | ||
if (_dataReader == null) | ||
{ | ||
_relationalQueryContext.Connection.Open(); | ||
|
||
try | ||
{ | ||
var projection = _selectExpression.Projection.ToList(); | ||
|
||
var selectExpression = new ParameterValueBasedSelectExpressionOptimizer( | ||
_sqlExpressionFactory, | ||
_parameterNameGeneratorFactory) | ||
.Optimize(_selectExpression, _relationalQueryContext.ParameterValues); | ||
|
||
var relationalCommand = _querySqlGeneratorFactory.Create().GetCommand(selectExpression); | ||
|
||
_dataReader | ||
= await relationalCommand.ExecuteReaderAsync( | ||
_relationalQueryContext.Connection, | ||
_relationalQueryContext.ParameterValues, | ||
_relationalQueryContext.CommandLogger, | ||
cancellationToken); | ||
|
||
var readerColumns = Enumerable.Range(0, _dataReader.DbDataReader.FieldCount) | ||
.Select( | ||
i => new | ||
{ | ||
Name = _dataReader.DbDataReader.GetName(i), | ||
Ordinal = i | ||
}).ToList(); | ||
|
||
_indexMap = new int[projection.Count]; | ||
|
||
for (var i = 0; i < projection.Count; i++) | ||
{ | ||
if (projection[i].Expression is ColumnExpression columnExpression) | ||
{ | ||
var columnName = columnExpression.Name; | ||
|
||
if (columnName != null) | ||
{ | ||
var readerColumn | ||
= readerColumns.SingleOrDefault( | ||
c => | ||
string.Equals(columnName, c.Name, StringComparison.OrdinalIgnoreCase)); | ||
|
||
if (readerColumn == null) | ||
{ | ||
throw new InvalidOperationException(RelationalStrings.FromSqlMissingColumn(columnName)); | ||
} | ||
|
||
_indexMap[i] = readerColumn.Ordinal; | ||
} | ||
} | ||
} | ||
} | ||
catch (Exception) | ||
{ | ||
// If failure happens creating the data reader, then it won't be available to | ||
// handle closing the connection, so do it explicitly here to preserve ref counting. | ||
_relationalQueryContext.Connection.Close(); | ||
|
||
throw; | ||
} | ||
} | ||
|
||
var hasNext = await _dataReader.ReadAsync(cancellationToken); | ||
|
||
Current | ||
= hasNext | ||
? await _shaper(_relationalQueryContext, _dataReader.DbDataReader, _indexMap) | ||
: default; | ||
|
||
return hasNext; | ||
} | ||
catch (Exception exception) | ||
{ | ||
_logger.QueryIterationFailed(_contextType, exception); | ||
|
||
throw; | ||
} | ||
} | ||
|
||
public void Reset() => throw new NotImplementedException(); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.