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: Introduce identity resolution in no tracking query #20739

Merged
merged 1 commit into from
Apr 28, 2020
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 @@ -33,6 +33,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 @@ -42,7 +43,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,17 +53,17 @@ public QueryingEnumerable(
_shaper = shaper;
_contextType = contextType;
_logger = logger;
_performIdentityResolution = performIdentityResolution;

var partitionKey = selectExpression.GetPartitionKey(cosmosQueryContext.ParameterValues);

if (partitionKey != null && partitionKeyFromExtension != null && partitionKeyFromExtension != partitionKey)
{
throw new InvalidOperationException(CosmosStrings.PartitionKeyMismatch(partitionKeyFromExtension, partitionKey));
}

_partitionKey = partitionKey ?? partitionKeyFromExtension;
}

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

Expand Down Expand Up @@ -107,6 +109,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 @@ -119,6 +122,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 @@ -141,6 +145,7 @@ public bool MoveNext()
_partitionKey,
sqlQuery)
.GetEnumerator();
_cosmosQueryContext.InitializeStateManager(_performIdentityResolution);
}

var hasNext = _enumerator.MoveNext();
Expand Down Expand Up @@ -179,6 +184,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 @@ -192,6 +198,7 @@ public AsyncEnumerator(QueryingEnumerable<T> queryingEnumerable, CancellationTok
_contextType = queryingEnumerable._contextType;
_partitionKey = queryingEnumerable._partitionKey;
_logger = queryingEnumerable._logger;
_performIdentityResolution = queryingEnumerable._performIdentityResolution;
_cancellationToken = cancellationToken;
}

Expand All @@ -213,6 +220,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 @@ -87,7 +87,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(QueryCompilationContext.PerformIdentityResolution));

case ReadItemExpression readItemExpression:

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

default:
throw new NotImplementedException();
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore.Cosmos/Query/Internal/SelectExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public SelectExpression(
public virtual Expression GetMappedProjection([NotNull] ProjectionMember projectionMember)
=> _projectionMapping[projectionMember];


/// <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
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 @@ -92,7 +92,8 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery
innerEnumerable,
Expression.Constant(shaperLambda.Compile()),
Expression.Constant(_contextType),
Expression.Constant(_logger));
Expression.Constant(_logger),
Expression.Constant(QueryCompilationContext.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(QueryCompilationContext.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
Loading