Skip to content

Commit

Permalink
Query: Introduce FromSqlQueryingEnumerable for non composed FromSql
Browse files Browse the repository at this point in the history
- Move ReaderColumns to RelationalCommandCache
  • Loading branch information
smitpatel committed Jun 10, 2020
1 parent 81f9ddb commit 17b24cb
Show file tree
Hide file tree
Showing 5 changed files with 438 additions and 139 deletions.
326 changes: 326 additions & 0 deletions src/EFCore.Relational/Query/Internal/FromSqlQueryingEnumerable.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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.
/// </summary>
public class FromSqlQueryingEnumerable<T> : IEnumerable<T>, IAsyncEnumerable<T>, IRelationalQueryingEnumerable
{
private readonly RelationalQueryContext _relationalQueryContext;
private readonly RelationalCommandCache _relationalCommandCache;
private readonly IReadOnlyList<string> _columnNames;
private readonly Func<QueryContext, DbDataReader, int[], T> _shaper;
private readonly Type _contextType;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _queryLogger;
private readonly bool _performIdentityResolution;

/// <summary>
/// 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.
/// </summary>
public FromSqlQueryingEnumerable(
[NotNull] RelationalQueryContext relationalQueryContext,
[NotNull] RelationalCommandCache relationalCommandCache,
[NotNull] IReadOnlyList<string> columnNames,
[NotNull] Func<QueryContext, DbDataReader, int[], T> shaper,
[NotNull] Type contextType,
bool performIdentityResolution)
{
_relationalQueryContext = relationalQueryContext;
_relationalCommandCache = relationalCommandCache;
_columnNames = columnNames;
_shaper = shaper;
_contextType = contextType;
_queryLogger = relationalQueryContext.QueryLogger;
_performIdentityResolution = performIdentityResolution;
}

/// <summary>
/// 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.
/// </summary>
public virtual IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
=> new AsyncEnumerator(this, cancellationToken);

/// <summary>
/// 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.
/// </summary>
public virtual IEnumerator<T> GetEnumerator() => new Enumerator(this);

/// <summary>
/// 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.
/// </summary>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

/// <summary>
/// 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.
/// </summary>
public virtual DbCommand CreateDbCommand()
=> _relationalCommandCache
.GetRelationalCommand(_relationalQueryContext.ParameterValues)
.CreateDbCommand(
new RelationalCommandParameterObject(
_relationalQueryContext.Connection,
_relationalQueryContext.ParameterValues,
null,
null,
null),
Guid.Empty,
(DbCommandMethod)(-1));

/// <summary>
/// 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.
/// </summary>
public virtual string ToQueryString()
=> _relationalQueryContext.RelationalQueryStringFactory.Create(CreateDbCommand());

/// <summary>
/// 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.
/// </summary>
public static int[] BuildIndexMap([CanBeNull] IReadOnlyList<string> 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<T>
{
private readonly RelationalQueryContext _relationalQueryContext;
private readonly RelationalCommandCache _relationalCommandCache;
private readonly IReadOnlyList<string> _columnNames;
private readonly Func<QueryContext, DbDataReader, int[], T> _shaper;
private readonly Type _contextType;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _queryLogger;
private readonly bool _performIdentityResolution;

private RelationalDataReader _dataReader;
private int[] _indexMap;
private IExecutionStrategy _executionStrategy;

public Enumerator(FromSqlQueryingEnumerable<T> 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<T>
{
private readonly RelationalQueryContext _relationalQueryContext;
private readonly RelationalCommandCache _relationalCommandCache;
private readonly IReadOnlyList<string> _columnNames;
private readonly Func<QueryContext, DbDataReader, int[], T> _shaper;
private readonly Type _contextType;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _queryLogger;
private readonly bool _performIdentityResolution;
private readonly CancellationToken _cancellationToken;

private RelationalDataReader _dataReader;
private int[] _indexMap;
private IExecutionStrategy _executionStrategy;

public AsyncEnumerator(
FromSqlQueryingEnumerable<T> 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<bool> 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<bool> 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;
}
}
}
}
Loading

0 comments on commit 17b24cb

Please sign in to comment.