Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query: Enable parameter & sproc support for FromSql #15890

Merged
merged 1 commit into from
Jun 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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