From 7ecadd7b14556057b6526378efba48b1325ddd6a Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Wed, 26 Feb 2020 19:49:05 -0800 Subject: [PATCH] Query: Introduce custom query roots This change introduces derived types of EntityQueryable to be processed as query root in core pipeline. Currently FromSql and Queryable functions generate a method call in query pipeline which would be mapped to a query root. This means that any visitor which need to detect query root, they have to have special code to handle this. With this change, any provider bringing custom query roots can inject custom query root for relevant subtree in the query and it would be processed transparently through the stack. Only during translation, once the custom query root needs to be intercepted to generate correct SQL. Converted FromSql in this PR. Will submit another PR to convert queryable functions. Custom query roots can be generated during query construction itself (before it reaches EF), such query root requires - Overriden Equals/GetHashCode methods so we differentiate them during query caching. - Optional ToString method for debugging print out. - Conversion to such custom roots in QueryFilter if needed. - Components of custom root cannot be parameterized in ParameterExtractingExpressionVisitor Part of #18923 --- .../Query/IFromSqlQueryable.cs | 23 ++++++++ ...stomQueryableInjectingExpressionVisitor.cs | 45 +++++++++++++++ .../Query/Internal/FromSqlQueryable`.cs | 56 +++++++++++++++++++ .../RelationalQueryTranslationPreprocessor.cs | 9 +++ ...yableMethodTranslatingExpressionVisitor.cs | 32 ++++++----- .../Internal/ExpressionExtensions.cs | 5 +- src/EFCore/Query/ExpressionPrinter.cs | 4 +- ...ntityEqualityRewritingExpressionVisitor.cs | 6 +- src/EFCore/Query/Internal/EntityQueryable`.cs | 32 +++++++++++ .../Internal/ExpressionEqualityComparer.cs | 3 +- ...ingExpressionVisitor.ExpressionVisitors.cs | 7 +-- .../NavigationExpandingExpressionVisitor.cs | 40 ++++--------- ...yableMethodTranslatingExpressionVisitor.cs | 5 +- .../Query/GearsOfWarQueryTestBase.cs | 8 +-- .../NorthwindQueryFiltersQueryTestBase.cs | 4 +- .../Query/QueryBugsTest.cs | 2 +- 16 files changed, 216 insertions(+), 65 deletions(-) create mode 100644 src/EFCore.Relational/Query/IFromSqlQueryable.cs create mode 100644 src/EFCore.Relational/Query/Internal/CustomQueryableInjectingExpressionVisitor.cs create mode 100644 src/EFCore.Relational/Query/Internal/FromSqlQueryable`.cs diff --git a/src/EFCore.Relational/Query/IFromSqlQueryable.cs b/src/EFCore.Relational/Query/IFromSqlQueryable.cs new file mode 100644 index 00000000000..f26b266a735 --- /dev/null +++ b/src/EFCore.Relational/Query/IFromSqlQueryable.cs @@ -0,0 +1,23 @@ +// 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.Linq.Expressions; + +namespace Microsoft.EntityFrameworkCore.Query +{ + /// + /// An interface to identify FromSql query roots in LINQ. + /// + public interface IFromSqlQueryable : IEntityQueryable + { + /// + /// Return Sql used to get data for this query root. + /// + string Sql { get; } + + /// + /// Return arguments for the Sql. + /// + Expression Argument { get; } + } +} diff --git a/src/EFCore.Relational/Query/Internal/CustomQueryableInjectingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/CustomQueryableInjectingExpressionVisitor.cs new file mode 100644 index 00000000000..414a4edfb0c --- /dev/null +++ b/src/EFCore.Relational/Query/Internal/CustomQueryableInjectingExpressionVisitor.cs @@ -0,0 +1,45 @@ +// 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.Linq.Expressions; +using System.Reflection; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Microsoft.EntityFrameworkCore.Query.Internal +{ + public class CustomQueryableInjectingExpressionVisitor : ExpressionVisitor + { + protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) + { + if (methodCallExpression.Method.DeclaringType == typeof(RelationalQueryableExtensions) + && methodCallExpression.Method.Name == nameof(RelationalQueryableExtensions.FromSqlOnQueryable)) + { + var sql = (string)((ConstantExpression)methodCallExpression.Arguments[1]).Value; + var entityType = ((IEntityQueryable)((ConstantExpression)methodCallExpression.Arguments[0]).Value).EntityType; + + return CreateFromSqlQueryableExpression(entityType, sql, methodCallExpression.Arguments[2]); + } + + return base.VisitMethodCall(methodCallExpression); + } + + public static ConstantExpression CreateFromSqlQueryableExpression(IEntityType entityType, string sql, Expression argument) + { + return Expression.Constant( + _createFromSqlQueryableMethod + .MakeGenericMethod(entityType.ClrType) + .Invoke( + null, new object[] { NullAsyncQueryProvider.Instance, entityType, sql, argument })); + } + + private static readonly MethodInfo _createFromSqlQueryableMethod + = typeof(CustomQueryableInjectingExpressionVisitor) + .GetTypeInfo().GetDeclaredMethod(nameof(CreateFromSqlQueryable)); + + [UsedImplicitly] + private static FromSqlQueryable CreateFromSqlQueryable( + IAsyncQueryProvider entityQueryProvider, IEntityType entityType, string sql, Expression argument) + => new FromSqlQueryable(entityQueryProvider, entityType, sql, argument); + } +} diff --git a/src/EFCore.Relational/Query/Internal/FromSqlQueryable`.cs b/src/EFCore.Relational/Query/Internal/FromSqlQueryable`.cs new file mode 100644 index 00000000000..470142a20d8 --- /dev/null +++ b/src/EFCore.Relational/Query/Internal/FromSqlQueryable`.cs @@ -0,0 +1,56 @@ +// 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.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Microsoft.EntityFrameworkCore.Query.Internal +{ + /// + /// 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. + /// + public class FromSqlQueryable : EntityQueryable, IFromSqlQueryable + { + /// + /// 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. + /// + public FromSqlQueryable(IAsyncQueryProvider queryProvider, IEntityType entityType, string sql, Expression argument) + : base(queryProvider, entityType) + { + Sql = sql; + Argument = argument; + } + + /// + /// 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. + /// + public string Sql { get; } + + /// + /// 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. + /// + public Expression Argument { get; } + + /// + /// 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. + /// + public override string ToString() => $"{base.ToString()}.FromSql(\"{Sql}\", {Argument.Print()}"; + } +} diff --git a/src/EFCore.Relational/Query/RelationalQueryTranslationPreprocessor.cs b/src/EFCore.Relational/Query/RelationalQueryTranslationPreprocessor.cs index f0f441f5fcf..1130314bbe0 100644 --- a/src/EFCore.Relational/Query/RelationalQueryTranslationPreprocessor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryTranslationPreprocessor.cs @@ -1,7 +1,9 @@ // 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.Linq.Expressions; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.Query @@ -21,5 +23,12 @@ public RelationalQueryTranslationPreprocessor( } protected virtual RelationalQueryTranslationPreprocessorDependencies RelationalDependencies { get; } + + public override Expression NormalizeQueryableMethodCall(Expression expression) + { + expression = new CustomQueryableInjectingExpressionVisitor().Visit(expression); + + return base.NormalizeQueryableMethodCall(expression); + } } } diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index a28a30726db..6d9b90c03ef 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -63,21 +63,18 @@ protected RelationalQueryableMethodTranslatingExpressionVisitor( _subquery = true; } + protected override Expression VisitConstant(ConstantExpression constantExpression) + { + return constantExpression.Value is IFromSqlQueryable fromSqlQueryable + ? CreateShapedQueryExpression(fromSqlQueryable) + : base.VisitConstant(constantExpression); + } + protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) { Check.NotNull(methodCallExpression, nameof(methodCallExpression)); - if (methodCallExpression.Method.DeclaringType == typeof(RelationalQueryableExtensions) - && methodCallExpression.Method.Name == nameof(RelationalQueryableExtensions.FromSqlOnQueryable)) - { - var sql = (string)((ConstantExpression)methodCallExpression.Arguments[1]).Value; - var queryable = (IEntityQueryable)((ConstantExpression)methodCallExpression.Arguments[0]).Value; - - return CreateShapedQueryExpression( - queryable.EntityType, _sqlExpressionFactory.Select(queryable.EntityType, sql, methodCallExpression.Arguments[2])); - } - - var dbFunction = this._model.FindDbFunction(methodCallExpression.Method); + var dbFunction = _model.FindDbFunction(methodCallExpression.Method); if (dbFunction != null && dbFunction.IsIQueryable) { return CreateShapedQueryExpression(methodCallExpression); @@ -94,13 +91,13 @@ protected virtual ShapedQueryExpression CreateShapedQueryExpression([NotNull] Me var sqlFuncExpression = _sqlTranslator.TranslateMethodCall(methodCallExpression) as SqlFunctionExpression; var elementType = methodCallExpression.Method.ReturnType.GetGenericArguments()[0]; - var entityType =_model.FindEntityType(elementType); + var entityType = _model.FindEntityType(elementType); var queryExpression = _sqlExpressionFactory.Select(entityType, sqlFuncExpression); return CreateShapedQueryExpression(entityType, queryExpression); } - [Obsolete("Use overload which takes IEntityType.")] + [Obsolete("Use overload which takes IEntityType.")] protected override ShapedQueryExpression CreateShapedQueryExpression(Type elementType) { Check.NotNull(elementType, nameof(elementType)); @@ -118,6 +115,15 @@ protected override ShapedQueryExpression CreateShapedQueryExpression(IEntityType return CreateShapedQueryExpression(entityType, _sqlExpressionFactory.Select(entityType)); } + private ShapedQueryExpression CreateShapedQueryExpression(IFromSqlQueryable fromSqlQueryable) + { + Check.NotNull(fromSqlQueryable, nameof(fromSqlQueryable)); + + return CreateShapedQueryExpression( + fromSqlQueryable.EntityType, + _sqlExpressionFactory.Select(fromSqlQueryable.EntityType, fromSqlQueryable.Sql, fromSqlQueryable.Argument)); + } + private static ShapedQueryExpression CreateShapedQueryExpression(IEntityType entityType, SelectExpression selectExpression) => new ShapedQueryExpression( selectExpression, diff --git a/src/EFCore/Extensions/Internal/ExpressionExtensions.cs b/src/EFCore/Extensions/Internal/ExpressionExtensions.cs index ce20e8cbeee..59541c2e9cf 100644 --- a/src/EFCore/Extensions/Internal/ExpressionExtensions.cs +++ b/src/EFCore/Extensions/Internal/ExpressionExtensions.cs @@ -8,6 +8,7 @@ using System.Linq.Expressions; using System.Reflection; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Utilities; @@ -138,9 +139,9 @@ public static bool IsLogicalOperation([NotNull] this Expression expression) /// 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. /// + [Obsolete("Use constantExpression.Value is IEntityQueryable instead.")] public static bool IsEntityQueryable([NotNull] this ConstantExpression constantExpression) - => constantExpression.Type.IsGenericType - && constantExpression.Type.GetGenericTypeDefinition() == typeof(EntityQueryable<>); + => constantExpression.Value is IEntityQueryable; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Query/ExpressionPrinter.cs b/src/EFCore/Query/ExpressionPrinter.cs index ed0110f1aca..0a755c64e7d 100644 --- a/src/EFCore/Query/ExpressionPrinter.cs +++ b/src/EFCore/Query/ExpressionPrinter.cs @@ -376,9 +376,9 @@ protected override Expression VisitConstant(ConstantExpression constantExpressio { printable.Print(this); } - else if (constantExpression.IsEntityQueryable()) + else if (constantExpression.Value is IEntityQueryable entityQueryable) { - _stringBuilder.Append($"DbSet<{constantExpression.Type.GenericTypeArguments.First().ShortDisplayName()}>"); + _stringBuilder.Append(entityQueryable.ToString()); } else { diff --git a/src/EFCore/Query/Internal/EntityEqualityRewritingExpressionVisitor.cs b/src/EFCore/Query/Internal/EntityEqualityRewritingExpressionVisitor.cs index a423fb93351..42a52abce6c 100644 --- a/src/EFCore/Query/Internal/EntityEqualityRewritingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/EntityEqualityRewritingExpressionVisitor.cs @@ -55,10 +55,8 @@ protected override Expression VisitConstant(ConstantExpression constantExpressio { Check.NotNull(constantExpression, nameof(constantExpression)); - return constantExpression.IsEntityQueryable() - ? new EntityReferenceExpression( - constantExpression, - ((IEntityQueryable)constantExpression.Value).EntityType) + return constantExpression.Value is IEntityQueryable entityQueryable + ? new EntityReferenceExpression(constantExpression, entityQueryable.EntityType) : (Expression)constantExpression; } diff --git a/src/EFCore/Query/Internal/EntityQueryable`.cs b/src/EFCore/Query/Internal/EntityQueryable`.cs index dab658ae9a5..2745644532d 100644 --- a/src/EFCore/Query/Internal/EntityQueryable`.cs +++ b/src/EFCore/Query/Internal/EntityQueryable`.cs @@ -156,5 +156,37 @@ IList IListSource.GetList() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual QueryDebugView DebugView => new QueryDebugView(() => Expression.Print(), this.ToQueryString); + + /// + /// 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. + /// + public override bool Equals(object obj) + => obj != null + && (ReferenceEquals(this, obj) + || obj is IEntityQueryable entityQueryable + && entityQueryable.EntityType == _entityType); + + /// + /// 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. + /// + public override int GetHashCode() => _entityType?.GetHashCode() ?? 0; + + /// + /// 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. + /// + public override string ToString() + => Expression is ConstantExpression constantExpression + && ReferenceEquals(constantExpression.Value, this) + ? $"DbSet<{_entityType.ClrType.ShortDisplayName()}>()" + : base.ToString(); } } diff --git a/src/EFCore/Query/Internal/ExpressionEqualityComparer.cs b/src/EFCore/Query/Internal/ExpressionEqualityComparer.cs index b12e20879c7..4d419bebe18 100644 --- a/src/EFCore/Query/Internal/ExpressionEqualityComparer.cs +++ b/src/EFCore/Query/Internal/ExpressionEqualityComparer.cs @@ -421,8 +421,7 @@ private static bool CompareConstant(ConstantExpression a, ConstantExpression b) => a.Value == b.Value || (a.Value != null && b.Value != null - && (a.IsEntityQueryable() && b.IsEntityQueryable() && a.Value.GetType() == b.Value.GetType() - || Equals(a.Value, b.Value))); + && (Equals(a.Value, b.Value))); private bool CompareGoto(GotoExpression a, GotoExpression b) => a.Kind == b.Kind diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs index b39934c83e6..5df7bf34a6d 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs @@ -682,12 +682,11 @@ protected override Expression VisitConstant(ConstantExpression constantExpressio { Check.NotNull(constantExpression, nameof(constantExpression)); - if (constantExpression.IsEntityQueryable()) + if (constantExpression.Value is IEntityQueryable entityQueryable) { - var entityType = ((IEntityQueryable)constantExpression.Value).EntityType; - if (entityType == _entityType) + if (entityQueryable.EntityType == _entityType) { - return _navigationExpandingExpressionVisitor.CreateNavigationExpansionExpression(constantExpression, entityType); + return _navigationExpandingExpressionVisitor.CreateNavigationExpansionExpression(constantExpression, _entityType); } } diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs index f5867dadc98..f94d7f6294a 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs @@ -108,12 +108,16 @@ protected override Expression VisitConstant(ConstantExpression constantExpressio { Check.NotNull(constantExpression, nameof(constantExpression)); - if (constantExpression.IsEntityQueryable()) + if (constantExpression.Value is IEntityQueryable entityQueryable) { - var entityType = ((IEntityQueryable)constantExpression.Value).EntityType; - var definingQuery = entityType.GetDefiningQuery(); - NavigationExpansionExpression navigationExpansionExpression; - if (definingQuery != null) + var entityType = entityQueryable.EntityType; + NavigationExpansionExpression navigationExpansionExpression = null; + + // Only apply defining query if not a custom query root. + // This code will get removed when defining query is re-worked + if (constantExpression.Type.IsGenericType + && constantExpression.Type.GetGenericTypeDefinition() == typeof(EntityQueryable<>) + && entityType.GetDefiningQuery() is LambdaExpression definingQuery) { var processedDefiningQueryBody = _parameterExtractingExpressionVisitor.ExtractParameters(definingQuery.Body); processedDefiningQueryBody = _queryTranslationPreprocessor.NormalizeQueryableMethodCall(processedDefiningQueryBody); @@ -125,10 +129,8 @@ protected override Expression VisitConstant(ConstantExpression constantExpressio processedDefiningQueryBody = Reduce(processedDefiningQueryBody); navigationExpansionExpression = CreateNavigationExpansionExpression(processedDefiningQueryBody, entityType); } - else - { - navigationExpansionExpression = CreateNavigationExpansionExpression(constantExpression, entityType); - } + + navigationExpansionExpression ??= CreateNavigationExpansionExpression(constantExpression, entityType); return ApplyQueryFilter(navigationExpansionExpression); } @@ -521,24 +523,6 @@ when QueryableMethods.IsSumWithSelector(method): return methodCallExpression.Update(null, new[] { argument }); } - if (method.IsGenericMethod - && method.Name == "FromSqlOnQueryable" - && methodCallExpression.Arguments.Count == 3 - && methodCallExpression.Arguments[0] is ConstantExpression constantExpression - && methodCallExpression.Arguments[1] is ConstantExpression - && (methodCallExpression.Arguments[2] is ParameterExpression || methodCallExpression.Arguments[2] is ConstantExpression) - && constantExpression.IsEntityQueryable()) - { - var entityType = ((IEntityQueryable)constantExpression.Value).EntityType; - var source = CreateNavigationExpansionExpression(constantExpression, entityType); - source.UpdateSource( - methodCallExpression.Update( - null, - new[] { source.Source, methodCallExpression.Arguments[1], methodCallExpression.Arguments[2] })); - - return ApplyQueryFilter(source); - } - return ProcessUnknownMethod(methodCallExpression); } @@ -915,7 +899,7 @@ private NavigationExpansionExpression ProcessLeftJoin( var currentTree = new NavigationTreeNode(outerSource.CurrentTree, innerSource.CurrentTree); var pendingSelector = new ReplacingExpressionVisitor( new Expression[] { resultSelector.Parameters[0], resultSelector.Parameters[1] }, - new[] { outerSource.PendingSelector, innerPendingSelector}) + new[] { outerSource.PendingSelector, innerPendingSelector }) .Visit(resultSelector.Body); var parameterName = GetParameterName("ti"); diff --git a/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs index ec0f7905969..91d42422b48 100644 --- a/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs @@ -2,7 +2,6 @@ // 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; using System.Linq.Expressions; using System.Reflection; @@ -37,8 +36,8 @@ protected override Expression VisitConstant(ConstantExpression constantExpressio { Check.NotNull(constantExpression, nameof(constantExpression)); - return constantExpression.IsEntityQueryable() - ? CreateShapedQueryExpression(((IEntityQueryable)constantExpression.Value).EntityType) + return constantExpression.Value is IEntityQueryable entityQueryable + ? CreateShapedQueryExpression(entityQueryable.EntityType) : base.VisitConstant(constantExpression); } diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index c436c8db3e7..79c6144395e 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -1504,7 +1504,7 @@ public virtual async Task Select_navigation_with_concat_and_count(bool async) @"(MaterializeCollectionNavigation( navigation: Navigation: Gear.Weapons, subquery: (NavigationExpansionExpression - Source: DbSet + Source: DbSet() .Where(w => EF.Property(g, ""FullName"") != null && EF.Property(g, ""FullName"") == EF.Property(w, ""OwnerFullName"")) PendingSelector: w => (NavigationTreeExpression Value: (EntityReference: Weapon) @@ -1519,7 +1519,7 @@ public virtual async Task Select_navigation_with_concat_and_count(bool async) .Concat((MaterializeCollectionNavigation( navigation: Navigation: Gear.Weapons, subquery: (NavigationExpansionExpression - Source: DbSet + Source: DbSet() .Where(w0 => EF.Property(g, ""FullName"") != null && EF.Property(g, ""FullName"") == EF.Property(w0, ""OwnerFullName"")) PendingSelector: w0 => (NavigationTreeExpression Value: (EntityReference: Weapon) @@ -1547,7 +1547,7 @@ public virtual async Task Concat_with_collection_navigations(bool async) @"(MaterializeCollectionNavigation( navigation: Navigation: Gear.Weapons, subquery: (NavigationExpansionExpression - Source: DbSet + Source: DbSet() .Where(w => EF.Property(g, ""FullName"") != null && EF.Property(g, ""FullName"") == EF.Property(w, ""OwnerFullName"")) PendingSelector: w => (NavigationTreeExpression Value: (EntityReference: Weapon) @@ -1562,7 +1562,7 @@ public virtual async Task Concat_with_collection_navigations(bool async) .Union((MaterializeCollectionNavigation( navigation: Navigation: Gear.Weapons, subquery: (NavigationExpansionExpression - Source: DbSet + Source: DbSet() .Where(w0 => EF.Property(g, ""FullName"") != null && EF.Property(g, ""FullName"") == EF.Property(w0, ""OwnerFullName"")) PendingSelector: w0 => (NavigationTreeExpression Value: (EntityReference: Weapon) diff --git a/test/EFCore.Specification.Tests/Query/NorthwindQueryFiltersQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindQueryFiltersQueryTestBase.cs index e5f22feb4ee..97ba3f15aab 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindQueryFiltersQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindQueryFiltersQueryTestBase.cs @@ -51,7 +51,7 @@ public virtual void Find() public virtual void Client_eval() { Assert.Equal( - CoreStrings.TranslationFailed("DbSet .Where(p => NorthwindContext.ClientMethod(p))"), + CoreStrings.TranslationFailed("DbSet() .Where(p => NorthwindContext.ClientMethod(p))"), RemoveNewLines( Assert.Throws( () => _context.Products.ToList()).Message)); @@ -134,7 +134,7 @@ public virtual void Project_reference_that_itself_has_query_filter_with_another_ public virtual void Included_one_to_many_query_with_client_eval() { Assert.Equal( - CoreStrings.TranslationFailed("DbSet .Where(p => NorthwindContext.ClientMethod(p))"), + CoreStrings.TranslationFailed("DbSet() .Where(p => NorthwindContext.ClientMethod(p))"), RemoveNewLines( Assert.Throws( () => _context.Products.Include(p => p.OrderDetails).ToList()).Message)); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs index 52e770493d0..f3912561e88 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -6767,7 +6767,7 @@ public void Cast_to_non_implemented_interface_is_not_removed_from_expression_tre () => queryBase.Cast().FirstOrDefault(x => x.Id == id)).Message; Assert.Equal( - CoreStrings.TranslationFailed(@"DbSet .Cast() .Where(e => e.Id == __id_0)"), + CoreStrings.TranslationFailed(@"DbSet() .Cast() .Where(e => e.Id == __id_0)"), message.Replace("\r", "").Replace("\n", "")); }