Skip to content

Commit

Permalink
Refactor RelationalShapedQueryCompilingExpressionVisitor
Browse files Browse the repository at this point in the history
  • Loading branch information
AndriySvyryd committed Oct 30, 2019
1 parent d28b941 commit b21099b
Show file tree
Hide file tree
Showing 18 changed files with 1,005 additions and 996 deletions.
1 change: 1 addition & 0 deletions All.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ Licensed under the Apache License, Version 2.0. See License.txt in the project r
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EXml_002ECodeStyle_002EFormatSettingsUpgrade_002EXmlMoveToCommonFormatterSettingsUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=annotatable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=fallbacks/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=initializers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=keyless/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=materializer/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=materializers/@EntryIndexedValue">True</s:Boolean>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// 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.

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 CollectionMaterializationContext
{
public CollectionMaterializationContext(object parent, object collection, object[] parentIdentifier, object[] outerIdentifier)
{
Parent = parent;
Collection = collection;
ParentIdentifier = parentIdentifier;
OuterIdentifier = outerIdentifier;
ResultContext = new ResultContext();
}

public virtual ResultContext ResultContext { get; }
public virtual object Parent { get; }
public virtual object Collection { get; }
public virtual object[] ParentIdentifier { get; }
public virtual object[] OuterIdentifier { get; }
public virtual object[] SelfIdentifier { get; private set; }

public virtual void UpdateSelfIdentifier(object[] selfIdentifier)
{
SelfIdentifier = selfIdentifier;
}
}
}

Large diffs are not rendered by default.

311 changes: 311 additions & 0 deletions src/EFCore.Relational/Query/Internal/QueryingEnumerable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
// 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 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 QueryingEnumerable<T> : IEnumerable<T>, IAsyncEnumerable<T>
{
private readonly RelationalQueryContext _relationalQueryContext;
private readonly RelationalCommandCache _relationalCommandCache;
private readonly IReadOnlyList<string> _columnNames;
private readonly Func<QueryContext, DbDataReader, ResultContext, int[], ResultCoordinator, T> _shaper;
private readonly Type _contextType;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _logger;

public QueryingEnumerable(
RelationalQueryContext relationalQueryContext,
RelationalCommandCache relationalCommandCache,
IReadOnlyList<string> columnNames,
Func<QueryContext, DbDataReader, ResultContext, int[], ResultCoordinator, T> shaper,
Type contextType,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
_relationalQueryContext = relationalQueryContext;
_relationalCommandCache = relationalCommandCache;
_columnNames = columnNames;
_shaper = shaper;
_contextType = contextType;
_logger = logger;
}

public virtual IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
=> new AsyncEnumerator(this, cancellationToken);

public virtual IEnumerator<T> GetEnumerator() => new Enumerator(this);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

private sealed class Enumerator : IEnumerator<T>
{
private readonly RelationalQueryContext _relationalQueryContext;
private readonly RelationalCommandCache _relationalCommandCache;
private readonly IReadOnlyList<string> _columnNames;
private readonly Func<QueryContext, DbDataReader, ResultContext, int[], ResultCoordinator, T> _shaper;
private readonly Type _contextType;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _logger;

private RelationalDataReader _dataReader;
private int[] _indexMap;
private ResultCoordinator _resultCoordinator;

public Enumerator(QueryingEnumerable<T> queryingEnumerable)
{
_relationalQueryContext = queryingEnumerable._relationalQueryContext;
_relationalCommandCache = queryingEnumerable._relationalCommandCache;
_columnNames = queryingEnumerable._columnNames;
_shaper = queryingEnumerable._shaper;
_contextType = queryingEnumerable._contextType;
_logger = queryingEnumerable._logger;
}

public T Current { get; private set; }

object IEnumerator.Current => Current;

public bool MoveNext()
{
try
{
using (_relationalQueryContext.ConcurrencyDetector.EnterCriticalSection())
{
if (_dataReader == null)
{
var relationalCommand = _relationalCommandCache.GetRelationalCommand(
_relationalQueryContext.ParameterValues);

_dataReader
= relationalCommand.ExecuteReader(
new RelationalCommandParameterObject(
_relationalQueryContext.Connection,
_relationalQueryContext.ParameterValues,
_relationalQueryContext.Context,
_relationalQueryContext.CommandLogger));

// Non-Composed FromSql
if (_columnNames != null)
{
var readerColumns = Enumerable.Range(0, _dataReader.DbDataReader.FieldCount)
.ToDictionary(i => _dataReader.DbDataReader.GetName(i), i => i, StringComparer.OrdinalIgnoreCase);

_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;
}
}
else
{
_indexMap = null;
}

_resultCoordinator = new ResultCoordinator();
}

var hasNext = _resultCoordinator.HasNext ?? _dataReader.Read();
Current = default;

if (hasNext)
{
while (true)
{
_resultCoordinator.ResultReady = true;
_resultCoordinator.HasNext = null;
Current = _shaper(
_relationalQueryContext, _dataReader.DbDataReader,
_resultCoordinator.ResultContext, _indexMap, _resultCoordinator);
if (_resultCoordinator.ResultReady)
{
// We generated a result so null out previously stored values
_resultCoordinator.ResultContext.Values = null;
break;
}

if (!_dataReader.Read())
{
_resultCoordinator.HasNext = false;
// Enumeration has ended, materialize last element
_resultCoordinator.ResultReady = true;
Current = _shaper(
_relationalQueryContext, _dataReader.DbDataReader,
_resultCoordinator.ResultContext, _indexMap, _resultCoordinator);

break;
}
}
}

return hasNext;
}
}
catch (Exception exception)
{
_logger.QueryIterationFailed(_contextType, exception);

throw;
}
}

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, ResultContext, int[], ResultCoordinator, T> _shaper;
private readonly Type _contextType;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _logger;
private readonly CancellationToken _cancellationToken;

private RelationalDataReader _dataReader;
private int[] _indexMap;
private ResultCoordinator _resultCoordinator;

public AsyncEnumerator(
QueryingEnumerable<T> queryingEnumerable,
CancellationToken cancellationToken)
{
_relationalQueryContext = queryingEnumerable._relationalQueryContext;
_relationalCommandCache = queryingEnumerable._relationalCommandCache;
_columnNames = queryingEnumerable._columnNames;
_shaper = queryingEnumerable._shaper;
_contextType = queryingEnumerable._contextType;
_logger = queryingEnumerable._logger;
_cancellationToken = cancellationToken;
}

public T Current { get; private set; }

public async ValueTask<bool> MoveNextAsync()
{
try
{
using (_relationalQueryContext.ConcurrencyDetector.EnterCriticalSection())
{
if (_dataReader == null)
{
var relationalCommand = _relationalCommandCache.GetRelationalCommand(
_relationalQueryContext.ParameterValues);

_dataReader
= await relationalCommand.ExecuteReaderAsync(
new RelationalCommandParameterObject(
_relationalQueryContext.Connection,
_relationalQueryContext.ParameterValues,
_relationalQueryContext.Context,
_relationalQueryContext.CommandLogger),
_cancellationToken);

// Non-Composed FromSql
if (_columnNames != null)
{
var readerColumns = Enumerable.Range(0, _dataReader.DbDataReader.FieldCount)
.ToDictionary(i => _dataReader.DbDataReader.GetName(i), i => i, StringComparer.OrdinalIgnoreCase);

_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;
}
}
else
{
_indexMap = null;
}

_resultCoordinator = new ResultCoordinator();
}

var hasNext = _resultCoordinator.HasNext ?? await _dataReader.ReadAsync(_cancellationToken);
Current = default;

if (hasNext)
{
while (true)
{
_resultCoordinator.ResultReady = true;
_resultCoordinator.HasNext = null;
Current = _shaper(
_relationalQueryContext, _dataReader.DbDataReader,
_resultCoordinator.ResultContext, _indexMap, _resultCoordinator);
if (_resultCoordinator.ResultReady)
{
// We generated a result so null out previously stored values
_resultCoordinator.ResultContext.Values = null;
break;
}

if (!await _dataReader.ReadAsync(_cancellationToken))
{
_resultCoordinator.HasNext = false;
// Enumeration has ended, materialize last element
_resultCoordinator.ResultReady = true;
Current = _shaper(
_relationalQueryContext, _dataReader.DbDataReader,
_resultCoordinator.ResultContext, _indexMap, _resultCoordinator);

break;
}
}
}

return hasNext;
}
}
catch (Exception exception)
{
_logger.QueryIterationFailed(_contextType, exception);

throw;
}
}

public ValueTask DisposeAsync()
{
if (_dataReader != null)
{
var dataReader = _dataReader;
_dataReader = null;

return dataReader.DisposeAsync();
}

return default;
}
}
}
}
Loading

0 comments on commit b21099b

Please sign in to comment.