Skip to content

Commit

Permalink
Query: Enable parameter & sproc support for FromSql
Browse files Browse the repository at this point in the history
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
smitpatel committed Jun 1, 2019
1 parent 6daa20b commit e311f44
Show file tree
Hide file tree
Showing 24 changed files with 815 additions and 151 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public static readonly IDictionary<Type, ServiceCharacteristics> RelationalServi
{ typeof(IRelationalTypeMappingSourcePlugin), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) },

// New Query Pipeline
{ typeof(IQuerySqlGeneratorFactory2), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IQuerySqlGeneratorFactory2), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IRelationalSqlTranslatingExpressionVisitorFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IMethodCallTranslatorProvider), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IMemberTranslatorProvider), new ServiceCharacteristics(ServiceLifetime.Singleton) },
Expand Down
26 changes: 17 additions & 9 deletions src/EFCore.Relational/Query/Pipeline/AsyncQueryingEnumerable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,30 @@ private class AsyncQueryingEnumerable<T> : IAsyncEnumerable<T>
private readonly IQuerySqlGeneratorFactory2 _querySqlGeneratorFactory;
private readonly Type _contextType;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _logger;
private readonly ISqlExpressionFactory _sqlExpressionFactory;
private readonly IParameterNameGeneratorFactory _parameterNameGeneratorFactory;

public AsyncQueryingEnumerable(
RelationalQueryContext relationalQueryContext,
IQuerySqlGeneratorFactory2 querySqlGeneratorFactory,
ISqlExpressionFactory sqlExpressionFactory,
IParameterNameGeneratorFactory parameterNameGeneratorFactory,
SelectExpression selectExpression,
Func<QueryContext, DbDataReader, ResultCoordinator, 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()
{
return new AsyncEnumerator(this);
}
public IAsyncEnumerator<T> GetEnumerator() => new AsyncEnumerator(this);

private sealed class AsyncEnumerator : IAsyncEnumerator<T>
{
Expand All @@ -55,6 +58,8 @@ private sealed class AsyncEnumerator : IAsyncEnumerator<T>
private readonly IQuerySqlGeneratorFactory2 _querySqlGeneratorFactory;
private readonly Type _contextType;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _logger;
private readonly ISqlExpressionFactory _sqlExpressionFactory;
private readonly IParameterNameGeneratorFactory _parameterNameGeneratorFactory;

public AsyncEnumerator(AsyncQueryingEnumerable<T> queryingEnumerable)
{
Expand All @@ -64,6 +69,8 @@ public AsyncEnumerator(AsyncQueryingEnumerable<T> queryingEnumerable)
_querySqlGeneratorFactory = queryingEnumerable._querySqlGeneratorFactory;
_contextType = queryingEnumerable._contextType;
_logger = queryingEnumerable._logger;
_sqlExpressionFactory = queryingEnumerable._sqlExpressionFactory;
_parameterNameGeneratorFactory = queryingEnumerable._parameterNameGeneratorFactory;
}

public T Current { get; private set; }
Expand All @@ -85,11 +92,12 @@ public async Task<bool> MoveNext(CancellationToken cancellationToken)

try
{
var relationalCommand = _querySqlGeneratorFactory.Create()
.GetCommand(
_selectExpression,
_relationalQueryContext.ParameterValues,
_relationalQueryContext.CommandLogger);
var selectExpression = new ParameterValueBasedSelectExpressionOptimizer(
_sqlExpressionFactory,
_parameterNameGeneratorFactory)
.Optimize(_selectExpression, _relationalQueryContext.ParameterValues);

var relationalCommand = _querySqlGeneratorFactory.Create().GetCommand(selectExpression);

_dataReader
= await relationalCommand.ExecuteReaderAsync(
Expand Down
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();
}
}
}
}
Loading

0 comments on commit e311f44

Please sign in to comment.