From 78cf7d1a4985766baec50c78ee9491dd82155979 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Mon, 22 Oct 2018 16:36:13 -0700 Subject: [PATCH] Query: Remove ReLinq dependency from EntityQueryable Part of #12048 --- .../TestModels/Northwind/NorthwindData.cs | 2 +- .../Internal/CompiledAsyncEnumerableQuery.cs | 3 +- .../Query/Internal/CompiledAsyncTaskQuery.cs | 2 +- .../Query/Internal/CompiledQueryCache.cs | 4 +- .../Query/Internal/EntityQueryProvider.cs | 2 +- src/EFCore/Query/Internal/EntityQueryable`.cs | 61 +++++++++++-- .../Query/Internal/IAsyncQueryProvider.cs | 2 +- .../Query/Internal/ICompiledQueryCache.cs | 4 +- src/EFCore/Query/Internal/IQueryCompiler.cs | 6 +- .../Query/Internal/NullAsyncQueryProvider.cs | 6 +- src/EFCore/Query/Internal/QueryCompiler.cs | 87 ++++++++++--------- .../Extensions/QueryableExtensionsTest.cs | 2 +- 12 files changed, 117 insertions(+), 64 deletions(-) diff --git a/src/EFCore.Specification.Tests/TestModels/Northwind/NorthwindData.cs b/src/EFCore.Specification.Tests/TestModels/Northwind/NorthwindData.cs index 8fa5426cbcb..8ad64a4f6db 100644 --- a/src/EFCore.Specification.Tests/TestModels/Northwind/NorthwindData.cs +++ b/src/EFCore.Specification.Tests/TestModels/Northwind/NorthwindData.cs @@ -210,7 +210,7 @@ public object Execute(Expression expression) throw new NotImplementedException(); } - public IAsyncEnumerable ExecuteAsync(Expression expression) + public TResult ExecuteAsync(Expression expression) { throw new NotImplementedException(); } diff --git a/src/EFCore/Query/Internal/CompiledAsyncEnumerableQuery.cs b/src/EFCore/Query/Internal/CompiledAsyncEnumerableQuery.cs index 33d3ab7caab..314d52839b1 100644 --- a/src/EFCore/Query/Internal/CompiledAsyncEnumerableQuery.cs +++ b/src/EFCore/Query/Internal/CompiledAsyncEnumerableQuery.cs @@ -2,6 +2,7 @@ // 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.Linq.Expressions; using JetBrains.Annotations; @@ -93,7 +94,7 @@ public virtual AsyncEnumerable Execute> CreateCompiledQuery( IQueryCompiler queryCompiler, Expression expression) { - var compiledQuery = queryCompiler.CreateCompiledAsyncEnumerableQuery(expression); + var compiledQuery = queryCompiler.CreateCompiledAsyncQuery>(expression); return qc => new AsyncEnumerable(compiledQuery(qc)); } diff --git a/src/EFCore/Query/Internal/CompiledAsyncTaskQuery.cs b/src/EFCore/Query/Internal/CompiledAsyncTaskQuery.cs index 380474498cf..12d67d7822c 100644 --- a/src/EFCore/Query/Internal/CompiledAsyncTaskQuery.cs +++ b/src/EFCore/Query/Internal/CompiledAsyncTaskQuery.cs @@ -163,6 +163,6 @@ public virtual Task ExecuteAsync protected override Func> CreateCompiledQuery( IQueryCompiler queryCompiler, Expression expression) - => queryCompiler.CreateCompiledAsyncTaskQuery(expression); + => queryCompiler.CreateCompiledAsyncSingletonQuery(expression); } } diff --git a/src/EFCore/Query/Internal/CompiledQueryCache.cs b/src/EFCore/Query/Internal/CompiledQueryCache.cs index 03389c82346..921dac69806 100644 --- a/src/EFCore/Query/Internal/CompiledQueryCache.cs +++ b/src/EFCore/Query/Internal/CompiledQueryCache.cs @@ -47,8 +47,8 @@ public virtual Func GetOrAddQuery( /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual Func> GetOrAddAsyncQuery( - object cacheKey, Func>> compiler) + public virtual Func GetOrAddAsyncQuery( + object cacheKey, Func> compiler) => GetOrAddQueryCore(cacheKey, compiler); private Func GetOrAddQueryCore( diff --git a/src/EFCore/Query/Internal/EntityQueryProvider.cs b/src/EFCore/Query/Internal/EntityQueryProvider.cs index 040b3c7d6ab..93194a44946 100644 --- a/src/EFCore/Query/Internal/EntityQueryProvider.cs +++ b/src/EFCore/Query/Internal/EntityQueryProvider.cs @@ -73,7 +73,7 @@ public virtual object Execute(Expression expression) /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual IAsyncEnumerable ExecuteAsync(Expression expression) + public virtual TResult ExecuteAsync(Expression expression) => _queryCompiler.ExecuteAsync(expression); /// diff --git a/src/EFCore/Query/Internal/EntityQueryable`.cs b/src/EFCore/Query/Internal/EntityQueryable`.cs index cb896979ba6..c1cfc28b347 100644 --- a/src/EFCore/Query/Internal/EntityQueryable`.cs +++ b/src/EFCore/Query/Internal/EntityQueryable`.cs @@ -5,11 +5,11 @@ using System.Collections; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Linq.Expressions; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Utilities; -using Remotion.Linq; namespace Microsoft.EntityFrameworkCore.Query.Internal { @@ -17,37 +17,80 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public class EntityQueryable : QueryableBase, IAsyncEnumerable, IDetachableContext, IListSource + public class EntityQueryable + : IOrderedQueryable, + IAsyncEnumerable, + IDetachableContext, + IListSource { private static readonly EntityQueryable _detached = new EntityQueryable(NullAsyncQueryProvider.Instance); + private readonly IAsyncQueryProvider _queryProvider; + /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public EntityQueryable([NotNull] IAsyncQueryProvider provider) - : base(Check.NotNull(provider, nameof(provider))) + public EntityQueryable([NotNull] IAsyncQueryProvider queryProvider) { + Check.NotNull(queryProvider, nameof(queryProvider)); + + _queryProvider = queryProvider; + Expression = Expression.Constant(this); } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public EntityQueryable([NotNull] IAsyncQueryProvider provider, [NotNull] Expression expression) - : base( - Check.NotNull(provider, nameof(provider)), - Check.NotNull(expression, nameof(expression))) + public EntityQueryable([NotNull] IAsyncQueryProvider queryProvider, [NotNull] Expression expression) { + Check.NotNull(queryProvider, nameof(queryProvider)); + Check.NotNull(expression, nameof(expression)); + + _queryProvider = queryProvider; + Expression = expression; } + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual Type ElementType => typeof(TResult); + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual Expression Expression { get; } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual IQueryProvider Provider => _queryProvider; + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual IEnumerator GetEnumerator() + => _queryProvider.Execute>(Expression).GetEnumerator(); + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + IEnumerator IEnumerable.GetEnumerator() + => _queryProvider.Execute(Expression).GetEnumerator(); + /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// IAsyncEnumerator IAsyncEnumerable.GetEnumerator() - => ((IAsyncQueryProvider)Provider).ExecuteAsync(Expression).GetEnumerator(); + => _queryProvider.ExecuteAsync>(Expression).GetEnumerator(); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used diff --git a/src/EFCore/Query/Internal/IAsyncQueryProvider.cs b/src/EFCore/Query/Internal/IAsyncQueryProvider.cs index 574871b4c51..4e14e0d4b01 100644 --- a/src/EFCore/Query/Internal/IAsyncQueryProvider.cs +++ b/src/EFCore/Query/Internal/IAsyncQueryProvider.cs @@ -20,7 +20,7 @@ public interface IAsyncQueryProvider : IQueryProvider /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - IAsyncEnumerable ExecuteAsync([NotNull] Expression expression); + TResult ExecuteAsync([NotNull] Expression expression); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used diff --git a/src/EFCore/Query/Internal/ICompiledQueryCache.cs b/src/EFCore/Query/Internal/ICompiledQueryCache.cs index 02f67a037ee..d9253a175fe 100644 --- a/src/EFCore/Query/Internal/ICompiledQueryCache.cs +++ b/src/EFCore/Query/Internal/ICompiledQueryCache.cs @@ -25,8 +25,8 @@ Func GetOrAddQuery( /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - Func> GetOrAddAsyncQuery( + Func GetOrAddAsyncQuery( [NotNull] object cacheKey, - [NotNull] Func>> compiler); + [NotNull] Func> compiler); } } diff --git a/src/EFCore/Query/Internal/IQueryCompiler.cs b/src/EFCore/Query/Internal/IQueryCompiler.cs index de2da897380..03d7cc80635 100644 --- a/src/EFCore/Query/Internal/IQueryCompiler.cs +++ b/src/EFCore/Query/Internal/IQueryCompiler.cs @@ -26,7 +26,7 @@ public interface IQueryCompiler /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - IAsyncEnumerable ExecuteAsync([NotNull] Expression query); + TResult ExecuteAsync([NotNull] Expression query); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used @@ -44,12 +44,12 @@ public interface IQueryCompiler /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - Func> CreateCompiledAsyncEnumerableQuery([NotNull] Expression query); + Func CreateCompiledAsyncQuery([NotNull] Expression query); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - Func> CreateCompiledAsyncTaskQuery([NotNull] Expression query); + Func> CreateCompiledAsyncSingletonQuery([NotNull] Expression query); } } diff --git a/src/EFCore/Query/Internal/NullAsyncQueryProvider.cs b/src/EFCore/Query/Internal/NullAsyncQueryProvider.cs index 26f0584e417..741e9588faf 100644 --- a/src/EFCore/Query/Internal/NullAsyncQueryProvider.cs +++ b/src/EFCore/Query/Internal/NullAsyncQueryProvider.cs @@ -41,17 +41,17 @@ object IQueryProvider.Execute(Expression expression) throw new NotImplementedException(); } - TResult1 IQueryProvider.Execute(Expression expression) + TResult IQueryProvider.Execute(Expression expression) { throw new NotImplementedException(); } - IAsyncEnumerable IAsyncQueryProvider.ExecuteAsync(Expression expression) + TResult IAsyncQueryProvider.ExecuteAsync(Expression expression) { throw new NotImplementedException(); } - Task IAsyncQueryProvider.ExecuteAsync(Expression expression, CancellationToken cancellationToken) + Task IAsyncQueryProvider.ExecuteAsync(Expression expression, CancellationToken cancellationToken) { throw new NotImplementedException(); } diff --git a/src/EFCore/Query/Internal/QueryCompiler.cs b/src/EFCore/Query/Internal/QueryCompiler.cs index 18500d16be9..7fc369a1dd7 100644 --- a/src/EFCore/Query/Internal/QueryCompiler.cs +++ b/src/EFCore/Query/Internal/QueryCompiler.cs @@ -28,6 +28,10 @@ public class QueryCompiler : IQueryCompiler = typeof(IDatabase).GetTypeInfo() .GetDeclaredMethod(nameof(IDatabase.CompileQuery)); + private static MethodInfo CompileAsyncQueryMethod { get; } + = typeof(IDatabase).GetTypeInfo() + .GetDeclaredMethod(nameof(IDatabase.CompileAsyncQuery)); + private readonly IQueryContextFactory _queryContextFactory; private readonly ICompiledQueryCache _compiledQueryCache; private readonly ICompiledQueryCacheKeyGenerator _compiledQueryCacheKeyGenerator; @@ -158,7 +162,7 @@ var resultItemType /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual IAsyncEnumerable ExecuteAsync(Expression query) + public virtual TResult ExecuteAsync(Expression query) { Check.NotNull(query, nameof(query)); @@ -166,15 +170,20 @@ public virtual IAsyncEnumerable ExecuteAsync(Expression query) query = _queryModelGenerator.ExtractParameters(_logger, query, queryContext); - return CompileAsyncQuery(query)(queryContext); + var compiledQuery + = _compiledQueryCache + .GetOrAddAsyncQuery( + _compiledQueryCacheKeyGenerator.GenerateCacheKey(query, async: true), + () => CompileAsyncQueryCore(query, _queryModelGenerator, _database)); + + return compiledQuery(queryContext); } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual Func> CreateCompiledAsyncEnumerableQuery( - Expression query) + public virtual Func CreateCompiledAsyncQuery(Expression query) { Check.NotNull(query, nameof(query)); @@ -188,40 +197,39 @@ public virtual Func> CreateCompiledAsync /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual Func> CreateCompiledAsyncTaskQuery(Expression query) + public virtual Task ExecuteAsync(Expression query, CancellationToken cancellationToken) { Check.NotNull(query, nameof(query)); - query = _queryModelGenerator.ExtractParameters( - _logger, query, _queryContextFactory.Create(), parameterize: false); + var queryContext = _queryContextFactory.Create(); - var compiledQuery = CompileAsyncQueryCore(query, _queryModelGenerator, _database); + queryContext.CancellationToken = cancellationToken; - return CreateCompiledSingletonAsyncQuery(compiledQuery, _logger, _contextType); - } + query = _queryModelGenerator.ExtractParameters(_logger, query, queryContext); - private static Func> CreateCompiledSingletonAsyncQuery( - Func> compiledQuery, - IDiagnosticsLogger logger, Type contextType) - => qc => ExecuteSingletonAsyncQuery(qc, compiledQuery, logger, contextType); + var compiledQuery + = _compiledQueryCache + .GetOrAddAsyncQuery( + _compiledQueryCacheKeyGenerator.GenerateCacheKey(query, async: true), + () => CompileAsyncQueryCore>(query, _queryModelGenerator, _database)); + + return ExecuteSingletonAsyncQuery(queryContext, compiledQuery, _logger, _contextType); + } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual Task ExecuteAsync(Expression query, CancellationToken cancellationToken) + public virtual Func> CreateCompiledAsyncSingletonQuery(Expression query) { Check.NotNull(query, nameof(query)); - var queryContext = _queryContextFactory.Create(); - - queryContext.CancellationToken = cancellationToken; - - query = _queryModelGenerator.ExtractParameters(_logger, query, queryContext); + query = _queryModelGenerator.ExtractParameters( + _logger, query, _queryContextFactory.Create(), parameterize: false); - var compiledQuery = CompileAsyncQuery(query); + var compiledQuery = CompileAsyncQueryCore>(query, _queryModelGenerator, _database); - return ExecuteSingletonAsyncQuery(queryContext, compiledQuery, _logger, _contextType); + return qc => ExecuteSingletonAsyncQuery(qc, compiledQuery, _logger, _contextType); } private static async Task ExecuteSingletonAsyncQuery( @@ -249,29 +257,30 @@ private static async Task ExecuteSingletonAsyncQuery( } } - /// - /// This API supports the Entity Framework Core infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - protected virtual Func> CompileAsyncQuery( - [NotNull] Expression query) - { - Check.NotNull(query, nameof(query)); - - return _compiledQueryCache - .GetOrAddAsyncQuery( - _compiledQueryCacheKeyGenerator.GenerateCacheKey(query, async: true), - () => CompileAsyncQueryCore(query, _queryModelGenerator, _database)); - } - - private static Func> CompileAsyncQueryCore( + private static Func CompileAsyncQueryCore( Expression query, IQueryModelGenerator queryModelGenerator, IDatabase database) { var queryModel = queryModelGenerator.ParseQuery(query); - return database.CompileAsyncQuery(queryModel); + var resultItemType + = (queryModel.GetOutputDataInfo() + as StreamedSequenceInfo)?.ResultItemType + ?? typeof(TResult).TryGetSequenceType(); + + try + { + return (Func)CompileAsyncQueryMethod + .MakeGenericMethod(resultItemType) + .Invoke(database, new object[] { queryModel }); + } + catch (TargetInvocationException e) + { + ExceptionDispatchInfo.Capture(e.InnerException).Throw(); + + throw; + } } } } diff --git a/test/EFCore.Tests/Extensions/QueryableExtensionsTest.cs b/test/EFCore.Tests/Extensions/QueryableExtensionsTest.cs index 1f8bc1c8ee8..13266cea0d4 100644 --- a/test/EFCore.Tests/Extensions/QueryableExtensionsTest.cs +++ b/test/EFCore.Tests/Extensions/QueryableExtensionsTest.cs @@ -179,7 +179,7 @@ var cancellationTokenPresent public IQueryable CreateQuery(Expression expression) => throw new NotImplementedException(); public object Execute(Expression expression) => throw new NotImplementedException(); public TResult Execute(Expression expression) => throw new NotImplementedException(); - public IAsyncEnumerable ExecuteAsync(Expression expression) => throw new NotImplementedException(); + public TResult ExecuteAsync(Expression expression) => throw new NotImplementedException(); } private class FakeQueryable : IQueryable