Skip to content

Commit

Permalink
Query: Introduce identity resolution in no tracking query
Browse files Browse the repository at this point in the history
- Uses an adhoc statemanager in the background which is different from statemanager in the context

Resolves #19877
  • Loading branch information
smitpatel committed Apr 24, 2020
1 parent 8cd62aa commit a3f2744
Show file tree
Hide file tree
Showing 21 changed files with 333 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ private sealed class QueryingEnumerable<T> : IEnumerable<T>, IAsyncEnumerable<T>
private readonly Type _contextType;
private readonly string _partitionKey;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _logger;
private readonly bool _performIdentityResolution;

public QueryingEnumerable(
CosmosQueryContext cosmosQueryContext,
Expand All @@ -41,7 +42,8 @@ public QueryingEnumerable(
Func<CosmosQueryContext, JObject, T> shaper,
Type contextType,
string partitionKeyFromExtension,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
IDiagnosticsLogger<DbLoggerCategory.Query> logger,
bool performIdentityResolution)
{
_cosmosQueryContext = cosmosQueryContext;
_sqlExpressionFactory = sqlExpressionFactory;
Expand All @@ -51,6 +53,7 @@ public QueryingEnumerable(
_contextType = contextType;
_logger = logger;
_partitionKey = partitionKeyFromExtension;
_performIdentityResolution = performIdentityResolution;
}

public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
Expand Down Expand Up @@ -98,6 +101,7 @@ private sealed class Enumerator : IEnumerator<T>
private readonly Type _contextType;
private readonly string _partitionKey;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _logger;
private readonly bool _performIdentityResolution;

private IEnumerator<JObject> _enumerator;

Expand All @@ -110,6 +114,7 @@ public Enumerator(QueryingEnumerable<T> queryingEnumerable)
_contextType = queryingEnumerable._contextType;
_partitionKey = queryingEnumerable._partitionKey;
_logger = queryingEnumerable._logger;
_performIdentityResolution = queryingEnumerable._performIdentityResolution;
}

public T Current { get; private set; }
Expand All @@ -132,6 +137,7 @@ public bool MoveNext()
_partitionKey,
sqlQuery)
.GetEnumerator();
_cosmosQueryContext.InitializeStateManager(_performIdentityResolution);
}

var hasNext = _enumerator.MoveNext();
Expand Down Expand Up @@ -170,6 +176,7 @@ private sealed class AsyncEnumerator : IAsyncEnumerator<T>
private readonly Type _contextType;
private readonly string _partitionKey;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _logger;
private readonly bool _performIdentityResolution;
private readonly CancellationToken _cancellationToken;

private IAsyncEnumerator<JObject> _enumerator;
Expand All @@ -183,6 +190,7 @@ public AsyncEnumerator(QueryingEnumerable<T> queryingEnumerable, CancellationTok
_contextType = queryingEnumerable._contextType;
_partitionKey = queryingEnumerable._partitionKey;
_logger = queryingEnumerable._logger;
_performIdentityResolution = queryingEnumerable._performIdentityResolution;
_cancellationToken = cancellationToken;
}

Expand All @@ -204,6 +212,7 @@ public async ValueTask<bool> MoveNextAsync()
_partitionKey,
sqlQuery)
.GetAsyncEnumerator(_cancellationToken);
_cosmosQueryContext.InitializeStateManager(_performIdentityResolution);
}

var hasNext = await _enumerator.MoveNextAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Microsoft.EntityFrameworkCore.Cosmos.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.Query;
Expand All @@ -34,19 +35,22 @@ private sealed class ReadItemQueryingEnumerable<T> : IEnumerable<T>, IAsyncEnume
private readonly Func<CosmosQueryContext, JObject, T> _shaper;
private readonly Type _contextType;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _logger;
private readonly bool _performIdentityResolution;

public ReadItemQueryingEnumerable(
CosmosQueryContext cosmosQueryContext,
ReadItemExpression readItemExpression,
Func<CosmosQueryContext, JObject, T> shaper,
Type contextType,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
IDiagnosticsLogger<DbLoggerCategory.Query> logger,
bool performIdentityResolution)
{
_cosmosQueryContext = cosmosQueryContext;
_readItemExpression = readItemExpression;
_shaper = shaper;
_contextType = contextType;
_logger = logger;
_performIdentityResolution = performIdentityResolution;
}

public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
Expand All @@ -68,6 +72,7 @@ private sealed class Enumerator : IEnumerator<T>, IAsyncEnumerator<T>
private readonly Func<CosmosQueryContext, JObject, T> _shaper;
private readonly Type _contextType;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _logger;
private readonly bool _performIdentityResolution;
private readonly CancellationToken _cancellationToken;

private JObject _item;
Expand All @@ -80,6 +85,7 @@ public Enumerator(ReadItemQueryingEnumerable<T> readItemEnumerable, Cancellation
_shaper = readItemEnumerable._shaper;
_contextType = readItemEnumerable._contextType;
_logger = readItemEnumerable._logger;
_performIdentityResolution = readItemEnumerable._performIdentityResolution;
_cancellationToken = cancellationToken;
}

Expand Down Expand Up @@ -182,6 +188,8 @@ private bool ShapeResult()
{
var hasNext = !(_item is null);

_cosmosQueryContext.InitializeStateManager(_performIdentityResolution);

Current
= hasNext
? _shaper(_cosmosQueryContext, _item)
Expand Down Expand Up @@ -246,10 +254,10 @@ private bool TryGenerateIdFromKeys(IProperty idProperty, out object value)
{
var entityEntry = Activator.CreateInstance(_readItemExpression.EntityType.ClrType);

#pragma warning disable EF1001 // Internal EF Core API usage.
#pragma warning disable EF1001
var internalEntityEntry = new InternalEntityEntryFactory().Create(
_cosmosQueryContext.StateManager, _readItemExpression.EntityType, entityEntry);
#pragma warning restore EF1001 // Internal EF Core API usage.
_cosmosQueryContext.Context.GetDependencies().StateManager, _readItemExpression.EntityType, entityEntry);
#pragma warning restore EF1001

foreach (var keyProperty in _readItemExpression.EntityType.FindPrimaryKey().Properties)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery
Expression.Constant(shaperLambda.Compile()),
Expression.Constant(_contextType),
Expression.Constant(_partitionKeyFromExtension, typeof(string)),
Expression.Constant(_logger));
Expression.Constant(_logger),
Expression.Constant(PerformIdentityResolution));

case ReadItemExpression readItemExpression:

Expand All @@ -107,8 +108,9 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery
Expression.Constant(readItemExpression),
Expression.Constant(shaperReadItemLambda.Compile()),
Expression.Constant(_contextType),
Expression.Constant(_logger));

Expression.Constant(_logger),
Expression.Constant(PerformIdentityResolution));

default:
throw new NotImplementedException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,22 @@ private sealed class QueryingEnumerable<T> : IAsyncEnumerable<T>, IEnumerable<T>
private readonly Func<QueryContext, ValueBuffer, T> _shaper;
private readonly Type _contextType;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _logger;
private readonly bool _performIdentityResolution;

public QueryingEnumerable(
QueryContext queryContext,
IEnumerable<ValueBuffer> innerEnumerable,
Func<QueryContext, ValueBuffer, T> shaper,
Type contextType,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
IDiagnosticsLogger<DbLoggerCategory.Query> logger,
bool performIdentityResolution)
{
_queryContext = queryContext;
_innerEnumerable = innerEnumerable;
_shaper = shaper;
_contextType = contextType;
_logger = logger;
_performIdentityResolution = performIdentityResolution;
}

public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
Expand All @@ -61,6 +64,7 @@ private sealed class Enumerator : IEnumerator<T>, IAsyncEnumerator<T>
private readonly Func<QueryContext, ValueBuffer, T> _shaper;
private readonly Type _contextType;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _logger;
private readonly bool _performIdentityResolution;
private readonly CancellationToken _cancellationToken;

public Enumerator(QueryingEnumerable<T> queryingEnumerable, CancellationToken cancellationToken = default)
Expand All @@ -70,6 +74,7 @@ public Enumerator(QueryingEnumerable<T> queryingEnumerable, CancellationToken ca
_shaper = queryingEnumerable._shaper;
_contextType = queryingEnumerable._contextType;
_logger = queryingEnumerable._logger;
_performIdentityResolution = queryingEnumerable._performIdentityResolution;
_cancellationToken = cancellationToken;
}

Expand Down Expand Up @@ -118,6 +123,7 @@ private bool MoveNextHelper()
if (_enumerator == null)
{
_enumerator = _innerEnumerable.GetEnumerator();
_queryContext.InitializeStateManager(_performIdentityResolution);
}

var hasNext = _enumerator.MoveNext();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery
innerEnumerable,
Expression.Constant(shaperLambda.Compile()),
Expression.Constant(_contextType),
Expression.Constant(_logger));
Expression.Constant(_logger),
Expression.Constant(PerformIdentityResolution));
}

private static readonly MethodInfo _tableMethodInfo
Expand Down
13 changes: 12 additions & 1 deletion src/EFCore.Relational/Query/Internal/QueryingEnumerable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class QueryingEnumerable<T> : IEnumerable<T>, IAsyncEnumerable<T>, IRelat
private readonly Func<QueryContext, DbDataReader, ResultContext, int[], ResultCoordinator, T> _shaper;
private readonly Type _contextType;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _logger;
private readonly bool _performIdentityResolution;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -43,7 +44,8 @@ public QueryingEnumerable(
[NotNull] IReadOnlyList<ReaderColumn> readerColumns,
[NotNull] Func<QueryContext, DbDataReader, ResultContext, int[], ResultCoordinator, T> shaper,
[NotNull] Type contextType,
[NotNull] IDiagnosticsLogger<DbLoggerCategory.Query> logger)
[NotNull] IDiagnosticsLogger<DbLoggerCategory.Query> logger,
bool performIdentityResolution)
{
_relationalQueryContext = relationalQueryContext;
_relationalCommandCache = relationalCommandCache;
Expand All @@ -52,6 +54,7 @@ public QueryingEnumerable(
_shaper = shaper;
_contextType = contextType;
_logger = logger;
_performIdentityResolution = performIdentityResolution;
}

/// <summary>
Expand Down Expand Up @@ -148,6 +151,7 @@ private sealed class Enumerator : IEnumerator<T>
private readonly Func<QueryContext, DbDataReader, ResultContext, int[], ResultCoordinator, T> _shaper;
private readonly Type _contextType;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _logger;
private readonly bool _performIdentityResolution;

private RelationalDataReader _dataReader;
private int[] _indexMap;
Expand All @@ -163,6 +167,7 @@ public Enumerator(QueryingEnumerable<T> queryingEnumerable)
_shaper = queryingEnumerable._shaper;
_contextType = queryingEnumerable._contextType;
_logger = queryingEnumerable._logger;
_performIdentityResolution = queryingEnumerable._performIdentityResolution;
}

public T Current { get; private set; }
Expand Down Expand Up @@ -246,6 +251,8 @@ private bool InitializeReader(DbContext _, bool result)

_resultCoordinator = new ResultCoordinator();

_relationalQueryContext.InitializeStateManager(_performIdentityResolution);

return result;
}

Expand All @@ -267,6 +274,7 @@ private sealed class AsyncEnumerator : IAsyncEnumerator<T>
private readonly Func<QueryContext, DbDataReader, ResultContext, int[], ResultCoordinator, T> _shaper;
private readonly Type _contextType;
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _logger;
private readonly bool _performIdentityResolution;
private readonly CancellationToken _cancellationToken;

private RelationalDataReader _dataReader;
Expand All @@ -285,6 +293,7 @@ public AsyncEnumerator(
_shaper = queryingEnumerable._shaper;
_contextType = queryingEnumerable._contextType;
_logger = queryingEnumerable._logger;
_performIdentityResolution = queryingEnumerable._performIdentityResolution;
_cancellationToken = cancellationToken;
}

Expand Down Expand Up @@ -368,6 +377,8 @@ private async Task<bool> InitializeReaderAsync(DbContext _, bool result, Cancell

_resultCoordinator = new ResultCoordinator();

_relationalQueryContext.InitializeStateManager(_performIdentityResolution);

return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery
Expression.Constant(projectionColumns, typeof(IReadOnlyList<ReaderColumn>)),
Expression.Constant(shaperLambda.Compile()),
Expression.Constant(_contextType),
Expression.Constant(_logger));
Expression.Constant(_logger),
Expression.Constant(PerformIdentityResolution));
}
}
}
38 changes: 38 additions & 0 deletions src/EFCore/Extensions/EntityFrameworkQueryableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2530,6 +2530,44 @@ public static IQueryable<TEntity> AsTracking<TEntity>(
? source.AsTracking()
: source.AsNoTracking();

internal static readonly MethodInfo PerformIdentityResolutionMethodInfo
= typeof(EntityFrameworkQueryableExtensions)
.GetTypeInfo().GetDeclaredMethod(nameof(PerformIdentityResolution));

/// <summary>
/// <para>
/// Returns a new query where the change tracker will not track any of the entities that are return
/// but query will perform identity resolution in results.
/// If the entity instances are modified, this will not be detected by the change tracker and
/// <see cref="DbContext.SaveChanges()" /> will not persist those changes to the database.
/// </para>
/// <para>
/// Perfoming identity resolution in no tracking query can be useful if creating several instances of same object is
/// expensive than re-using same object. It should be kept in mind that in order to perform identity resolution,
/// all previously created objects will be kept in memory which can lead to high memory usage.
/// </para>
/// </summary>
/// <typeparam name="TEntity"> The type of entity being queried. </typeparam>
/// <param name="source"> The source query. </param>
/// <returns>
/// A new query where the result set will not be tracked by the context.
/// </returns>
public static IQueryable<TEntity> PerformIdentityResolution<TEntity>(
[NotNull] this IQueryable<TEntity> source)
where TEntity : class
{
Check.NotNull(source, nameof(source));

return
source.Provider is EntityQueryProvider
? source.Provider.CreateQuery<TEntity>(
Expression.Call(
instance: null,
method: PerformIdentityResolutionMethodInfo.MakeGenericMethod(typeof(TEntity)),
arguments: source.Expression))
: source;
}

#endregion

#region Tagging
Expand Down
6 changes: 6 additions & 0 deletions src/EFCore/Properties/CoreStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a3f2744

Please sign in to comment.