From 594b6ec0ff53ddd2b379ad6017d865206f206257 Mon Sep 17 00:00:00 2001 From: Maurycy Markowski Date: Mon, 24 Jun 2019 11:49:51 -0700 Subject: [PATCH] Fix to #15264 - QueryRewrite: incorporate query filters into nav rewrite Also, fix to #13361 - Query: QueryFilters/Defining queries are not applied recursively When nav rewrite constructs new EntityQueryable it now looks into EntityType for any query filter annotations and applies them on top. Those predicates are parameterized and the parameters created are injected into the context as part of query execution expression. Query filters also fundamentally change how nav expansion creates new navigations - before navigations were being added one by one, so we could easily build the NavigationTree by a new node representing given INavigation. With query filters, the newly created navigation may contain arbitrarily complex NavigationTree structure already (if the query filter itself has some navigations). To support that, when we create new EntityQueryable for join or collection navigation, we need to visit it (creating NavigationExpansionExpression) and merge the resulting NavigationTree with the previous navigation tree. --- src/EFCore/Internal/EntityFinder.cs | 2 +- ...terializeCollectionNavigationExpression.cs | 48 ++ .../NavigationExpansion/NavigationExpander.cs | 15 +- .../NavigationExpansionHelpers.cs | 494 +++++++++++++----- .../NavigationExpansion/NavigationTreeNode.cs | 57 ++ .../CollectionNavigationRewritingVisitor.cs | 62 +-- .../Visitors/IncludeApplyingVisitor.cs | 12 +- .../Visitors/NavigationExpandingVisitor.cs | 25 +- .../NavigationExpandingVisitor_MethodCall.cs | 33 +- .../NavigationExpansionReducingVisitor.cs | 20 +- .../NavigationPropertyUnbindingVisitor.cs | 12 +- .../Query/Pipeline/QueryCompilationContext.cs | 99 ++++ .../QueryCompilationContextFactory.cs | 7 +- ...ueryMetadataExtractingExpressionVisitor.cs | 9 + .../QueryOptimizingExpressionVisitor.cs | 4 +- .../InMemoryComplianceTest.cs | 4 +- .../Query/FiltersInMemoryTest.cs | 1 - .../Query/FiltersInheritanceInMemoryTest.cs | 1 - .../Query/FiltersInheritanceTestBase.cs | 22 +- .../Query/FiltersTestBase.cs | 40 +- .../QueryFilterFuncletizationTestBase.cs | 56 +- .../Query/SimpleQueryTestBase.QueryTypes.cs | 14 +- .../Query/FiltersInheritanceSqlServerTest.cs | 51 +- .../Query/FiltersSqlServerTest.cs | 84 +-- .../Query/QueryBugsTest.cs | 2 +- .../QueryFilterFuncletizationSqlServerTest.cs | 206 ++++---- .../SqlServerComplianceTest.cs | 3 - .../Query/FiltersInheritanceSqliteTest.cs | 3 +- .../Query/FiltersSqliteTest.cs | 6 +- .../QueryFilterFuncletizationSqliteTest.cs | 3 +- .../SqliteComplianceTest.cs | 3 - 31 files changed, 961 insertions(+), 437 deletions(-) create mode 100644 src/EFCore/Query/NavigationExpansion/MaterializeCollectionNavigationExpression.cs diff --git a/src/EFCore/Internal/EntityFinder.cs b/src/EFCore/Internal/EntityFinder.cs index c76ffaf6d48..70d6ba9570a 100644 --- a/src/EFCore/Internal/EntityFinder.cs +++ b/src/EFCore/Internal/EntityFinder.cs @@ -222,7 +222,7 @@ private IQueryable GetDatabaseValuesQuery(InternalEntityEntry entry) keyValues[i] = keyValue; } - return _queryRoot.AsNoTracking()//.IgnoreQueryFilters() + return _queryRoot.AsNoTracking().IgnoreQueryFilters() .Where(BuildObjectLambda(properties, new ValueBuffer(keyValues))) .Select(BuildProjection(entityType)); } diff --git a/src/EFCore/Query/NavigationExpansion/MaterializeCollectionNavigationExpression.cs b/src/EFCore/Query/NavigationExpansion/MaterializeCollectionNavigationExpression.cs new file mode 100644 index 00000000000..330d8eb38d0 --- /dev/null +++ b/src/EFCore/Query/NavigationExpansion/MaterializeCollectionNavigationExpression.cs @@ -0,0 +1,48 @@ +// 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.Metadata; +using Microsoft.EntityFrameworkCore.Query.Expressions.Internal; +using Microsoft.EntityFrameworkCore.Query.Internal; + +namespace Microsoft.EntityFrameworkCore.Query.NavigationExpansion +{ + public class MaterializeCollectionNavigationExpression : Expression, IPrintable + { + private Type _returnType; + public MaterializeCollectionNavigationExpression(Expression operand, INavigation navigation) + { + Operand = operand; + Navigation = navigation; + _returnType = navigation.ClrType; + } + + public virtual Expression Operand { get; } + public virtual INavigation Navigation { get; } + + public override ExpressionType NodeType => ExpressionType.Extension; + public override Type Type => _returnType; + public override bool CanReduce => false; + + protected override Expression VisitChildren(ExpressionVisitor visitor) + { + var newOperand = visitor.Visit(Operand); + + return Update(newOperand); + } + + public virtual MaterializeCollectionNavigationExpression Update(Expression operand) + => operand != Operand + ? new MaterializeCollectionNavigationExpression(operand, Navigation) + : this; + + public virtual void Print(ExpressionPrinter expressionPrinter) + { + expressionPrinter.StringBuilder.Append($"MATERIALIZE_COLLECTION({ Navigation }, "); + expressionPrinter.Visit(Operand); + expressionPrinter.StringBuilder.Append(")"); + } + } +} diff --git a/src/EFCore/Query/NavigationExpansion/NavigationExpander.cs b/src/EFCore/Query/NavigationExpansion/NavigationExpander.cs index 6b4335deed4..e43cfcfc2e3 100644 --- a/src/EFCore/Query/NavigationExpansion/NavigationExpander.cs +++ b/src/EFCore/Query/NavigationExpansion/NavigationExpander.cs @@ -3,27 +3,28 @@ using System.Linq.Expressions; using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query.NavigationExpansion.Visitors; +using Microsoft.EntityFrameworkCore.Query.Pipeline; using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.Query.NavigationExpansion { public class NavigationExpander { - private IModel _model; + private readonly QueryCompilationContext _queryCompilationContext; - public NavigationExpander([NotNull] IModel model) + public NavigationExpander([NotNull] QueryCompilationContext queryCompilationContext) { - Check.NotNull(model, nameof(model)); + Check.NotNull(queryCompilationContext, nameof(queryCompilationContext)); - _model = model; + _queryCompilationContext = queryCompilationContext; } public virtual Expression ExpandNavigations(Expression expression) { - var newExpression = new NavigationExpandingVisitor(_model).Visit(expression); - newExpression = new NavigationExpansionReducingVisitor().Visit(newExpression); + var navigationExpandingVisitor = new NavigationExpandingVisitor(_queryCompilationContext); + var newExpression = navigationExpandingVisitor.Visit(expression); + newExpression = new NavigationExpansionReducingVisitor(navigationExpandingVisitor, _queryCompilationContext).Visit(newExpression); return newExpression; } diff --git a/src/EFCore/Query/NavigationExpansion/NavigationExpansionHelpers.cs b/src/EFCore/Query/NavigationExpansion/NavigationExpansionHelpers.cs index 14552be1632..a4db124c10f 100644 --- a/src/EFCore/Query/NavigationExpansion/NavigationExpansionHelpers.cs +++ b/src/EFCore/Query/NavigationExpansion/NavigationExpansionHelpers.cs @@ -11,6 +11,8 @@ using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query.Expressions.Internal; using Microsoft.EntityFrameworkCore.Query.Internal; +using Microsoft.EntityFrameworkCore.Query.NavigationExpansion.Visitors; +using Microsoft.EntityFrameworkCore.Query.Pipeline; namespace Microsoft.EntityFrameworkCore.Query.NavigationExpansion { @@ -19,7 +21,9 @@ public static class NavigationExpansionHelpers public static NavigationExpansionExpression CreateNavigationExpansionRoot( Expression operand, IEntityType entityType, - INavigation materializeCollectionNavigation) + INavigation materializeCollectionNavigation, + NavigationExpandingVisitor navigationExpandingVisitor, + QueryCompilationContext queryCompilationContext) { var sourceMapping = new SourceMapping { @@ -39,7 +43,7 @@ public static NavigationExpansionExpression CreateNavigationExpansionRoot( pendingSelectorParameter.Type), pendingSelectorParameter); - return new NavigationExpansionExpression( + var result = new NavigationExpansionExpression( operand, new NavigationExpansionExpressionState( pendingSelectorParameter, @@ -53,6 +57,49 @@ public static NavigationExpansionExpression CreateNavigationExpansionRoot( customRootMappings: new List>(), materializeCollectionNavigation), materializeCollectionNavigation?.ClrType ?? operand.Type); + + var rootEntityType = entityType.RootType(); + var queryFilterAnnotation = rootEntityType.FindAnnotation("QueryFilter"); + if (queryFilterAnnotation != null + && !queryCompilationContext.IgnoreQueryFilters + && !navigationExpandingVisitor.AppliedQueryFilters.Contains(rootEntityType)) + { + navigationExpandingVisitor.AppliedQueryFilters.Add(rootEntityType); + var filterPredicate = (LambdaExpression)queryFilterAnnotation.Value; + + var parameterExtractingExpressionVisitor = new ParameterExtractingExpressionVisitor( + queryCompilationContext.EvaluatableExpressionFilter, + queryCompilationContext.ParameterValues, + queryCompilationContext.ContextType, + queryCompilationContext.Logger, + parameterize: false, + generateContextAccessors: true); + + filterPredicate = (LambdaExpression)parameterExtractingExpressionVisitor.ExtractParameters(filterPredicate); + + // in case of query filters we need to strip MaterializeCollectionNavigation from the initial collection and apply it after the filter + result = (NavigationExpansionExpression)RemoveMaterializeCollection(result); + var sequenceType = result.Type.GetSequenceType(); + + // if we are constructing EntityQueryable of a derived type, we need to re-map filter predicate to the correct derived type + var filterPredicateParameter = filterPredicate.Parameters[0]; + if (filterPredicateParameter.Type != sequenceType) + { + var newFilterPredicateParameter = Expression.Parameter(sequenceType, filterPredicateParameter.Name); + filterPredicate = (LambdaExpression)new ExpressionReplacingVisitor(filterPredicateParameter, newFilterPredicateParameter).Visit(filterPredicate); + } + + var whereMethod = LinqMethodHelpers.QueryableWhereMethodInfo.MakeGenericMethod(result.Type.GetSequenceType()); + var filteredResult = (Expression)Expression.Call(whereMethod, result, filterPredicate); + if (materializeCollectionNavigation != null) + { + filteredResult = new MaterializeCollectionNavigationExpression(result, materializeCollectionNavigation); + } + + result = (NavigationExpansionExpression)navigationExpandingVisitor.Visit(filteredResult); + } + + return result; } private static readonly MethodInfo _leftJoinMethodInfo = typeof(QueryableExtensions).GetTypeInfo() @@ -65,7 +112,9 @@ public static (Expression source, ParameterExpression parameter) AddNavigationJo NavigationTreeNode navigationTree, NavigationExpansionExpressionState state, List navigationPath, - bool include) + bool include, + NavigationExpandingVisitor navigationExpandingVisitor, + QueryCompilationContext queryCompilationContext) { var joinNeeded = include ? navigationTree.Included == NavigationTreeNodeIncludeMode.ReferencePending @@ -73,140 +122,172 @@ public static (Expression source, ParameterExpression parameter) AddNavigationJo if (joinNeeded) { - var navigation = navigationTree.Navigation; - var sourceType = sourceExpression.Type.GetSequenceType(); - var navigationTargetEntityType = navigation.GetTargetType(); - - var entityQueryable = NullAsyncQueryProvider.Instance.CreateEntityQueryableExpression(navigationTargetEntityType.ClrType); - var resultType = typeof(TransparentIdentifier<,>).MakeGenericType(sourceType, navigationTargetEntityType.ClrType); - - var outerParameter = Expression.Parameter(sourceType, parameterExpression.Name); - var outerKeySelectorParameter = outerParameter; - var transparentIdentifierAccessorExpression = outerParameter.BuildPropertyAccess(navigationTree.Parent.ToMapping); - - var outerKeySelectorBody = CreateKeyAccessExpression( - transparentIdentifierAccessorExpression, - navigation.IsDependentToPrincipal() - ? navigation.ForeignKey.Properties - : navigation.ForeignKey.PrincipalKey.Properties, - addNullCheck: navigationTree.Parent != null && navigationTree.Parent.Optional); - - var innerKeySelectorParameterType = navigationTargetEntityType.ClrType; - var innerKeySelectorParameter = Expression.Parameter( - innerKeySelectorParameterType, - parameterExpression.Name + "." + navigationTree.Navigation.Name); - - var innerKeySelectorBody = CreateKeyAccessExpression( - innerKeySelectorParameter, - navigation.IsDependentToPrincipal() - ? navigation.ForeignKey.PrincipalKey.Properties - : navigation.ForeignKey.Properties); - - if (outerKeySelectorBody.Type.IsNullableType() - && !innerKeySelectorBody.Type.IsNullableType()) + // TODO: hack/quirk to work-around potential bugs in the new navigation generation + if (queryCompilationContext.IgnoreQueryFilters) { - innerKeySelectorBody = Expression.Convert(innerKeySelectorBody, outerKeySelectorBody.Type); + LegacyCodepath(ref sourceExpression, ref parameterExpression, navigationTree, state, navigationPath, include); } - else if (innerKeySelectorBody.Type.IsNullableType() - && !outerKeySelectorBody.Type.IsNullableType()) + else { - outerKeySelectorBody = Expression.Convert(outerKeySelectorBody, innerKeySelectorBody.Type); - } + var navigation = navigationTree.Navigation; + var sourceType = sourceExpression.Type.GetSequenceType(); + var entityQueryable = NullAsyncQueryProvider.Instance.CreateEntityQueryableExpression(navigation.GetTargetType().ClrType); + var navigationRoot = CreateNavigationExpansionRoot(entityQueryable, navigation.GetTargetType(), materializeCollectionNavigation: null, navigationExpandingVisitor, queryCompilationContext); + + var resultType = typeof(TransparentIdentifier<,>).MakeGenericType(sourceType, navigationRoot.State.CurrentParameter.Type); + + var outerParameter = Expression.Parameter(sourceType, parameterExpression.Name); + var outerKeySelectorParameter = outerParameter; + var outerTransparentIdentifierAccessorExpression = outerParameter.BuildPropertyAccess(navigationTree.Parent.ToMapping); + + var outerKeySelectorBody = CreateKeyAccessExpression( + outerTransparentIdentifierAccessorExpression, + navigation.IsDependentToPrincipal() + ? navigation.ForeignKey.Properties + : navigation.ForeignKey.PrincipalKey.Properties, + addNullCheck: navigationTree.Parent != null && navigationTree.Parent.Optional); + + var innerKeySelectorParameter = Expression.Parameter( + navigationRoot.State.CurrentParameter.Type, + navigationRoot.State.CurrentParameter.Name + "." + navigationTree.Navigation.Name); + + // we are guaranteed to only have one SourceMapping here because it will either be a simple EntityQueryable (normal navigation expansion case) + // or EntityQueryable with navigations from query filters - those however can only contain navigations that stem from the original, so it's all part of the same root, hence only one SourceMapping + // + // if the query filter is complex and contains Joins/GroupJoins etc that would spawn additional SourceMappings, + // those mappings would be on the inner NavigationExpansionExpression, so we don't need to worry about them here. + var navigationRootSourceMapping = navigationRoot.State.SourceMappings.Single(); + var innerTransparentIdentifierAccessorExpression = innerKeySelectorParameter.BuildPropertyAccess(navigationRootSourceMapping.NavigationTree.ToMapping); + + var innerKeySelectorBody = CreateKeyAccessExpression( + innerTransparentIdentifierAccessorExpression, + navigation.IsDependentToPrincipal() + ? navigation.ForeignKey.PrincipalKey.Properties + : navigation.ForeignKey.Properties); + + if (outerKeySelectorBody.Type.IsNullableType() + && !innerKeySelectorBody.Type.IsNullableType()) + { + innerKeySelectorBody = Expression.Convert(innerKeySelectorBody, outerKeySelectorBody.Type); + } + else if (innerKeySelectorBody.Type.IsNullableType() + && !outerKeySelectorBody.Type.IsNullableType()) + { + outerKeySelectorBody = Expression.Convert(outerKeySelectorBody, innerKeySelectorBody.Type); + } - var outerKeySelector = Expression.Lambda( - outerKeySelectorBody, - outerKeySelectorParameter); + var outerKeySelector = Expression.Lambda( + outerKeySelectorBody, + outerKeySelectorParameter); - var innerKeySelector = Expression.Lambda( - innerKeySelectorBody, - innerKeySelectorParameter); + var innerKeySelector = Expression.Lambda( + innerKeySelectorBody, + innerKeySelectorParameter); - if (!sourceExpression.Type.IsQueryableType()) - { - var asQueryableMethodInfo = LinqMethodHelpers.AsQueryable.MakeGenericMethod(sourceType); - sourceExpression = Expression.Call(asQueryableMethodInfo, sourceExpression); - } + if (!sourceExpression.Type.IsQueryableType()) + { + var asQueryableMethodInfo = LinqMethodHelpers.AsQueryable.MakeGenericMethod(sourceType); + sourceExpression = Expression.Call(asQueryableMethodInfo, sourceExpression); + } - var joinMethodInfo = navigationTree.Optional - ? _leftJoinMethodInfo.MakeGenericMethod( - sourceType, - navigationTargetEntityType.ClrType, - outerKeySelector.Body.Type, - resultType) - : LinqMethodHelpers.QueryableJoinMethodInfo.MakeGenericMethod( - sourceType, - navigationTargetEntityType.ClrType, - outerKeySelector.Body.Type, - resultType); - - var resultSelectorOuterParameterName = outerParameter.Name; - var resultSelectorOuterParameter = Expression.Parameter(sourceType, resultSelectorOuterParameterName); - - var resultSelectorInnerParameterName = innerKeySelectorParameter.Name; - var resultSelectorInnerParameter = Expression.Parameter(navigationTargetEntityType.ClrType, resultSelectorInnerParameterName); - - var transparentIdentifierCtorInfo - = resultType.GetTypeInfo().GetConstructors().Single(); - - var transparentIdentifierOuterMemberInfo = resultType.GetTypeInfo().GetDeclaredField("Outer"); - var transparentIdentifierInnerMemberInfo = resultType.GetTypeInfo().GetDeclaredField("Inner"); - - var resultSelector = Expression.Lambda( - Expression.New( - transparentIdentifierCtorInfo, - new[] { resultSelectorOuterParameter, resultSelectorInnerParameter }, - new[] { transparentIdentifierOuterMemberInfo, transparentIdentifierInnerMemberInfo }), - resultSelectorOuterParameter, - resultSelectorInnerParameter); - - var joinMethodCall = Expression.Call( - joinMethodInfo, - sourceExpression, - entityQueryable, - outerKeySelector, - innerKeySelector, - resultSelector); - - sourceExpression = joinMethodCall; - - var transparentIdentifierParameterName = resultSelectorInnerParameterName; - var transparentIdentifierParameter = Expression.Parameter(resultSelector.ReturnType, transparentIdentifierParameterName); - parameterExpression = transparentIdentifierParameter; - - // remap navigation 'To' paths -> for this navigation prepend "Inner", for every other (already expanded) navigation prepend "Outer" - navigationTree.ToMapping.Insert(0, nameof(TransparentIdentifier.Inner)); - foreach (var mapping in state.SourceMappings) - { - var nodes = include - ? mapping.NavigationTree.Flatten().Where(n => (n.Included == NavigationTreeNodeIncludeMode.ReferenceComplete + var joinMethodInfo = navigationTree.Optional + ? _leftJoinMethodInfo.MakeGenericMethod( + sourceType, + navigationRoot.State.CurrentParameter.Type, + outerKeySelector.Body.Type, + resultType) + : LinqMethodHelpers.QueryableJoinMethodInfo.MakeGenericMethod( + sourceType, + navigationRoot.State.CurrentParameter.Type, + outerKeySelector.Body.Type, + resultType); + + var resultSelectorOuterParameterName = outerParameter.Name; + var resultSelectorOuterParameter = Expression.Parameter(sourceType, resultSelectorOuterParameterName); + + var resultSelectorInnerParameterName = innerKeySelectorParameter.Name; + var resultSelectorInnerParameter = Expression.Parameter(navigationRoot.State.CurrentParameter.Type, resultSelectorInnerParameterName); + + var transparentIdentifierCtorInfo + = resultType.GetTypeInfo().GetConstructors().Single(); + + var transparentIdentifierOuterMemberInfo = resultType.GetTypeInfo().GetDeclaredField("Outer"); + var transparentIdentifierInnerMemberInfo = resultType.GetTypeInfo().GetDeclaredField("Inner"); + + var resultSelector = Expression.Lambda( + Expression.New( + transparentIdentifierCtorInfo, + new[] { resultSelectorOuterParameter, resultSelectorInnerParameter }, + new[] { transparentIdentifierOuterMemberInfo, transparentIdentifierInnerMemberInfo }), + resultSelectorOuterParameter, + resultSelectorInnerParameter); + + var joinMethodCall = Expression.Call( + joinMethodInfo, + sourceExpression, + navigationRoot.Operand, + outerKeySelector, + innerKeySelector, + resultSelector); + + sourceExpression = joinMethodCall; + + var transparentIdentifierParameterName = resultSelectorInnerParameterName; + var transparentIdentifierParameter = Expression.Parameter(resultSelector.ReturnType, transparentIdentifierParameterName); + parameterExpression = transparentIdentifierParameter; + + // remap navigation 'To' paths -> for inner navigations (they should all be expanded by now) prepend "Inner", for every other (already expanded) navigation prepend "Outer" + var innerNodes = include + ? navigationRootSourceMapping.NavigationTree.Flatten().Where(n => (n.Included == NavigationTreeNodeIncludeMode.ReferenceComplete || n.ExpansionMode == NavigationTreeNodeExpansionMode.ReferenceComplete || n.Navigation.ForeignKey.IsOwnership) && n != navigationTree) - : mapping.NavigationTree.Flatten().Where(n => (n.ExpansionMode == NavigationTreeNodeExpansionMode.ReferenceComplete + : navigationRootSourceMapping.NavigationTree.Flatten().Where(n => (n.ExpansionMode == NavigationTreeNodeExpansionMode.ReferenceComplete || n.Navigation.ForeignKey.IsOwnership) && n != navigationTree); - foreach (var navigationTreeNode in nodes) + foreach (var innerNode in innerNodes) { - navigationTreeNode.ToMapping.Insert(0, nameof(TransparentIdentifier.Outer)); + innerNode.ToMapping.Insert(0, nameof(TransparentIdentifier.Inner)); } - } - foreach (var customRootMapping in state.CustomRootMappings) - { - customRootMapping.Insert(0, nameof(TransparentIdentifier.Outer)); - } + foreach (var mapping in state.SourceMappings) + { + var nodes = include + ? mapping.NavigationTree.Flatten().Where(n => (n.Included == NavigationTreeNodeIncludeMode.ReferenceComplete + || n.ExpansionMode == NavigationTreeNodeExpansionMode.ReferenceComplete + || n.Navigation.ForeignKey.IsOwnership) + && n != navigationTree) + : mapping.NavigationTree.Flatten().Where(n => (n.ExpansionMode == NavigationTreeNodeExpansionMode.ReferenceComplete + || n.Navigation.ForeignKey.IsOwnership) + && n != navigationTree); + + foreach (var navigationTreeNode in nodes) + { + navigationTreeNode.ToMapping.Insert(0, nameof(TransparentIdentifier.Outer)); + } + } - if (include) - { - navigationTree.Included = NavigationTreeNodeIncludeMode.ReferenceComplete; - } - else - { - navigationTree.ExpansionMode = NavigationTreeNodeExpansionMode.ReferenceComplete; + // TODO: there shouldn't be any custom root mappings for inner, but not 100% sure - think & TEST! + foreach (var customRootMapping in state.CustomRootMappings) + { + customRootMapping.Insert(0, nameof(TransparentIdentifier.Outer)); + } + + if (include) + { + navigationTree.Included = NavigationTreeNodeIncludeMode.ReferenceComplete; + } + else + { + navigationTree.ExpansionMode = NavigationTreeNodeExpansionMode.ReferenceComplete; + } + // finally, we need to incorporate the newly created navigation tree into the old one + navigationRootSourceMapping.NavigationTree.SetNavigation(navigationTree.Navigation); + navigationTree.Parent.AddChild(navigationRootSourceMapping.NavigationTree); + navigationPath.Add(navigation); } - navigationPath.Add(navigation); } else { @@ -223,12 +304,158 @@ var transparentIdentifierCtorInfo child, state, navigationPath.ToList(), - include); + include, + navigationExpandingVisitor, + queryCompilationContext); } return result; } + private static void LegacyCodepath( + ref Expression sourceExpression, + ref ParameterExpression parameterExpression, + NavigationTreeNode navigationTree, + NavigationExpansionExpressionState state, + List navigationPath, + bool include) + { + var navigation = navigationTree.Navigation; + var sourceType = sourceExpression.Type.GetSequenceType(); + var navigationTargetEntityType = navigation.GetTargetType(); + + var entityQueryable = NullAsyncQueryProvider.Instance.CreateEntityQueryableExpression(navigationTargetEntityType.ClrType); + var resultType = typeof(TransparentIdentifier<,>).MakeGenericType(sourceType, navigationTargetEntityType.ClrType); + + var outerParameter = Expression.Parameter(sourceType, parameterExpression.Name); + var outerKeySelectorParameter = outerParameter; + var transparentIdentifierAccessorExpression = outerParameter.BuildPropertyAccess(navigationTree.Parent.ToMapping); + + var outerKeySelectorBody = CreateKeyAccessExpression( + transparentIdentifierAccessorExpression, + navigation.IsDependentToPrincipal() + ? navigation.ForeignKey.Properties + : navigation.ForeignKey.PrincipalKey.Properties, + addNullCheck: navigationTree.Parent != null && navigationTree.Parent.Optional); + + var innerKeySelectorParameterType = navigationTargetEntityType.ClrType; + var innerKeySelectorParameter = Expression.Parameter( + innerKeySelectorParameterType, + parameterExpression.Name + "." + navigationTree.Navigation.Name); + + var innerKeySelectorBody = CreateKeyAccessExpression( + innerKeySelectorParameter, + navigation.IsDependentToPrincipal() + ? navigation.ForeignKey.PrincipalKey.Properties + : navigation.ForeignKey.Properties); + + if (outerKeySelectorBody.Type.IsNullableType() + && !innerKeySelectorBody.Type.IsNullableType()) + { + innerKeySelectorBody = Expression.Convert(innerKeySelectorBody, outerKeySelectorBody.Type); + } + else if (innerKeySelectorBody.Type.IsNullableType() + && !outerKeySelectorBody.Type.IsNullableType()) + { + outerKeySelectorBody = Expression.Convert(outerKeySelectorBody, innerKeySelectorBody.Type); + } + + var outerKeySelector = Expression.Lambda( + outerKeySelectorBody, + outerKeySelectorParameter); + + var innerKeySelector = Expression.Lambda( + innerKeySelectorBody, + innerKeySelectorParameter); + + if (!sourceExpression.Type.IsQueryableType()) + { + var asQueryableMethodInfo = LinqMethodHelpers.AsQueryable.MakeGenericMethod(sourceType); + sourceExpression = Expression.Call(asQueryableMethodInfo, sourceExpression); + } + + var joinMethodInfo = navigationTree.Optional + ? _leftJoinMethodInfo.MakeGenericMethod( + sourceType, + navigationTargetEntityType.ClrType, + outerKeySelector.Body.Type, + resultType) + : LinqMethodHelpers.QueryableJoinMethodInfo.MakeGenericMethod( + sourceType, + navigationTargetEntityType.ClrType, + outerKeySelector.Body.Type, + resultType); + + var resultSelectorOuterParameterName = outerParameter.Name; + var resultSelectorOuterParameter = Expression.Parameter(sourceType, resultSelectorOuterParameterName); + + var resultSelectorInnerParameterName = innerKeySelectorParameter.Name; + var resultSelectorInnerParameter = Expression.Parameter(navigationTargetEntityType.ClrType, resultSelectorInnerParameterName); + + var transparentIdentifierCtorInfo + = resultType.GetTypeInfo().GetConstructors().Single(); + + var transparentIdentifierOuterMemberInfo = resultType.GetTypeInfo().GetDeclaredField("Outer"); + var transparentIdentifierInnerMemberInfo = resultType.GetTypeInfo().GetDeclaredField("Inner"); + + var resultSelector = Expression.Lambda( + Expression.New( + transparentIdentifierCtorInfo, + new[] { resultSelectorOuterParameter, resultSelectorInnerParameter }, + new[] { transparentIdentifierOuterMemberInfo, transparentIdentifierInnerMemberInfo }), + resultSelectorOuterParameter, + resultSelectorInnerParameter); + + var joinMethodCall = Expression.Call( + joinMethodInfo, + sourceExpression, + entityQueryable, + outerKeySelector, + innerKeySelector, + resultSelector); + + sourceExpression = joinMethodCall; + + var transparentIdentifierParameterName = resultSelectorInnerParameterName; + var transparentIdentifierParameter = Expression.Parameter(resultSelector.ReturnType, transparentIdentifierParameterName); + parameterExpression = transparentIdentifierParameter; + + // remap navigation 'To' paths -> for this navigation prepend "Inner", for every other (already expanded) navigation prepend "Outer" + navigationTree.ToMapping.Insert(0, nameof(TransparentIdentifier.Inner)); + foreach (var mapping in state.SourceMappings) + { + var nodes = include + ? mapping.NavigationTree.Flatten().Where(n => (n.Included == NavigationTreeNodeIncludeMode.ReferenceComplete + || n.ExpansionMode == NavigationTreeNodeExpansionMode.ReferenceComplete + || n.Navigation.ForeignKey.IsOwnership) + && n != navigationTree) + : mapping.NavigationTree.Flatten().Where(n => (n.ExpansionMode == NavigationTreeNodeExpansionMode.ReferenceComplete + || n.Navigation.ForeignKey.IsOwnership) + && n != navigationTree); + + foreach (var navigationTreeNode in nodes) + { + navigationTreeNode.ToMapping.Insert(0, nameof(TransparentIdentifier.Outer)); + } + } + + foreach (var customRootMapping in state.CustomRootMappings) + { + customRootMapping.Insert(0, nameof(TransparentIdentifier.Outer)); + } + + if (include) + { + navigationTree.Included = NavigationTreeNodeIncludeMode.ReferenceComplete; + } + else + { + navigationTree.ExpansionMode = NavigationTreeNodeExpansionMode.ReferenceComplete; + + } + navigationPath.Add(navigation); + } + public static Expression CreateKeyAccessExpression( Expression target, IReadOnlyList properties, bool addNullCheck = false) => properties.Count == 1 @@ -290,5 +517,34 @@ public static TResult MaterializeCollectionNavigation( return (TResult)collection; } + + public static Expression RemoveMaterializeCollection(Expression expression) + { + if (expression is NavigationExpansionExpression navigationExpansionExpression + && navigationExpansionExpression.State.MaterializeCollectionNavigation != null) + { + navigationExpansionExpression.State.MaterializeCollectionNavigation = null; + + return new NavigationExpansionExpression( + navigationExpansionExpression.Operand, + navigationExpansionExpression.State, + navigationExpansionExpression.Operand.Type); + } + + if (expression is NavigationExpansionRootExpression navigationExpansionRootExpression + && navigationExpansionRootExpression.NavigationExpansion.State.MaterializeCollectionNavigation != null) + { + navigationExpansionRootExpression.NavigationExpansion.State.MaterializeCollectionNavigation = null; + + var rewritten = new NavigationExpansionExpression( + navigationExpansionRootExpression.NavigationExpansion.Operand, + navigationExpansionRootExpression.NavigationExpansion.State, + navigationExpansionRootExpression.NavigationExpansion.Operand.Type); + + return new NavigationExpansionRootExpression(rewritten, navigationExpansionRootExpression.Mapping); + } + + return expression; + } } } diff --git a/src/EFCore/Query/NavigationExpansion/NavigationTreeNode.cs b/src/EFCore/Query/NavigationExpansion/NavigationTreeNode.cs index e1e266d7d3b..b6d74806314 100644 --- a/src/EFCore/Query/NavigationExpansion/NavigationTreeNode.cs +++ b/src/EFCore/Query/NavigationExpansion/NavigationTreeNode.cs @@ -132,6 +132,56 @@ public static NavigationTreeNode Create( return result; } + private static void PrependFromMappings(NavigationTreeNode navigationTreeNode, List> fromMappingsToPrepend) + { + var newFromMappings = new List>(); + foreach (var parentFromMapping in fromMappingsToPrepend) + { + foreach (var fromMapping in navigationTreeNode.FromMappings) + { + var newMapping = parentFromMapping.ToList(); + newMapping.AddRange(fromMapping); + newFromMappings.Add(newMapping); + } + } + + navigationTreeNode.FromMappings = newFromMappings; + foreach (var child in navigationTreeNode.Children) + { + PrependFromMappings(child, fromMappingsToPrepend); + } + } + + public void AddChild([NotNull] NavigationTreeNode childNode, bool propagateFromMappings = true) + { + Check.NotNull(childNode, nameof(childNode)); + + // when adding the first child - propagate FromMappings from the parent + if (propagateFromMappings) + { + PrependFromMappings(childNode, FromMappings); + } + + var existingChild = Children.Where(c => c.Navigation == childNode.Navigation).SingleOrDefault(); + if (existingChild != null) + { + // if the child exisits, copy ToMappings, add new unique FromMappings and try adding it's children + // however for those children we don't need to re-propagate the mappings, since they are already in place + var newMappings = childNode.FromMappings.Where(m => !existingChild.FromMappings.Any(em => em.SequenceEqual(m))); + existingChild.ToMapping = childNode.ToMapping; + existingChild.FromMappings.AddRange(newMappings); + foreach (var grandChild in existingChild.Children) + { + existingChild.AddChild(grandChild, propagateFromMappings: false); + } + } + else + { + Children.Add(childNode); + childNode.Parent = this; + } + } + public List Flatten() { var result = new List(); @@ -150,5 +200,12 @@ public void MakeOptional() { Optional = true; } + + // TODO: hack - refactor this so that it's not needed + internal void SetNavigation(INavigation navigation) + { + Navigation = navigation; + PrependFromMappings(this, new List> { new List { navigation.Name } }); + } } } diff --git a/src/EFCore/Query/NavigationExpansion/Visitors/CollectionNavigationRewritingVisitor.cs b/src/EFCore/Query/NavigationExpansion/Visitors/CollectionNavigationRewritingVisitor.cs index 4468642b436..e32f654ba01 100644 --- a/src/EFCore/Query/NavigationExpansion/Visitors/CollectionNavigationRewritingVisitor.cs +++ b/src/EFCore/Query/NavigationExpansion/Visitors/CollectionNavigationRewritingVisitor.cs @@ -9,6 +9,7 @@ using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query.Internal; +using Microsoft.EntityFrameworkCore.Query.Pipeline; namespace Microsoft.EntityFrameworkCore.Query.NavigationExpansion.Visitors { @@ -19,10 +20,17 @@ namespace Microsoft.EntityFrameworkCore.Query.NavigationExpansion.Visitors public class CollectionNavigationRewritingVisitor : ExpressionVisitor { private readonly ParameterExpression _sourceParameter; + private readonly NavigationExpandingVisitor _navigationExpandingVisitor; + private readonly QueryCompilationContext _queryCompilationContext; - public CollectionNavigationRewritingVisitor(ParameterExpression sourceParameter) + public CollectionNavigationRewritingVisitor( + ParameterExpression sourceParameter, + NavigationExpandingVisitor navigationExpandingVisitor, + QueryCompilationContext queryCompilationContext) { _sourceParameter = sourceParameter; + _navigationExpandingVisitor = navigationExpandingVisitor; + _queryCompilationContext = queryCompilationContext; } protected override Expression VisitLambda(Expression lambdaExpression) @@ -61,7 +69,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp && methodCallExpression.Method.DeclaringType.IsGenericType && methodCallExpression.Method.DeclaringType.GetGenericTypeDefinition() == typeof(List<>)) { - var newCaller = RemoveMaterializeCollection(Visit(methodCallExpression.Object)); + var newCaller = NavigationExpansionHelpers.RemoveMaterializeCollection(Visit(methodCallExpression.Object)); var newPredicate = Visit(methodCallExpression.Arguments[0]); return Expression.Call( @@ -79,7 +87,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp && navigationBindingCaller.NavigationTreeNode.Navigation != null && navigationBindingCaller.NavigationTreeNode.Navigation.IsCollection()) { - var newCaller = RemoveMaterializeCollection(Visit(methodCallExpression.Object)); + var newCaller = NavigationExpansionHelpers.RemoveMaterializeCollection(Visit(methodCallExpression.Object)); var newArgument = Visit(methodCallExpression.Arguments[0]); var lambdaParameter = Expression.Parameter(newCaller.Type.GetSequenceType(), newCaller.Type.GetSequenceType().GenerateParameterName()); @@ -93,13 +101,13 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp lambda); } - var newObject = RemoveMaterializeCollection(Visit(methodCallExpression.Object)); + var newObject = NavigationExpansionHelpers.RemoveMaterializeCollection(Visit(methodCallExpression.Object)); var newArguments = new List(); var argumentsChanged = false; foreach (var argument in methodCallExpression.Arguments) { - var newArgument = RemoveMaterializeCollection(Visit(argument)); + var newArgument = NavigationExpansionHelpers.RemoveMaterializeCollection(Visit(argument)); newArguments.Add(newArgument); if (newArgument != argument) { @@ -112,36 +120,12 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp : methodCallExpression; } - private Expression RemoveMaterializeCollection(Expression expression) - { - if (expression is NavigationExpansionExpression navigationExpansionExpression - && navigationExpansionExpression.State.MaterializeCollectionNavigation != null) - { - navigationExpansionExpression.State.MaterializeCollectionNavigation = null; - - return new NavigationExpansionExpression( - navigationExpansionExpression.Operand, - navigationExpansionExpression.State, - navigationExpansionExpression.Operand.Type); - } - - if (expression is NavigationExpansionRootExpression navigationExpansionRootExpression - && navigationExpansionRootExpression.NavigationExpansion.State.MaterializeCollectionNavigation != null) - { - navigationExpansionRootExpression.NavigationExpansion.State.MaterializeCollectionNavigation = null; - - var rewritten = new NavigationExpansionExpression( - navigationExpansionRootExpression.NavigationExpansion.Operand, - navigationExpansionRootExpression.NavigationExpansion.State, - navigationExpansionRootExpression.NavigationExpansion.Operand.Type); - - return new NavigationExpansionRootExpression(rewritten, navigationExpansionRootExpression.Mapping); - } - - return expression; - } - - public static Expression CreateCollectionNavigationExpression(NavigationTreeNode navigationTreeNode, ParameterExpression rootParameter, SourceMapping sourceMapping) + public static NavigationExpansionExpression CreateCollectionNavigationExpression( + NavigationTreeNode navigationTreeNode, + ParameterExpression rootParameter, + SourceMapping sourceMapping, + NavigationExpandingVisitor navigationExpandingVisitor, + QueryCompilationContext queryCompilationContext) { var collectionEntityType = navigationTreeNode.Navigation.ForeignKey.DeclaringEntityType; var entityQueryable = (Expression)NullAsyncQueryProvider.Instance.CreateEntityQueryableExpression(collectionEntityType.ClrType); @@ -176,7 +160,7 @@ public static Expression CreateCollectionNavigationExpression(NavigationTreeNode entityQueryable, predicate); - var result = NavigationExpansionHelpers.CreateNavigationExpansionRoot(operand, collectionEntityType, navigationTreeNode.Navigation); + var result = NavigationExpansionHelpers.CreateNavigationExpansionRoot(operand, collectionEntityType, navigationTreeNode.Navigation, navigationExpandingVisitor, queryCompilationContext); // this is needed for cases like: root.Include(r => r.Collection).ThenInclude(c => c.Reference).Select(r => r.Collection) // result should be elements of the collection navigation with their 'Reference' included @@ -196,8 +180,8 @@ protected override Expression VisitExtension(Expression extensionExpression) && lastNavigation.IsCollection()) { return lastNavigation.ForeignKey.IsOwnership - ? NavigationExpansionHelpers.CreateNavigationExpansionRoot(navigationBindingExpression, lastNavigation.GetTargetType(), lastNavigation) - : CreateCollectionNavigationExpression(navigationBindingExpression.NavigationTreeNode, navigationBindingExpression.RootParameter, navigationBindingExpression.SourceMapping); + ? NavigationExpansionHelpers.CreateNavigationExpansionRoot(navigationBindingExpression, lastNavigation.GetTargetType(), lastNavigation, _navigationExpandingVisitor, _queryCompilationContext) + : CreateCollectionNavigationExpression(navigationBindingExpression.NavigationTreeNode, navigationBindingExpression.RootParameter, navigationBindingExpression.SourceMapping, _navigationExpandingVisitor, _queryCompilationContext); } else { @@ -210,7 +194,7 @@ protected override Expression VisitExtension(Expression extensionExpression) protected override Expression VisitMember(MemberExpression memberExpression) { - var newExpression = RemoveMaterializeCollection(Visit(memberExpression.Expression)); + var newExpression = NavigationExpansionHelpers.RemoveMaterializeCollection(Visit(memberExpression.Expression)); if (newExpression != memberExpression.Expression) { if (memberExpression.Member.Name == nameof(List.Count)) diff --git a/src/EFCore/Query/NavigationExpansion/Visitors/IncludeApplyingVisitor.cs b/src/EFCore/Query/NavigationExpansion/Visitors/IncludeApplyingVisitor.cs index 318d123d207..748d93ecee2 100644 --- a/src/EFCore/Query/NavigationExpansion/Visitors/IncludeApplyingVisitor.cs +++ b/src/EFCore/Query/NavigationExpansion/Visitors/IncludeApplyingVisitor.cs @@ -4,11 +4,21 @@ using System.Linq; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Query.Pipeline; namespace Microsoft.EntityFrameworkCore.Query.NavigationExpansion.Visitors { public class PendingSelectorIncludeRewriter : ExpressionVisitor { + private readonly NavigationExpandingVisitor _navigationExpandingVisitor; + private readonly QueryCompilationContext _queryCompilationContext; + + public PendingSelectorIncludeRewriter(NavigationExpandingVisitor navigationExpandingVisitor, QueryCompilationContext queryCompilationContext) + { + _navigationExpandingVisitor = navigationExpandingVisitor; + _queryCompilationContext = queryCompilationContext; + } + protected override Expression VisitMember(MemberExpression memberExpression) => memberExpression; protected override Expression VisitInvocation(InvocationExpression invocationExpression) => invocationExpression; protected override Expression VisitLambda(Expression lambdaExpression) => lambdaExpression; @@ -88,7 +98,7 @@ private IncludeExpression CreateIncludeReferenceCall(Expression caller, Navigati private IncludeExpression CreateIncludeCollectionCall(Expression caller, NavigationTreeNode node, ParameterExpression rootParameter, SourceMapping sourceMapping) { - var included = CollectionNavigationRewritingVisitor.CreateCollectionNavigationExpression(node, rootParameter, sourceMapping); + var included = CollectionNavigationRewritingVisitor.CreateCollectionNavigationExpression(node, rootParameter, sourceMapping, _navigationExpandingVisitor, _queryCompilationContext); return new IncludeExpression(caller, included, node.Navigation); } diff --git a/src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpandingVisitor.cs b/src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpandingVisitor.cs index a8f875c68dd..b63f43741e6 100644 --- a/src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpandingVisitor.cs +++ b/src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpandingVisitor.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; using System.Linq.Expressions; using System.Reflection; @@ -14,13 +15,15 @@ namespace Microsoft.EntityFrameworkCore.Query.NavigationExpansion.Visitors { public partial class NavigationExpandingVisitor : ExpressionVisitor { - private readonly IModel _model; + private readonly QueryCompilationContext _queryCompilationContext; - public NavigationExpandingVisitor(IModel model) + public NavigationExpandingVisitor(QueryCompilationContext queryCompilationContext) { - _model = model; + _queryCompilationContext = queryCompilationContext; } + public virtual List AppliedQueryFilters { get; } = new List(); + protected override Expression VisitExtension(Expression extensionExpression) { if (extensionExpression is NavigationBindingExpression navigationBindingExpression) @@ -48,6 +51,14 @@ protected override Expression VisitExtension(Expression extensionExpression) return navigationExpansionExpression; } + if (extensionExpression is MaterializeCollectionNavigationExpression materializeCollectionNavigationExpression) + { + var newOperand = VisitSourceExpression(materializeCollectionNavigationExpression.Operand); + newOperand.State.MaterializeCollectionNavigation = materializeCollectionNavigationExpression.Navigation; + + return new NavigationExpansionExpression(newOperand.Operand, newOperand.State, materializeCollectionNavigationExpression.Type); + } + return base.VisitExtension(extensionExpression); } @@ -165,7 +176,7 @@ private Expression ProcessMemberPushdown( materializeCollectionNavigation: null); var rewrittenNavigationExpansionExpression = new NavigationExpansionExpression(navigationExpansionExpression.Operand, rewrittenState, combinedKeySelectorBody.Type); - var inner = new NavigationExpansionReducingVisitor().Visit(rewrittenNavigationExpansionExpression); + var inner = new NavigationExpansionReducingVisitor(this, _queryCompilationContext).Visit(rewrittenNavigationExpansionExpression); var predicate = Expression.Lambda( Expression.Equal(outerKeyAccess, inner), @@ -179,7 +190,7 @@ private Expression ProcessMemberPushdown( var entityType = lastNavigation.ForeignKey.DeclaringEntityType; - return NavigationExpansionHelpers.CreateNavigationExpansionRoot(rewritten, entityType, materializeCollectionNavigation: null); + return NavigationExpansionHelpers.CreateNavigationExpansionRoot(rewritten, entityType, materializeCollectionNavigation: null, this, _queryCompilationContext); } else { @@ -246,7 +257,7 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) if (binaryExpression.Left is MemberExpression leftMember && leftMember.Type.TryGetSequenceType() is Type leftSequenceType && leftSequenceType != null - && _model.FindEntityType(leftMember.Expression.Type) is IEntityType leftParentEntityType) + && _queryCompilationContext.Model.FindEntityType(leftMember.Expression.Type) is IEntityType leftParentEntityType) { leftNavigation = leftParentEntityType.FindNavigation(leftMember.Member.Name); if (leftNavigation != null) @@ -258,7 +269,7 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) if (binaryExpression.Right is MemberExpression rightMember && rightMember.Type.TryGetSequenceType() is Type rightSequenceType && rightSequenceType != null - && _model.FindEntityType(rightMember.Expression.Type) is IEntityType rightParentEntityType) + && _queryCompilationContext.Model.FindEntityType(rightMember.Expression.Type) is IEntityType rightParentEntityType) { rightNavigation = rightParentEntityType.FindNavigation(rightMember.Member.Name); if (rightNavigation != null) diff --git a/src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpandingVisitor_MethodCall.cs b/src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpandingVisitor_MethodCall.cs index bd580abbd18..020e170636c 100644 --- a/src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpandingVisitor_MethodCall.cs +++ b/src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpandingVisitor_MethodCall.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Xml; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -190,7 +189,7 @@ private Expression ProcessWhere(MethodCallExpression methodCallExpression) AdjustCurrentParameterName(source.State, predicate.Parameters[0].Name); var appliedNavigationsResult = FindAndApplyNavigations(source.Operand, predicate, source.State); - var newPredicateBody = new NavigationPropertyUnbindingVisitor(appliedNavigationsResult.state.CurrentParameter).Visit(appliedNavigationsResult.lambdaBody); + var newPredicateBody = new NavigationPropertyUnbindingVisitor(appliedNavigationsResult.state.CurrentParameter, this, _queryCompilationContext).Visit(appliedNavigationsResult.lambdaBody); var newPredicateLambda = Expression.Lambda(newPredicateBody, appliedNavigationsResult.state.CurrentParameter); var appliedOrderingsResult = ApplyPendingOrderings(appliedNavigationsResult.source, appliedNavigationsResult.state); @@ -321,7 +320,7 @@ private Expression ProcessSelectMany(MethodCallExpression methodCallExpression) collectionSelectorLambdaBody, outerState.CurrentParameter); - newCollectionSelectorLambda = (LambdaExpression)new NavigationPropertyUnbindingVisitor(outerState.CurrentParameter).Visit(newCollectionSelectorLambda); + newCollectionSelectorLambda = (LambdaExpression)new NavigationPropertyUnbindingVisitor(outerState.CurrentParameter, this, _queryCompilationContext).Visit(newCollectionSelectorLambda); if (methodCallExpression.Method.MethodIsClosedFormOf(LinqMethodHelpers.QueryableSelectManyMethodInfo)) { @@ -528,8 +527,8 @@ private Expression ProcessJoin(MethodCallExpression methodCallExpression) var outerApplyNavigationsResult = FindAndApplyNavigations(outerSource.Operand, outerKeySelector, outerSource.State); var innerApplyNavigationsResult = FindAndApplyNavigations(innerSource.Operand, innerKeySelector, innerSource.State); - var newOuterKeySelectorBody = new NavigationPropertyUnbindingVisitor(outerApplyNavigationsResult.state.CurrentParameter).Visit(outerApplyNavigationsResult.lambdaBody); - var newInnerKeySelectorBody = new NavigationPropertyUnbindingVisitor(innerApplyNavigationsResult.state.CurrentParameter).Visit(innerApplyNavigationsResult.lambdaBody); + var newOuterKeySelectorBody = new NavigationPropertyUnbindingVisitor(outerApplyNavigationsResult.state.CurrentParameter, this, _queryCompilationContext).Visit(outerApplyNavigationsResult.lambdaBody); + var newInnerKeySelectorBody = new NavigationPropertyUnbindingVisitor(innerApplyNavigationsResult.state.CurrentParameter, this, _queryCompilationContext).Visit(innerApplyNavigationsResult.lambdaBody); var outerApplyOrderingsResult = ApplyPendingOrderings(outerApplyNavigationsResult.source, outerApplyNavigationsResult.state); var innerApplyOrderingsResult = ApplyPendingOrderings(innerApplyNavigationsResult.source, innerApplyNavigationsResult.state); @@ -577,8 +576,8 @@ private Expression ProcessGroupJoin(MethodCallExpression methodCallExpression) var outerApplyNavigationsResult = FindAndApplyNavigations(outerSource.Operand, outerKeySelector, outerSource.State); var innerApplyNavigationsResult = FindAndApplyNavigations(innerSource.Operand, innerKeySelector, innerSource.State); - var newOuterKeySelectorBody = new NavigationPropertyUnbindingVisitor(outerApplyNavigationsResult.state.CurrentParameter).Visit(outerApplyNavigationsResult.lambdaBody); - var newInnerKeySelectorBody = new NavigationPropertyUnbindingVisitor(innerApplyNavigationsResult.state.CurrentParameter).Visit(innerApplyNavigationsResult.lambdaBody); + var newOuterKeySelectorBody = new NavigationPropertyUnbindingVisitor(outerApplyNavigationsResult.state.CurrentParameter, this, _queryCompilationContext).Visit(outerApplyNavigationsResult.lambdaBody); + var newInnerKeySelectorBody = new NavigationPropertyUnbindingVisitor(innerApplyNavigationsResult.state.CurrentParameter, this, _queryCompilationContext).Visit(innerApplyNavigationsResult.lambdaBody); var outerApplyOrderingsResult = ApplyPendingOrderings(outerApplyNavigationsResult.source, outerApplyNavigationsResult.state); var innerApplyOrderingsResult = ApplyPendingOrderings(innerApplyNavigationsResult.source, innerApplyNavigationsResult.state); @@ -683,7 +682,7 @@ private Expression ProcessAll(MethodCallExpression methodCallExpression) AdjustCurrentParameterName(source.State, predicate.Parameters[0].Name); var applyNavigationsResult = FindAndApplyNavigations(source.Operand, predicate, source.State); - var newPredicateBody = new NavigationPropertyUnbindingVisitor(applyNavigationsResult.state.CurrentParameter).Visit(applyNavigationsResult.lambdaBody); + var newPredicateBody = new NavigationPropertyUnbindingVisitor(applyNavigationsResult.state.CurrentParameter, this, _queryCompilationContext).Visit(applyNavigationsResult.lambdaBody); var applyOrderingsResult = ApplyPendingOrderings(applyNavigationsResult.source, applyNavigationsResult.state); var newMethodInfo = methodCallExpression.Method.GetGenericMethodDefinition().MakeGenericMethod(applyOrderingsResult.state.CurrentParameter.Type); @@ -757,7 +756,7 @@ private Expression ProcessAverageSumMinMax(MethodCallExpression methodCallExpres var selector = methodCallExpression.Arguments[1].UnwrapLambdaFromQuote(); AdjustCurrentParameterName(source.State, selector.Parameters[0].Name); var applyNavigationsResult = FindAndApplyNavigations(source.Operand, selector, source.State); - var newSelectorBody = new NavigationPropertyUnbindingVisitor(applyNavigationsResult.state.CurrentParameter).Visit(applyNavigationsResult.lambdaBody); + var newSelectorBody = new NavigationPropertyUnbindingVisitor(applyNavigationsResult.state.CurrentParameter, this, _queryCompilationContext).Visit(applyNavigationsResult.lambdaBody); var newSelector = Expression.Lambda(newSelectorBody, applyNavigationsResult.state.CurrentParameter); var applyOrderingsResult = ApplyPendingOrderings(applyNavigationsResult.source, applyNavigationsResult.state); @@ -818,7 +817,7 @@ private Expression ProcessOfType(MethodCallExpression methodCallExpression) { var source = VisitSourceExpression(methodCallExpression.Arguments[0]); var preProcessResult = PreProcessTerminatingOperation(source); - var newEntityType = _model.FindEntityType(methodCallExpression.Method.GetGenericArguments()[0]); + var newEntityType = _queryCompilationContext.Model.FindEntityType(methodCallExpression.Method.GetGenericArguments()[0]); // TODO: possible small optimization - only apply this if newEntityType is different than the old one if (newEntityType != null) @@ -859,7 +858,7 @@ private Expression ProcessSkipTake(MethodCallExpression methodCallExpression) if (applyOrderingsResult.state.ApplyPendingSelector) { - var unbinder = new NavigationPropertyUnbindingVisitor(applyOrderingsResult.state.CurrentParameter); + var unbinder = new NavigationPropertyUnbindingVisitor(applyOrderingsResult.state.CurrentParameter, this, _queryCompilationContext); var newSelectorBody = unbinder.Visit(applyOrderingsResult.state.PendingSelector.Body); var pssmg = new PendingSelectorSourceMappingGenerator(applyOrderingsResult.state.PendingSelector.Parameters[0], null); @@ -1268,9 +1267,9 @@ protected override Expression VisitConstant(ConstantExpression constantExpressio && constantExpression.Value.GetType().GetGenericTypeDefinition() == typeof(EntityQueryable<>)) { var elementType = constantExpression.Value.GetType().GetSequenceType(); - var entityType = _model.FindEntityType(elementType); + var entityType = _queryCompilationContext.Model.FindEntityType(elementType); - return NavigationExpansionHelpers.CreateNavigationExpansionRoot(constantExpression, entityType, materializeCollectionNavigation: null); + return NavigationExpansionHelpers.CreateNavigationExpansionRoot(constantExpression, entityType, materializeCollectionNavigation: null, this, _queryCompilationContext); } return base.VisitConstant(constantExpression); @@ -1281,7 +1280,7 @@ protected override Expression VisitConstant(ConstantExpression constantExpressio foreach (var pendingOrdering in state.PendingOrderings) { var remappedKeySelectorBody = new ExpressionReplacingVisitor(pendingOrdering.keySelector.Parameters[0], state.CurrentParameter).Visit(pendingOrdering.keySelector.Body); - var newSelectorBody = new NavigationPropertyUnbindingVisitor(state.CurrentParameter).Visit(remappedKeySelectorBody); + var newSelectorBody = new NavigationPropertyUnbindingVisitor(state.CurrentParameter, this, _queryCompilationContext).Visit(remappedKeySelectorBody); var newSelector = Expression.Lambda(newSelectorBody, state.CurrentParameter); var orderingMethod = pendingOrdering.method.MakeGenericMethod(state.CurrentParameter.Type, newSelectorBody.Type); source = Expression.Call(orderingMethod, source, newSelector); @@ -1311,7 +1310,7 @@ protected override Expression VisitConstant(ConstantExpression constantExpressio var boundLambdaBody = binder.Visit(remappedLambdaBody); boundLambdaBody = new NavigationComparisonOptimizingVisitor().Visit(boundLambdaBody); - boundLambdaBody = new CollectionNavigationRewritingVisitor(state.CurrentParameter).Visit(boundLambdaBody); + boundLambdaBody = new CollectionNavigationRewritingVisitor(state.CurrentParameter, this, _queryCompilationContext).Visit(boundLambdaBody); boundLambdaBody = Visit(boundLambdaBody); var result = (source, parameter: state.CurrentParameter); @@ -1330,7 +1329,9 @@ protected override Expression VisitConstant(ConstantExpression constantExpressio navigationTree, state, new List(), - include: false); + include: false, + this, + _queryCompilationContext); } applyPendingSelector = true; diff --git a/src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpansionReducingVisitor.cs b/src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpansionReducingVisitor.cs index c0f601d449d..611da01aafd 100644 --- a/src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpansionReducingVisitor.cs +++ b/src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpansionReducingVisitor.cs @@ -9,11 +9,21 @@ using System.Reflection; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Query.Pipeline; namespace Microsoft.EntityFrameworkCore.Query.NavigationExpansion.Visitors { public class NavigationExpansionReducingVisitor : ExpressionVisitor { + private readonly NavigationExpandingVisitor _navigationExpandingVisitor; + private readonly QueryCompilationContext _queryCompilationContext; + + public NavigationExpansionReducingVisitor(NavigationExpandingVisitor navigationExpandingVisitor, QueryCompilationContext queryCompilationContext) + { + _navigationExpandingVisitor = navigationExpandingVisitor; + _queryCompilationContext = queryCompilationContext; + } + protected override Expression VisitExtension(Expression extensionExpression) { if (extensionExpression is NavigationBindingExpression navigationBindingExpression) @@ -48,7 +58,7 @@ protected override Expression VisitExtension(Expression extensionExpression) foreach (var pendingOrdering in state.PendingOrderings) { var remappedKeySelectorBody = new ExpressionReplacingVisitor(pendingOrdering.keySelector.Parameters[0], state.CurrentParameter).Visit(pendingOrdering.keySelector.Body); - var newSelectorBody = new NavigationPropertyUnbindingVisitor(state.CurrentParameter).Visit(remappedKeySelectorBody); + var newSelectorBody = new NavigationPropertyUnbindingVisitor(state.CurrentParameter, _navigationExpandingVisitor, _queryCompilationContext).Visit(remappedKeySelectorBody); var newSelector = Expression.Lambda(newSelectorBody, state.CurrentParameter); var orderingMethod = pendingOrdering.method.MakeGenericMethod(state.CurrentParameter.Type, newSelectorBody.Type); result = Expression.Call(orderingMethod, result, newSelector); @@ -56,7 +66,7 @@ protected override Expression VisitExtension(Expression extensionExpression) if (state.ApplyPendingSelector) { - var pendingSelector = (LambdaExpression)new NavigationPropertyUnbindingVisitor(state.CurrentParameter).Visit(state.PendingSelector); + var pendingSelector = (LambdaExpression)new NavigationPropertyUnbindingVisitor(state.CurrentParameter, _navigationExpandingVisitor, _queryCompilationContext).Visit(state.PendingSelector); var pendingSelectorBodyType = pendingSelector.Type.GetGenericArguments()[1]; var pendingSelectMathod = result.Type.IsGenericType && (result.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>) || result.Type.GetGenericTypeDefinition() == typeof(IOrderedEnumerable<>)) @@ -122,7 +132,7 @@ protected override Expression VisitExtension(Expression extensionExpression) var includeFinder = new PendingIncludeFindingVisitor(); includeFinder.Visit(navigationExpansionExpression.State.PendingSelector.Body); - var includeRewriter = new PendingSelectorIncludeRewriter(); + var includeRewriter = new PendingSelectorIncludeRewriter(_navigationExpandingVisitor, _queryCompilationContext); var rewrittenBody = includeRewriter.Visit(navigationExpansionExpression.State.PendingSelector.Body); if (navigationExpansionExpression.State.PendingSelector.Body != rewrittenBody) @@ -143,7 +153,9 @@ protected override Expression VisitExtension(Expression extensionExpression) pendingIncludeNode.Key, navigationExpansionExpression.State, new List(), - include: true); + include: true, + _navigationExpandingVisitor, + _queryCompilationContext); } var pendingSelector = navigationExpansionExpression.State.PendingSelector; diff --git a/src/EFCore/Query/NavigationExpansion/Visitors/NavigationPropertyUnbindingVisitor.cs b/src/EFCore/Query/NavigationExpansion/Visitors/NavigationPropertyUnbindingVisitor.cs index 07d37d93a3d..5293ae8e1bf 100644 --- a/src/EFCore/Query/NavigationExpansion/Visitors/NavigationPropertyUnbindingVisitor.cs +++ b/src/EFCore/Query/NavigationExpansion/Visitors/NavigationPropertyUnbindingVisitor.cs @@ -3,16 +3,24 @@ using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Query.Pipeline; namespace Microsoft.EntityFrameworkCore.Query.NavigationExpansion.Visitors { public class NavigationPropertyUnbindingVisitor : ExpressionVisitor { private readonly ParameterExpression _rootParameter; + private readonly NavigationExpandingVisitor _navigationExpandingVisitor; + private readonly QueryCompilationContext _queryCompilationContext; - public NavigationPropertyUnbindingVisitor(ParameterExpression rootParameter) + public NavigationPropertyUnbindingVisitor( + ParameterExpression rootParameter, + NavigationExpandingVisitor navigationExpandingVisitor, + QueryCompilationContext queryCompilationContext) { _rootParameter = rootParameter; + _navigationExpandingVisitor = navigationExpandingVisitor; + _queryCompilationContext = queryCompilationContext; } protected override Expression VisitExtension(Expression extensionExpression) @@ -40,7 +48,7 @@ protected override Expression VisitExtension(Expression extensionExpression) if (extensionExpression is NavigationExpansionRootExpression || extensionExpression is NavigationExpansionExpression) { - var result = new NavigationExpansionReducingVisitor().Visit(extensionExpression); + var result = new NavigationExpansionReducingVisitor(_navigationExpandingVisitor, _queryCompilationContext).Visit(extensionExpression); return Visit(result); } diff --git a/src/EFCore/Query/Pipeline/QueryCompilationContext.cs b/src/EFCore/Query/Pipeline/QueryCompilationContext.cs index 067076c4481..11b7b1ff763 100644 --- a/src/EFCore/Query/Pipeline/QueryCompilationContext.cs +++ b/src/EFCore/Query/Pipeline/QueryCompilationContext.cs @@ -2,7 +2,11 @@ // 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; +using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -19,6 +23,8 @@ public class QueryCompilationContext private readonly IShapedQueryOptimizerFactory _shapedQueryOptimizerFactory; private readonly IShapedQueryCompilingExpressionVisitorFactory _shapedQueryCompilingExpressionVisitorFactory; + private readonly Parameters _parameters; + public QueryCompilationContext( IModel model, IQueryOptimizerFactory queryOptimizerFactory, @@ -28,6 +34,7 @@ public QueryCompilationContext( ICurrentDbContext currentDbContext, IDbContextOptions contextOptions, IDiagnosticsLogger logger, + IEvaluatableExpressionFilter evaluatableExpressionFilter, bool async) { Async = async; @@ -36,21 +43,28 @@ public QueryCompilationContext( ContextOptions = contextOptions; ContextType = currentDbContext.Context.GetType(); Logger = logger; + EvaluatableExpressionFilter = evaluatableExpressionFilter; _queryOptimizerFactory = queryOptimizerFactory; _queryableMethodTranslatingExpressionVisitorFactory = queryableMethodTranslatingExpressionVisitorFactory; _shapedQueryOptimizerFactory = shapedQueryOptimizerFactory; _shapedQueryCompilingExpressionVisitorFactory = shapedQueryCompilingExpressionVisitorFactory; + _parameters = new Parameters(); } public bool Async { get; } public IModel Model { get; } public IDbContextOptions ContextOptions { get; } public bool TrackQueryResults { get; internal set; } + public bool IgnoreQueryFilters { get; internal set; } public virtual IDiagnosticsLogger Logger { get; } public virtual Type ContextType { get; } + public virtual IEvaluatableExpressionFilter EvaluatableExpressionFilter { get; set; } + + internal virtual IParameterValues ParameterValues => _parameters; + public virtual Func CreateQueryExecutor(Expression query) { query = _queryOptimizerFactory.Create(this).Visit(query); @@ -62,6 +76,16 @@ public virtual Func CreateQueryExecutor(Expressi // Inject tracking query = _shapedQueryCompilingExpressionVisitorFactory.Create(this).Visit(query); + var setFilterParameterExpressions + = CreateSetFilterParametersExpressions(out var contextVariableExpression); + + if (setFilterParameterExpressions != null) + { + query = Expression.Block( + new[] { contextVariableExpression }, + setFilterParameterExpressions.Concat(new[] { query })); + } + var queryExecutorExpression = Expression.Lambda>( query, QueryContextParameter); @@ -75,5 +99,80 @@ public virtual Func CreateQueryExecutor(Expressi Logger.QueryExecutionPlanned(new ExpressionPrinter(), queryExecutorExpression); } } + + private static readonly MethodInfo _queryContextAddParameterMethodInfo + = typeof(QueryContext) + .GetTypeInfo() + .GetDeclaredMethod(nameof(QueryContext.AddParameter)); + + private static readonly PropertyInfo _queryContextContextPropertyInfo + = typeof(QueryContext) + .GetTypeInfo() + .GetDeclaredProperty(nameof(QueryContext.Context)); + + private IEnumerable CreateSetFilterParametersExpressions(out ParameterExpression contextVariableExpression) + { + contextVariableExpression = null; + + if (_parameters.ParameterValues.Count == 0) + { + return null; + } + + contextVariableExpression = Expression.Variable(ContextType, "context"); + + var blockExpressions + = new List + { + Expression.Assign( + contextVariableExpression, + Expression.Convert( + Expression.Property( + QueryContextParameter, + _queryContextContextPropertyInfo), + ContextType)) + }; + + foreach (var keyValuePair in _parameters.ParameterValues) + { + blockExpressions.Add( + Expression.Call( + QueryContextParameter, + _queryContextAddParameterMethodInfo, + Expression.Constant(keyValuePair.Key), + Expression.Convert( + Expression.Invoke( + (LambdaExpression)keyValuePair.Value, + contextVariableExpression), + typeof(object)))); + } + + return blockExpressions; + } + + private class Parameters : IParameterValues + { + private readonly IDictionary _parameterValues = new Dictionary(); + + public IReadOnlyDictionary ParameterValues => (IReadOnlyDictionary)_parameterValues; + + public virtual void AddParameter(string name, object value) + { + _parameterValues.Add(name, value); + } + + public virtual void SetParameter(string name, object value) + { + _parameterValues[name] = value; + } + + public virtual object RemoveParameter(string name) + { + var value = _parameterValues[name]; + _parameterValues.Remove(name); + + return value; + } + } } } diff --git a/src/EFCore/Query/Pipeline/QueryCompilationContextFactory.cs b/src/EFCore/Query/Pipeline/QueryCompilationContextFactory.cs index a4d26f44b55..04b4408e88f 100644 --- a/src/EFCore/Query/Pipeline/QueryCompilationContextFactory.cs +++ b/src/EFCore/Query/Pipeline/QueryCompilationContextFactory.cs @@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Query.Internal; namespace Microsoft.EntityFrameworkCore.Query.Pipeline { @@ -17,6 +18,7 @@ public class QueryCompilationContextFactory : IQueryCompilationContextFactory private readonly ICurrentDbContext _currentDbContext; private readonly IDbContextOptions _contextOptions; private readonly IDiagnosticsLogger _logger; + private readonly IEvaluatableExpressionFilter _evaluatableExpressionFilter; public QueryCompilationContextFactory( IModel model, @@ -26,7 +28,8 @@ public QueryCompilationContextFactory( IShapedQueryCompilingExpressionVisitorFactory shapedQueryCompilingExpressionVisitorFactory, ICurrentDbContext currentDbContext, IDbContextOptions contextOptions, - IDiagnosticsLogger logger) + IDiagnosticsLogger logger, + IEvaluatableExpressionFilter evaluatableExpressionFilter) { _model = model; _queryOptimizerFactory = queryOptimizerFactory; @@ -36,6 +39,7 @@ public QueryCompilationContextFactory( _currentDbContext = currentDbContext; _contextOptions = contextOptions; _logger = logger; + _evaluatableExpressionFilter = evaluatableExpressionFilter; } public QueryCompilationContext Create(bool async) @@ -49,6 +53,7 @@ public QueryCompilationContext Create(bool async) _currentDbContext, _contextOptions, _logger, + _evaluatableExpressionFilter, async); return queryCompilationContext; diff --git a/src/EFCore/Query/Pipeline/QueryMetadataExtractingExpressionVisitor.cs b/src/EFCore/Query/Pipeline/QueryMetadataExtractingExpressionVisitor.cs index 56210bc880c..adeb92cfe50 100644 --- a/src/EFCore/Query/Pipeline/QueryMetadataExtractingExpressionVisitor.cs +++ b/src/EFCore/Query/Pipeline/QueryMetadataExtractingExpressionVisitor.cs @@ -30,6 +30,15 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp return innerQueryable; } + + if (genericMethodDefinition == EntityFrameworkQueryableExtensions.IgnoreQueryFiltersMethodInfo) + { + var innerQueryable = Visit(methodCallExpression.Arguments[0]); + + _queryCompilationContext.IgnoreQueryFilters = true; + + return innerQueryable; + } } return base.VisitMethodCall(methodCallExpression); diff --git a/src/EFCore/Query/Pipeline/QueryOptimizingExpressionVisitor.cs b/src/EFCore/Query/Pipeline/QueryOptimizingExpressionVisitor.cs index df95a96b8fa..f2c171e459b 100644 --- a/src/EFCore/Query/Pipeline/QueryOptimizingExpressionVisitor.cs +++ b/src/EFCore/Query/Pipeline/QueryOptimizingExpressionVisitor.cs @@ -19,13 +19,13 @@ public QueryOptimizer(QueryCompilationContext queryCompilationContext) public Expression Visit(Expression query) { + query = new QueryMetadataExtractingExpressionVisitor(_queryCompilationContext).Visit(query); query = new AllAnyToContainsRewritingExpressionVisitor().Visit(query); query = new GroupJoinFlatteningExpressionVisitor().Visit(query); query = new NullCheckRemovingExpressionVisitor().Visit(query); query = new EntityEqualityRewritingExpressionVisitor(_queryCompilationContext).Rewrite(query); - query = new NavigationExpander(_queryCompilationContext.Model).ExpandNavigations(query); + query = new NavigationExpander(_queryCompilationContext).ExpandNavigations(query); query = new EnumerableToQueryableReMappingExpressionVisitor().Visit(query); - query = new QueryMetadataExtractingExpressionVisitor(_queryCompilationContext).Visit(query); query = new NullCheckRemovingExpressionVisitor().Visit(query); query = new FunctionPreprocessingVisitor().Visit(query); new EnumerableVerifyingExpressionVisitor().Visit(query); diff --git a/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs b/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs index e6c4919e0bc..c4cb462bd6c 100644 --- a/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs @@ -19,10 +19,10 @@ public class InMemoryComplianceTest : ComplianceTestBase typeof(GraphUpdatesTestBase<>), // issue #15318 typeof(ProxyGraphUpdatesTestBase<>), // issue #15318 typeof(ComplexNavigationsWeakQueryTestBase<>), // issue #15285 - typeof(FiltersInheritanceTestBase<>), // issue #15264 - typeof(FiltersTestBase<>), // issue #15264 typeof(OwnedQueryTestBase<>), // issue #15285 // Query pipeline + typeof(FiltersInheritanceTestBase<>), + typeof(FiltersTestBase<>), typeof(SimpleQueryTestBase<>), typeof(ConcurrencyDetectorTestBase<>), typeof(AsNoTrackingTestBase<>), diff --git a/test/EFCore.InMemory.FunctionalTests/Query/FiltersInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/FiltersInMemoryTest.cs index 2fed8bd7aec..07e845f8ddd 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/FiltersInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/FiltersInMemoryTest.cs @@ -5,7 +5,6 @@ namespace Microsoft.EntityFrameworkCore.Query { - //issue #15264 internal class FiltersInMemoryTest : FiltersTestBase> { public FiltersInMemoryTest(NorthwindQueryInMemoryFixture fixture, ITestOutputHelper testOutputHelper) diff --git a/test/EFCore.InMemory.FunctionalTests/Query/FiltersInheritanceInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/FiltersInheritanceInMemoryTest.cs index 12dd0ad5cb7..5a3ae0a1c50 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/FiltersInheritanceInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/FiltersInheritanceInMemoryTest.cs @@ -3,7 +3,6 @@ namespace Microsoft.EntityFrameworkCore.Query { - // issue #15264 internal class FiltersInheritanceInMemoryTest : FiltersInheritanceTestBase { public FiltersInheritanceInMemoryTest(FiltersInheritanceInMemoryFixture fixture) diff --git a/test/EFCore.Specification.Tests/Query/FiltersInheritanceTestBase.cs b/test/EFCore.Specification.Tests/Query/FiltersInheritanceTestBase.cs index f067175b188..058de68dc43 100644 --- a/test/EFCore.Specification.Tests/Query/FiltersInheritanceTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/FiltersInheritanceTestBase.cs @@ -18,7 +18,7 @@ public abstract class FiltersInheritanceTestBase : IClassFixture("ALFKI")); } - // also issue #15264 [ConditionalFact(Skip = "Issue #14935. Cannot eval 'where ClientMethod([p])'")] public virtual void Client_eval() { Assert.Equal(69, _context.Products.ToList().Count); } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual async Task Materialized_query_async() { Assert.Equal(7, (await _context.Customers.ToListAsync()).Count); } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void Materialized_query_parameter() { _context.TenantPrefix = "F"; @@ -68,7 +66,7 @@ public virtual void Materialized_query_parameter() Assert.Equal(8, _context.Customers.ToList().Count); } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void Materialized_query_parameter_new_context() { Assert.Equal(7, _context.Customers.ToList().Count); @@ -81,13 +79,13 @@ public virtual void Materialized_query_parameter_new_context() } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void Projection_query() { Assert.Equal(7, _context.Customers.Select(c => c.CustomerID).ToList().Count); } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void Projection_query_parameter() { _context.TenantPrefix = "F"; @@ -95,7 +93,7 @@ public virtual void Projection_query_parameter() Assert.Equal(8, _context.Customers.Select(c => c.CustomerID).ToList().Count); } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void Include_query() { var results = _context.Customers.Include(c => c.Orders).ToList(); @@ -103,7 +101,7 @@ public virtual void Include_query() Assert.Equal(7, results.Count); } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void Include_query_opt_out() { var results = _context.Customers.Include(c => c.Orders).IgnoreQueryFilters().ToList(); @@ -111,16 +109,24 @@ public virtual void Include_query_opt_out() Assert.Equal(91, results.Count); } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void Included_many_to_one_query() { var results = _context.Orders.Include(o => o.Customer).ToList(); - Assert.Equal(830, results.Count); + Assert.Equal(80, results.Count); + Assert.True(results.All(o => o.Customer == null || o.CustomerID.StartsWith("B"))); + } + + [ConditionalFact] + public virtual void Project_reference_that_itself_has_query_filter_with_another_reference() + { + var results = _context.OrderDetails.Select(od => od.Order).ToList(); + + Assert.Equal(5, results.Count); Assert.True(results.All(o => o.Customer == null || o.CustomerID.StartsWith("B"))); } - // also issue #15264 [ConditionalFact(Skip = "Issue #14935. Cannot eval 'where ClientMethod([p])'")] public virtual void Included_one_to_many_query_with_client_eval() { @@ -133,7 +139,7 @@ public virtual void Included_one_to_many_query_with_client_eval() || p.OrderDetails.All(od => od.Quantity > 50))); } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact(Skip = "issue #16293")] public virtual void Navs_query() { var results @@ -146,7 +152,7 @@ where od.Discount < 10 Assert.Equal(5, results.Count); } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact(Skip = "issue #14551")] public virtual void Compiled_query() { var query = EF.CompileQuery( diff --git a/test/EFCore.Specification.Tests/Query/QueryFilterFuncletizationTestBase.cs b/test/EFCore.Specification.Tests/Query/QueryFilterFuncletizationTestBase.cs index 7e6501552e5..d5376a21f1b 100644 --- a/test/EFCore.Specification.Tests/Query/QueryFilterFuncletizationTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/QueryFilterFuncletizationTestBase.cs @@ -21,7 +21,7 @@ public abstract class QueryFilterFuncletizationTestBase : IClassFixtur protected QueryFilterFuncletizationContext CreateContext() => Fixture.CreateContext(); - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void DbContext_property_parameter_does_not_clash_with_closure_parameter_name() { using (var context = CreateContext()) @@ -31,7 +31,7 @@ public virtual void DbContext_property_parameter_does_not_clash_with_closure_par } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void DbContext_field_is_parameterized() { using (var context = CreateContext()) @@ -46,7 +46,7 @@ public virtual void DbContext_field_is_parameterized() } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void DbContext_property_is_parameterized() { using (var context = CreateContext()) @@ -61,7 +61,7 @@ public virtual void DbContext_property_is_parameterized() } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void DbContext_method_call_is_parameterized() { using (var context = CreateContext()) @@ -71,14 +71,14 @@ public virtual void DbContext_method_call_is_parameterized() } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void DbContext_list_is_parameterized() { using (var context = CreateContext()) { // This throws because the default value of TenantIds is null which is NRE var exception = Record.Exception(() => context.Set().ToList()); - Assert.True(exception is InvalidOperationException || exception is ArgumentNullException); + Assert.True(exception is InvalidOperationException || exception is ArgumentNullException || exception is NullReferenceException); context.TenantIds = new List(); var query = context.Set().ToList(); @@ -101,7 +101,7 @@ public virtual void DbContext_list_is_parameterized() } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void DbContext_property_chain_is_parameterized() { using (var context = CreateContext()) @@ -125,7 +125,7 @@ public virtual void DbContext_property_chain_is_parameterized() } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void DbContext_property_method_call_is_parameterized() { using (var context = CreateContext()) @@ -139,7 +139,7 @@ public virtual void DbContext_property_method_call_is_parameterized() } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void DbContext_method_call_chain_is_parameterized() { using (var context = CreateContext()) @@ -149,7 +149,7 @@ public virtual void DbContext_method_call_chain_is_parameterized() } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void DbContext_complex_expression_is_parameterized() { using (var context = CreateContext()) @@ -167,7 +167,7 @@ public virtual void DbContext_complex_expression_is_parameterized() } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void DbContext_property_based_filter_does_not_short_circuit() { using (var context = CreateContext()) @@ -184,7 +184,7 @@ public virtual void DbContext_property_based_filter_does_not_short_circuit() } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void EntityTypeConfiguration_DbContext_field_is_parameterized() { using (var context = CreateContext()) @@ -199,7 +199,7 @@ public virtual void EntityTypeConfiguration_DbContext_field_is_parameterized() } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void EntityTypeConfiguration_DbContext_property_is_parameterized() { using (var context = CreateContext()) @@ -214,7 +214,7 @@ public virtual void EntityTypeConfiguration_DbContext_property_is_parameterized( } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void EntityTypeConfiguration_DbContext_method_call_is_parameterized() { using (var context = CreateContext()) @@ -224,7 +224,7 @@ public virtual void EntityTypeConfiguration_DbContext_method_call_is_parameteriz } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void EntityTypeConfiguration_DbContext_property_chain_is_parameterized() { using (var context = CreateContext()) @@ -248,7 +248,7 @@ public virtual void EntityTypeConfiguration_DbContext_property_chain_is_paramete } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void Local_method_DbContext_field_is_parameterized() { using (var context = CreateContext()) @@ -263,7 +263,7 @@ public virtual void Local_method_DbContext_field_is_parameterized() } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void Local_static_method_DbContext_property_is_parameterized() { using (var context = CreateContext()) @@ -278,7 +278,7 @@ public virtual void Local_static_method_DbContext_property_is_parameterized() } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void Remote_method_DbContext_property_method_call_is_parameterized() { using (var context = CreateContext()) @@ -292,7 +292,7 @@ public virtual void Remote_method_DbContext_property_method_call_is_parameterize } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void Extension_method_DbContext_field_is_parameterized() { using (var context = CreateContext()) @@ -307,7 +307,7 @@ public virtual void Extension_method_DbContext_field_is_parameterized() } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void Extension_method_DbContext_property_chain_is_parameterized() { using (var context = CreateContext()) @@ -331,7 +331,7 @@ public virtual void Extension_method_DbContext_property_chain_is_parameterized() } } - [ConditionalFact(Skip = "See issue#13587")] + [ConditionalFact(Skip = "See issue#16319")] public virtual void Using_DbSet_in_filter_works() { using (var context = CreateContext()) @@ -340,7 +340,7 @@ public virtual void Using_DbSet_in_filter_works() } } - [ConditionalFact(Skip = "See issue#13587")] + [ConditionalFact(Skip = "See issue#16319")] public virtual void Using_Context_set_method_in_filter_works() { using (var context = CreateContext()) @@ -351,7 +351,7 @@ public virtual void Using_Context_set_method_in_filter_works() } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void Static_member_from_dbContext_is_inlined() { using (var context = CreateContext()) @@ -362,7 +362,7 @@ public virtual void Static_member_from_dbContext_is_inlined() } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void Static_member_from_non_dbContext_is_inlined() { using (var context = CreateContext()) @@ -373,7 +373,7 @@ public virtual void Static_member_from_non_dbContext_is_inlined() } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void Local_variable_from_OnModelCreating_is_inlined() { using (var context = CreateContext()) @@ -384,7 +384,7 @@ public virtual void Local_variable_from_OnModelCreating_is_inlined() } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void Local_variable_from_OnModelCreating_can_throw_exception() { using (var context = CreateContext()) @@ -397,7 +397,7 @@ public virtual void Local_variable_from_OnModelCreating_can_throw_exception() } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void Method_parameter_is_inlined() { using (var context = CreateContext()) @@ -406,7 +406,7 @@ public virtual void Method_parameter_is_inlined() } } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact] public virtual void Using_multiple_context_in_filter_parametrize_only_current_context() { using (var context = CreateContext()) diff --git a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.QueryTypes.cs b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.QueryTypes.cs index b2e6057076b..54e608a6d0f 100644 --- a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.QueryTypes.cs +++ b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.QueryTypes.cs @@ -56,7 +56,7 @@ public virtual void Auto_initialized_view_set() } } - [ConditionalFact(Skip = "Issue#15264")] + [ConditionalFact(Skip = "Issue#16323")] public virtual void QueryType_with_nav_defining_query() { using (var context = CreateContext()) @@ -70,7 +70,7 @@ var results } } - [ConditionalTheory(Skip = "Issue#15264")] + [ConditionalTheory(Skip = "Issue#16323")] [MemberData(nameof(IsAsyncData))] public virtual Task QueryType_with_defining_query(bool isAsync) { @@ -90,7 +90,7 @@ public virtual Task QueryType_with_defining_query_and_correlated_collection(bool .Select(cv => cv.Orders.Where(cc => true).ToList())); } - [ConditionalTheory(Skip = "Issue#15264")] + [ConditionalTheory(Skip = "Issue#15711")] [MemberData(nameof(IsAsyncData))] public virtual Task QueryType_with_mixed_tracking(bool isAsync) { @@ -107,7 +107,7 @@ from o in ovs.AsNoTracking().Where(ov => ov.CustomerID == c.CustomerID) e => e.c.CustomerID); } - [ConditionalTheory(Skip = "Issue#15264")] + [ConditionalTheory(Skip = "Issue#16323")] [MemberData(nameof(IsAsyncData))] public virtual Task QueryType_with_included_nav(bool isAsync) { @@ -122,7 +122,7 @@ public virtual Task QueryType_with_included_nav(bool isAsync) }); } - [ConditionalTheory(Skip = "Issue#15264")] + [ConditionalTheory(Skip = "Issue#16323")] [MemberData(nameof(IsAsyncData))] public virtual Task QueryType_with_included_navs_multi_level(bool isAsync) { @@ -138,7 +138,7 @@ public virtual Task QueryType_with_included_navs_multi_level(bool isAsync) }); } - [ConditionalTheory(Skip = "Issue#15264")] + [ConditionalTheory(Skip = "Issue#16323")] [MemberData(nameof(IsAsyncData))] public virtual Task QueryType_select_where_navigation(bool isAsync) { @@ -149,7 +149,7 @@ public virtual Task QueryType_select_where_navigation(bool isAsync) select ov); } - [ConditionalTheory(Skip = "Issue#15264")] + [ConditionalTheory(Skip = "Issue#16323")] [MemberData(nameof(IsAsyncData))] public virtual Task QueryType_select_where_navigation_multi_level(bool isAsync) { diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/FiltersInheritanceSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/FiltersInheritanceSqlServerTest.cs index 42991cac641..3a4d8d02903 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/FiltersInheritanceSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/FiltersInheritanceSqlServerTest.cs @@ -5,8 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query { - // issue #15264 - internal class FiltersInheritanceSqlServerTest : FiltersInheritanceTestBase + public class FiltersInheritanceSqlServerTest : FiltersInheritanceTestBase { public FiltersInheritanceSqlServerTest(FiltersInheritanceSqlServerFixture fixture, ITestOutputHelper testOutputHelper) : base(fixture) @@ -22,7 +21,7 @@ public override void Can_use_of_type_animal() AssertSql( @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] FROM [Animal] AS [a] -WHERE [a].[Discriminator] IN (N'Kiwi', N'Eagle') AND ([a].[CountryId] = 1) +WHERE [a].[Discriminator] IN (N'Eagle', N'Kiwi') AND ([a].[CountryId] = 1) ORDER BY [a].[Species]"); } @@ -33,7 +32,7 @@ public override void Can_use_is_kiwi() AssertSql( @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] FROM [Animal] AS [a] -WHERE ([a].[Discriminator] IN (N'Kiwi', N'Eagle') AND ([a].[CountryId] = 1)) AND ([a].[Discriminator] = N'Kiwi')"); +WHERE ([a].[Discriminator] IN (N'Eagle', N'Kiwi') AND ([a].[CountryId] = 1)) AND ([a].[Discriminator] = N'Kiwi')"); } public override void Can_use_is_kiwi_with_other_predicate() @@ -43,7 +42,7 @@ public override void Can_use_is_kiwi_with_other_predicate() AssertSql( @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] FROM [Animal] AS [a] -WHERE ([a].[Discriminator] IN (N'Kiwi', N'Eagle') AND ([a].[CountryId] = 1)) AND (([a].[Discriminator] = N'Kiwi') AND ([a].[CountryId] = 1))"); +WHERE ([a].[Discriminator] IN (N'Eagle', N'Kiwi') AND ([a].[CountryId] = 1)) AND (([a].[Discriminator] = N'Kiwi') AND ([a].[CountryId] = 1))"); } public override void Can_use_is_kiwi_in_projection() @@ -52,11 +51,11 @@ public override void Can_use_is_kiwi_in_projection() AssertSql( @"SELECT CASE - WHEN [a].[Discriminator] = N'Kiwi' - THEN CAST(1 AS bit) ELSE CAST(0 AS bit) + WHEN [a].[Discriminator] = N'Kiwi' THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) END FROM [Animal] AS [a] -WHERE [a].[Discriminator] IN (N'Kiwi', N'Eagle') AND ([a].[CountryId] = 1)"); +WHERE [a].[Discriminator] IN (N'Eagle', N'Kiwi') AND ([a].[CountryId] = 1)"); } public override void Can_use_of_type_bird() @@ -66,7 +65,7 @@ public override void Can_use_of_type_bird() AssertSql( @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] FROM [Animal] AS [a] -WHERE [a].[Discriminator] IN (N'Kiwi', N'Eagle') AND ([a].[CountryId] = 1) +WHERE ([a].[Discriminator] IN (N'Eagle', N'Kiwi') AND ([a].[CountryId] = 1)) AND [a].[Discriminator] IN (N'Eagle', N'Kiwi') ORDER BY [a].[Species]"); } @@ -77,7 +76,7 @@ public override void Can_use_of_type_bird_predicate() AssertSql( @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] FROM [Animal] AS [a] -WHERE ([a].[Discriminator] IN (N'Kiwi', N'Eagle') AND ([a].[CountryId] = 1)) AND ([a].[CountryId] = 1) +WHERE (([a].[Discriminator] IN (N'Eagle', N'Kiwi') AND ([a].[CountryId] = 1)) AND ([a].[CountryId] = 1)) AND [a].[Discriminator] IN (N'Eagle', N'Kiwi') ORDER BY [a].[Species]"); } @@ -88,7 +87,7 @@ public override void Can_use_of_type_bird_with_projection() AssertSql( @"SELECT [a].[EagleId] FROM [Animal] AS [a] -WHERE [a].[Discriminator] IN (N'Kiwi', N'Eagle') AND ([a].[CountryId] = 1)"); +WHERE ([a].[Discriminator] IN (N'Eagle', N'Kiwi') AND ([a].[CountryId] = 1)) AND [a].[Discriminator] IN (N'Eagle', N'Kiwi')"); } public override void Can_use_of_type_bird_first() @@ -98,7 +97,7 @@ public override void Can_use_of_type_bird_first() AssertSql( @"SELECT TOP(1) [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] FROM [Animal] AS [a] -WHERE [a].[Discriminator] IN (N'Kiwi', N'Eagle') AND ([a].[CountryId] = 1) +WHERE ([a].[Discriminator] IN (N'Eagle', N'Kiwi') AND ([a].[CountryId] = 1)) AND [a].[Discriminator] IN (N'Eagle', N'Kiwi') ORDER BY [a].[Species]"); } @@ -109,7 +108,33 @@ public override void Can_use_of_type_kiwi() AssertSql( @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[FoundOn] FROM [Animal] AS [a] -WHERE ([a].[Discriminator] = N'Kiwi') AND ([a].[CountryId] = 1)"); +WHERE ([a].[Discriminator] IN (N'Eagle', N'Kiwi') AND ([a].[CountryId] = 1)) AND ([a].[Discriminator] = N'Kiwi')"); + } + + public override void Can_use_derived_set() + { + base.Can_use_derived_set(); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group] +FROM [Animal] AS [a] +WHERE ([a].[Discriminator] = N'Eagle') AND ([a].[CountryId] = 1)"); + } + + public override void Can_use_IgnoreQueryFilters_and_GetDatabaseValues() + { + base.Can_use_IgnoreQueryFilters_and_GetDatabaseValues(); + + AssertSql( + @"SELECT TOP(2) [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group] +FROM [Animal] AS [a] +WHERE [a].[Discriminator] = N'Eagle'", + // + @"@__p_0='Aquila chrysaetos canadensis' (Size = 100) + +SELECT TOP(1) [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group] +FROM [Animal] AS [a] +WHERE ([a].[Discriminator] = N'Eagle') AND (([a].[Species] = @__p_0) AND @__p_0 IS NOT NULL)"); } private void AssertSql(params string[] expected) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/FiltersSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/FiltersSqlServerTest.cs index c0f65310956..a8faf1ff40d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/FiltersSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/FiltersSqlServerTest.cs @@ -9,8 +9,7 @@ // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.Query { - //issue #15264 - internal class FiltersSqlServerTest : FiltersTestBase> + public class FiltersSqlServerTest : FiltersTestBase> { public FiltersSqlServerTest(NorthwindQuerySqlServerFixture fixture, ITestOutputHelper testOutputHelper) : base(fixture) @@ -28,7 +27,7 @@ public override void Count_query() SELECT COUNT(*) FROM [Customers] AS [c] -WHERE ([c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0 + N'%' AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) OR (@__ef_filter__TenantPrefix_0 = N'')"); +WHERE ((@__ef_filter__TenantPrefix_0 = N'') AND @__ef_filter__TenantPrefix_0 IS NOT NULL) OR ([c].[CompanyName] IS NOT NULL AND (@__ef_filter__TenantPrefix_0 IS NOT NULL AND (([c].[CompanyName] LIKE [c].[CompanyName] + N'%') AND (((LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0) AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) IS NOT NULL AND @__ef_filter__TenantPrefix_0 IS NOT NULL)) OR (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) IS NULL AND @__ef_filter__TenantPrefix_0 IS NULL)))))"); } public override void Client_eval() @@ -49,7 +48,7 @@ public override void Materialized_query() SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE ([c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0 + N'%' AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) OR (@__ef_filter__TenantPrefix_0 = N'')"); +WHERE ((@__ef_filter__TenantPrefix_0 = N'') AND @__ef_filter__TenantPrefix_0 IS NOT NULL) OR ([c].[CompanyName] IS NOT NULL AND (@__ef_filter__TenantPrefix_0 IS NOT NULL AND (([c].[CompanyName] LIKE [c].[CompanyName] + N'%') AND (((LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0) AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) IS NOT NULL AND @__ef_filter__TenantPrefix_0 IS NOT NULL)) OR (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) IS NULL AND @__ef_filter__TenantPrefix_0 IS NULL)))))"); } public override void Find() @@ -62,7 +61,7 @@ public override void Find() SELECT TOP(1) [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE (([c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0 + N'%' AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) OR (@__ef_filter__TenantPrefix_0 = N'')) AND ([c].[CustomerID] = @__p_0)"); +WHERE (((@__ef_filter__TenantPrefix_0 = N'') AND @__ef_filter__TenantPrefix_0 IS NOT NULL) OR ([c].[CompanyName] IS NOT NULL AND (@__ef_filter__TenantPrefix_0 IS NOT NULL AND (([c].[CompanyName] LIKE [c].[CompanyName] + N'%') AND (((LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0) AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) IS NOT NULL AND @__ef_filter__TenantPrefix_0 IS NOT NULL)) OR (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) IS NULL AND @__ef_filter__TenantPrefix_0 IS NULL)))))) AND (([c].[CustomerID] = @__p_0) AND @__p_0 IS NOT NULL)"); } public override void Materialized_query_parameter() @@ -74,7 +73,7 @@ public override void Materialized_query_parameter() SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE ([c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0 + N'%' AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) OR (@__ef_filter__TenantPrefix_0 = N'')"); +WHERE ((@__ef_filter__TenantPrefix_0 = N'') AND @__ef_filter__TenantPrefix_0 IS NOT NULL) OR ([c].[CompanyName] IS NOT NULL AND (@__ef_filter__TenantPrefix_0 IS NOT NULL AND (([c].[CompanyName] LIKE [c].[CompanyName] + N'%') AND (((LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0) AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) IS NOT NULL AND @__ef_filter__TenantPrefix_0 IS NOT NULL)) OR (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) IS NULL AND @__ef_filter__TenantPrefix_0 IS NULL)))))"); } public override void Materialized_query_parameter_new_context() @@ -86,13 +85,13 @@ public override void Materialized_query_parameter_new_context() SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE ([c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0 + N'%' AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) OR (@__ef_filter__TenantPrefix_0 = N'')", +WHERE ((@__ef_filter__TenantPrefix_0 = N'') AND @__ef_filter__TenantPrefix_0 IS NOT NULL) OR ([c].[CompanyName] IS NOT NULL AND (@__ef_filter__TenantPrefix_0 IS NOT NULL AND (([c].[CompanyName] LIKE [c].[CompanyName] + N'%') AND (((LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0) AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) IS NOT NULL AND @__ef_filter__TenantPrefix_0 IS NOT NULL)) OR (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) IS NULL AND @__ef_filter__TenantPrefix_0 IS NULL)))))", // @"@__ef_filter__TenantPrefix_0='T' (Size = 4000) SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] -WHERE ([c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0 + N'%' AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) OR (@__ef_filter__TenantPrefix_0 = N'')"); +WHERE ((@__ef_filter__TenantPrefix_0 = N'') AND @__ef_filter__TenantPrefix_0 IS NOT NULL) OR ([c].[CompanyName] IS NOT NULL AND (@__ef_filter__TenantPrefix_0 IS NOT NULL AND (([c].[CompanyName] LIKE [c].[CompanyName] + N'%') AND (((LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0) AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) IS NOT NULL AND @__ef_filter__TenantPrefix_0 IS NOT NULL)) OR (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) IS NULL AND @__ef_filter__TenantPrefix_0 IS NULL)))))"); } public override void Projection_query_parameter() @@ -104,7 +103,7 @@ public override void Projection_query_parameter() SELECT [c].[CustomerID] FROM [Customers] AS [c] -WHERE ([c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0 + N'%' AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) OR (@__ef_filter__TenantPrefix_0 = N'')"); +WHERE ((@__ef_filter__TenantPrefix_0 = N'') AND @__ef_filter__TenantPrefix_0 IS NOT NULL) OR ([c].[CompanyName] IS NOT NULL AND (@__ef_filter__TenantPrefix_0 IS NOT NULL AND (([c].[CompanyName] LIKE [c].[CompanyName] + N'%') AND (((LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0) AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) IS NOT NULL AND @__ef_filter__TenantPrefix_0 IS NOT NULL)) OR (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) IS NULL AND @__ef_filter__TenantPrefix_0 IS NULL)))))"); } public override void Projection_query() @@ -116,7 +115,7 @@ public override void Projection_query() SELECT [c].[CustomerID] FROM [Customers] AS [c] -WHERE ([c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0 + N'%' AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) OR (@__ef_filter__TenantPrefix_0 = N'')"); +WHERE ((@__ef_filter__TenantPrefix_0 = N'') AND @__ef_filter__TenantPrefix_0 IS NOT NULL) OR ([c].[CompanyName] IS NOT NULL AND (@__ef_filter__TenantPrefix_0 IS NOT NULL AND (([c].[CompanyName] LIKE [c].[CompanyName] + N'%') AND (((LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0) AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) IS NOT NULL AND @__ef_filter__TenantPrefix_0 IS NOT NULL)) OR (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) IS NULL AND @__ef_filter__TenantPrefix_0 IS NULL)))))"); } public override void Include_query() @@ -126,23 +125,11 @@ public override void Include_query() AssertSql( @"@__ef_filter__TenantPrefix_0='B' (Size = 4000) -SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] +SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Customers] AS [c] -WHERE ([c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0 + N'%' AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) OR (@__ef_filter__TenantPrefix_0 = N'') -ORDER BY [c].[CustomerID]", - // - @"@__ef_filter__TenantPrefix_1='B' (Size = 4000) - -SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] -FROM [Orders] AS [o] -LEFT JOIN [Customers] AS [o.Customer] ON [o].[CustomerID] = [o.Customer].[CustomerID] -INNER JOIN ( - SELECT [c0].[CustomerID] - FROM [Customers] AS [c0] - WHERE ([c0].[CompanyName] LIKE @__ef_filter__TenantPrefix_1 + N'%' AND (LEFT([c0].[CompanyName], LEN(@__ef_filter__TenantPrefix_1)) = @__ef_filter__TenantPrefix_1)) OR (@__ef_filter__TenantPrefix_1 = N'') -) AS [t] ON [o].[CustomerID] = [t].[CustomerID] -WHERE [o.Customer].[CompanyName] IS NOT NULL -ORDER BY [t].[CustomerID]"); +LEFT JOIN [Orders] AS [o] ON [c].[CustomerID] = [o].[CustomerID] +WHERE ((@__ef_filter__TenantPrefix_0 = N'') AND @__ef_filter__TenantPrefix_0 IS NOT NULL) OR ([c].[CompanyName] IS NOT NULL AND (@__ef_filter__TenantPrefix_0 IS NOT NULL AND (([c].[CompanyName] LIKE [c].[CompanyName] + N'%') AND (((LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0) AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) IS NOT NULL AND @__ef_filter__TenantPrefix_0 IS NOT NULL)) OR (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) IS NULL AND @__ef_filter__TenantPrefix_0 IS NULL))))) +ORDER BY [c].[CustomerID], [o].[OrderID]"); } public override void Include_query_opt_out() @@ -150,17 +137,10 @@ public override void Include_query_opt_out() base.Include_query_opt_out(); AssertSql( - @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] + @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Customers] AS [c] -ORDER BY [c].[CustomerID]", - // - @"SELECT [c.Orders].[OrderID], [c.Orders].[CustomerID], [c.Orders].[EmployeeID], [c.Orders].[OrderDate] -FROM [Orders] AS [c.Orders] -INNER JOIN ( - SELECT [c0].[CustomerID] - FROM [Customers] AS [c0] -) AS [t] ON [c.Orders].[CustomerID] = [t].[CustomerID] -ORDER BY [t].[CustomerID]"); +LEFT JOIN [Orders] AS [o] ON [c].[CustomerID] = [o].[CustomerID] +ORDER BY [c].[CustomerID], [o].[OrderID]"); } public override void Included_many_to_one_query() @@ -172,13 +152,35 @@ public override void Included_many_to_one_query() SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [t].[CustomerID], [t].[Address], [t].[City], [t].[CompanyName], [t].[ContactName], [t].[ContactTitle], [t].[Country], [t].[Fax], [t].[Phone], [t].[PostalCode], [t].[Region] FROM [Orders] AS [o] -LEFT JOIN [Customers] AS [o.Customer] ON [o].[CustomerID] = [o.Customer].[CustomerID] LEFT JOIN ( SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] - WHERE ([c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0 + N'%' AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) OR (@__ef_filter__TenantPrefix_0 = N'') + WHERE ((@__ef_filter__TenantPrefix_0 = N'') AND @__ef_filter__TenantPrefix_0 IS NOT NULL) OR ([c].[CompanyName] IS NOT NULL AND (@__ef_filter__TenantPrefix_0 IS NOT NULL AND (([c].[CompanyName] LIKE [c].[CompanyName] + N'%') AND (((LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0) AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) IS NOT NULL AND @__ef_filter__TenantPrefix_0 IS NOT NULL)) OR (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) IS NULL AND @__ef_filter__TenantPrefix_0 IS NULL))))) ) AS [t] ON [o].[CustomerID] = [t].[CustomerID] -WHERE [o.Customer].[CompanyName] IS NOT NULL"); +WHERE [t].[CompanyName] IS NOT NULL"); + } + + public override void Project_reference_that_itself_has_query_filter_with_another_reference() + { + base.Project_reference_that_itself_has_query_filter_with_another_reference(); + + AssertSql( + @"@__ef_filter__TenantPrefix_1='B' (Size = 4000) +@__ef_filter___quantity_0='50' + +SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate] +FROM [Order Details] AS [o0] +INNER JOIN ( + SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [t].[CustomerID] AS [CustomerID0], [t].[Address], [t].[City], [t].[CompanyName], [t].[ContactName], [t].[ContactTitle], [t].[Country], [t].[Fax], [t].[Phone], [t].[PostalCode], [t].[Region] + FROM [Orders] AS [o] + LEFT JOIN ( + SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] + FROM [Customers] AS [c] + WHERE ((@__ef_filter__TenantPrefix_1 = N'') AND @__ef_filter__TenantPrefix_1 IS NOT NULL) OR ([c].[CompanyName] IS NOT NULL AND (@__ef_filter__TenantPrefix_1 IS NOT NULL AND (([c].[CompanyName] LIKE [c].[CompanyName] + N'%') AND (((LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_1)) = @__ef_filter__TenantPrefix_1) AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_1)) IS NOT NULL AND @__ef_filter__TenantPrefix_1 IS NOT NULL)) OR (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_1)) IS NULL AND @__ef_filter__TenantPrefix_1 IS NULL))))) + ) AS [t] ON [o].[CustomerID] = [t].[CustomerID] + WHERE [t].[CompanyName] IS NOT NULL +) AS [t0] ON [o0].[OrderID] = [t0].[OrderID] +WHERE [o0].[Quantity] > @__ef_filter___quantity_0"); } public override void Included_one_to_many_query_with_client_eval() @@ -224,7 +226,7 @@ WHERE [od].[Quantity] > @__ef_filter___quantity_1 WHERE (([c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0 + N'%' AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) OR (@__ef_filter__TenantPrefix_0 = N'')) AND ([t0].[Discount] < CAST(10 AS real))"); } - [ConditionalFact(Skip = "issue #15264")] + [ConditionalFact(Skip = "issue #16326")] public void FromSql_is_composed() { using (var context = CreateContext()) @@ -241,7 +243,7 @@ public void FromSql_is_composed() FROM ( select * from Customers ) AS [c] -WHERE ([c].[CompanyName] LIKE @__ef_filter__TenantPrefix_0 + N'%' AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) OR (@__ef_filter__TenantPrefix_0 = N'')"); +WHERE ((@__ef_filter__TenantPrefix_0 = N'') AND @__ef_filter__TenantPrefix_0 IS NOT NULL) OR ([c].[CompanyName] IS NOT NULL AND (@__ef_filter__TenantPrefix_0 IS NOT NULL AND (([c].[CompanyName] LIKE [c].[CompanyName] + N'%') AND (((LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0) AND (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) IS NOT NULL AND @__ef_filter__TenantPrefix_0 IS NOT NULL)) OR (LEFT([c].[CompanyName], LEN(@__ef_filter__TenantPrefix_0)) IS NULL AND @__ef_filter__TenantPrefix_0 IS NULL)))))"); } public override void Compiled_query() diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs index 8c81b8f7950..005abe712e3 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -4770,7 +4770,7 @@ public class EmployeeDevice13025 #region Bug12170 - [ConditionalFact] + [ConditionalFact(Skip = "issue #16321")] public virtual void Weak_entities_with_query_filter_subquery_flattening() { using (CreateDatabase12170()) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryFilterFuncletizationSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryFilterFuncletizationSqlServerTest.cs index bf2263537c5..076e1cb05de 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryFilterFuncletizationSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryFilterFuncletizationSqlServerTest.cs @@ -6,8 +6,7 @@ namespace Microsoft.EntityFrameworkCore.Query { - // issue #15264 - internal class QueryFilterFuncletizationSqlServerTest + public class QueryFilterFuncletizationSqlServerTest : QueryFilterFuncletizationTestBase { public QueryFilterFuncletizationSqlServerTest( @@ -27,9 +26,9 @@ public override void DbContext_property_parameter_does_not_clash_with_closure_pa @"@__ef_filter__Field_0='False' @__Field_0='False' -SELECT [e].[Id], [e].[IsEnabled] -FROM [FieldFilter] AS [e] -WHERE ([e].[IsEnabled] = @__ef_filter__Field_0) AND ([e].[IsEnabled] = @__Field_0)"); +SELECT [f].[Id], [f].[IsEnabled] +FROM [FieldFilter] AS [f] +WHERE (([f].[IsEnabled] = @__ef_filter__Field_0) AND @__ef_filter__Field_0 IS NOT NULL) AND (([f].[IsEnabled] = @__Field_0) AND @__Field_0 IS NOT NULL)"); } public override void DbContext_field_is_parameterized() @@ -39,15 +38,15 @@ public override void DbContext_field_is_parameterized() AssertSql( @"@__ef_filter__Field_0='False' -SELECT [e].[Id], [e].[IsEnabled] -FROM [FieldFilter] AS [e] -WHERE [e].[IsEnabled] = @__ef_filter__Field_0", +SELECT [f].[Id], [f].[IsEnabled] +FROM [FieldFilter] AS [f] +WHERE ([f].[IsEnabled] = @__ef_filter__Field_0) AND @__ef_filter__Field_0 IS NOT NULL", // @"@__ef_filter__Field_0='True' -SELECT [e].[Id], [e].[IsEnabled] -FROM [FieldFilter] AS [e] -WHERE [e].[IsEnabled] = @__ef_filter__Field_0"); +SELECT [f].[Id], [f].[IsEnabled] +FROM [FieldFilter] AS [f] +WHERE ([f].[IsEnabled] = @__ef_filter__Field_0) AND @__ef_filter__Field_0 IS NOT NULL"); } public override void DbContext_property_is_parameterized() @@ -57,15 +56,15 @@ public override void DbContext_property_is_parameterized() AssertSql( @"@__ef_filter__Property_0='False' -SELECT [e].[Id], [e].[IsEnabled] -FROM [PropertyFilter] AS [e] -WHERE [e].[IsEnabled] = @__ef_filter__Property_0", +SELECT [p].[Id], [p].[IsEnabled] +FROM [PropertyFilter] AS [p] +WHERE ([p].[IsEnabled] = @__ef_filter__Property_0) AND @__ef_filter__Property_0 IS NOT NULL", // @"@__ef_filter__Property_0='True' -SELECT [e].[Id], [e].[IsEnabled] -FROM [PropertyFilter] AS [e] -WHERE [e].[IsEnabled] = @__ef_filter__Property_0"); +SELECT [p].[Id], [p].[IsEnabled] +FROM [PropertyFilter] AS [p] +WHERE ([p].[IsEnabled] = @__ef_filter__Property_0) AND @__ef_filter__Property_0 IS NOT NULL"); } public override void DbContext_method_call_is_parameterized() @@ -75,9 +74,9 @@ public override void DbContext_method_call_is_parameterized() AssertSql( @"@__ef_filter__p_0='2' -SELECT [e].[Id], [e].[Tenant] -FROM [MethodCallFilter] AS [e] -WHERE [e].[Tenant] = @__ef_filter__p_0"); +SELECT [m].[Id], [m].[Tenant] +FROM [MethodCallFilter] AS [m] +WHERE ([m].[Tenant] = @__ef_filter__p_0) AND @__ef_filter__p_0 IS NOT NULL"); } public override void DbContext_list_is_parameterized() @@ -85,17 +84,17 @@ public override void DbContext_list_is_parameterized() base.DbContext_list_is_parameterized(); AssertSql( - @"SELECT [e].[Id], [e].[Tenant] -FROM [ListFilter] AS [e] -WHERE 0 = 1", + @"SELECT [l].[Id], [l].[Tenant] +FROM [ListFilter] AS [l] +WHERE CAST(1 AS bit) = CAST(0 AS bit)", // - @"SELECT [e].[Id], [e].[Tenant] -FROM [ListFilter] AS [e] -WHERE [e].[Tenant] IN (1)", + @"SELECT [l].[Id], [l].[Tenant] +FROM [ListFilter] AS [l] +WHERE [l].[Tenant] IN (1)", // - @"SELECT [e].[Id], [e].[Tenant] -FROM [ListFilter] AS [e] -WHERE [e].[Tenant] IN (2, 3)"); + @"SELECT [l].[Id], [l].[Tenant] +FROM [ListFilter] AS [l] +WHERE [l].[Tenant] IN (2, 3)"); } public override void DbContext_property_chain_is_parameterized() @@ -105,15 +104,15 @@ public override void DbContext_property_chain_is_parameterized() AssertSql( @"@__ef_filter__Enabled_0='False' -SELECT [e].[Id], [e].[IsEnabled] -FROM [PropertyChainFilter] AS [e] -WHERE [e].[IsEnabled] = @__ef_filter__Enabled_0", +SELECT [p].[Id], [p].[IsEnabled] +FROM [PropertyChainFilter] AS [p] +WHERE ([p].[IsEnabled] = @__ef_filter__Enabled_0) AND @__ef_filter__Enabled_0 IS NOT NULL", // @"@__ef_filter__Enabled_0='True' -SELECT [e].[Id], [e].[IsEnabled] -FROM [PropertyChainFilter] AS [e] -WHERE [e].[IsEnabled] = @__ef_filter__Enabled_0"); +SELECT [p].[Id], [p].[IsEnabled] +FROM [PropertyChainFilter] AS [p] +WHERE ([p].[IsEnabled] = @__ef_filter__Enabled_0) AND @__ef_filter__Enabled_0 IS NOT NULL"); } public override void DbContext_property_method_call_is_parameterized() @@ -123,9 +122,9 @@ public override void DbContext_property_method_call_is_parameterized() AssertSql( @"@__ef_filter__p_0='2' -SELECT [e].[Id], [e].[Tenant] -FROM [PropertyMethodCallFilter] AS [e] -WHERE [e].[Tenant] = @__ef_filter__p_0"); +SELECT [p].[Id], [p].[Tenant] +FROM [PropertyMethodCallFilter] AS [p] +WHERE ([p].[Tenant] = @__ef_filter__p_0) AND @__ef_filter__p_0 IS NOT NULL"); } public override void DbContext_method_call_chain_is_parameterized() @@ -135,9 +134,9 @@ public override void DbContext_method_call_chain_is_parameterized() AssertSql( @"@__ef_filter__p_0='2' -SELECT [e].[Id], [e].[Tenant] -FROM [MethodCallChainFilter] AS [e] -WHERE [e].[Tenant] = @__ef_filter__p_0"); +SELECT [m].[Id], [m].[Tenant] +FROM [MethodCallChainFilter] AS [m] +WHERE ([m].[Tenant] = @__ef_filter__p_0) AND @__ef_filter__p_0 IS NOT NULL"); } public override void DbContext_complex_expression_is_parameterized() @@ -148,23 +147,23 @@ public override void DbContext_complex_expression_is_parameterized() @"@__ef_filter__Property_0='False' @__ef_filter__p_1='True' -SELECT [x].[Id], [x].[IsEnabled] -FROM [ComplexFilter] AS [x] -WHERE ([x].[IsEnabled] = @__ef_filter__Property_0) AND (@__ef_filter__p_1 = CAST(1 AS bit))", +SELECT [c].[Id], [c].[IsEnabled] +FROM [ComplexFilter] AS [c] +WHERE (([c].[IsEnabled] = @__ef_filter__Property_0) AND @__ef_filter__Property_0 IS NOT NULL) AND (@__ef_filter__p_1 = CAST(1 AS bit))", // @"@__ef_filter__Property_0='True' @__ef_filter__p_1='True' -SELECT [x].[Id], [x].[IsEnabled] -FROM [ComplexFilter] AS [x] -WHERE ([x].[IsEnabled] = @__ef_filter__Property_0) AND (@__ef_filter__p_1 = CAST(1 AS bit))", +SELECT [c].[Id], [c].[IsEnabled] +FROM [ComplexFilter] AS [c] +WHERE (([c].[IsEnabled] = @__ef_filter__Property_0) AND @__ef_filter__Property_0 IS NOT NULL) AND (@__ef_filter__p_1 = CAST(1 AS bit))", // @"@__ef_filter__Property_0='True' @__ef_filter__p_1='False' -SELECT [x].[Id], [x].[IsEnabled] -FROM [ComplexFilter] AS [x] -WHERE ([x].[IsEnabled] = @__ef_filter__Property_0) AND (@__ef_filter__p_1 = CAST(1 AS bit))"); +SELECT [c].[Id], [c].[IsEnabled] +FROM [ComplexFilter] AS [c] +WHERE (([c].[IsEnabled] = @__ef_filter__Property_0) AND @__ef_filter__Property_0 IS NOT NULL) AND (@__ef_filter__p_1 = CAST(1 AS bit))"); } public override void DbContext_property_based_filter_does_not_short_circuit() @@ -175,22 +174,23 @@ public override void DbContext_property_based_filter_does_not_short_circuit() @"@__ef_filter__p_0='False' @__ef_filter__IsModerated_1='True' (Nullable = true) -SELECT [x].[Id], [x].[IsDeleted], [x].[IsModerated] -FROM [ShortCircuitFilter] AS [x] -WHERE ([x].[IsDeleted] = CAST(0 AS bit)) AND ((@__ef_filter__p_0 = CAST(1 AS bit)) OR (@__ef_filter__IsModerated_1 = [x].[IsModerated]))", +SELECT [s].[Id], [s].[IsDeleted], [s].[IsModerated] +FROM [ShortCircuitFilter] AS [s] +WHERE ([s].[IsDeleted] <> CAST(1 AS bit)) AND ((@__ef_filter__p_0 = CAST(1 AS bit)) OR ((@__ef_filter__IsModerated_1 = [s].[IsModerated]) AND @__ef_filter__IsModerated_1 IS NOT NULL))", // @"@__ef_filter__p_0='False' @__ef_filter__IsModerated_1='False' (Nullable = true) -SELECT [x].[Id], [x].[IsDeleted], [x].[IsModerated] -FROM [ShortCircuitFilter] AS [x] -WHERE ([x].[IsDeleted] = CAST(0 AS bit)) AND ((@__ef_filter__p_0 = CAST(1 AS bit)) OR (@__ef_filter__IsModerated_1 = [x].[IsModerated]))", +SELECT [s].[Id], [s].[IsDeleted], [s].[IsModerated] +FROM [ShortCircuitFilter] AS [s] +WHERE ([s].[IsDeleted] <> CAST(1 AS bit)) AND ((@__ef_filter__p_0 = CAST(1 AS bit)) OR ((@__ef_filter__IsModerated_1 = [s].[IsModerated]) AND @__ef_filter__IsModerated_1 IS NOT NULL))", // @"@__ef_filter__p_0='True' +@__ef_filter__IsModerated_1='' -SELECT [x].[Id], [x].[IsDeleted], [x].[IsModerated] -FROM [ShortCircuitFilter] AS [x] -WHERE ([x].[IsDeleted] = CAST(0 AS bit)) AND ((@__ef_filter__p_0 = CAST(1 AS bit)) OR [x].[IsModerated] IS NULL)"); +SELECT [s].[Id], [s].[IsDeleted], [s].[IsModerated] +FROM [ShortCircuitFilter] AS [s] +WHERE ([s].[IsDeleted] <> CAST(1 AS bit)) AND ((@__ef_filter__p_0 = CAST(1 AS bit)) OR ((@__ef_filter__IsModerated_1 = [s].[IsModerated]) AND @__ef_filter__IsModerated_1 IS NOT NULL))"); } public override void EntityTypeConfiguration_DbContext_field_is_parameterized() @@ -202,13 +202,13 @@ public override void EntityTypeConfiguration_DbContext_field_is_parameterized() SELECT [e].[Id], [e].[IsEnabled] FROM [EntityTypeConfigurationFieldFilter] AS [e] -WHERE [e].[IsEnabled] = @__ef_filter__Field_0", +WHERE ([e].[IsEnabled] = @__ef_filter__Field_0) AND @__ef_filter__Field_0 IS NOT NULL", // @"@__ef_filter__Field_0='True' SELECT [e].[Id], [e].[IsEnabled] FROM [EntityTypeConfigurationFieldFilter] AS [e] -WHERE [e].[IsEnabled] = @__ef_filter__Field_0"); +WHERE ([e].[IsEnabled] = @__ef_filter__Field_0) AND @__ef_filter__Field_0 IS NOT NULL"); } public override void EntityTypeConfiguration_DbContext_property_is_parameterized() @@ -220,13 +220,13 @@ public override void EntityTypeConfiguration_DbContext_property_is_parameterized SELECT [e].[Id], [e].[IsEnabled] FROM [EntityTypeConfigurationPropertyFilter] AS [e] -WHERE [e].[IsEnabled] = @__ef_filter__Property_0", +WHERE ([e].[IsEnabled] = @__ef_filter__Property_0) AND @__ef_filter__Property_0 IS NOT NULL", // @"@__ef_filter__Property_0='True' SELECT [e].[Id], [e].[IsEnabled] FROM [EntityTypeConfigurationPropertyFilter] AS [e] -WHERE [e].[IsEnabled] = @__ef_filter__Property_0"); +WHERE ([e].[IsEnabled] = @__ef_filter__Property_0) AND @__ef_filter__Property_0 IS NOT NULL"); } public override void EntityTypeConfiguration_DbContext_method_call_is_parameterized() @@ -238,7 +238,7 @@ public override void EntityTypeConfiguration_DbContext_method_call_is_parameteri SELECT [e].[Id], [e].[Tenant] FROM [EntityTypeConfigurationMethodCallFilter] AS [e] -WHERE [e].[Tenant] = @__ef_filter__p_0"); +WHERE ([e].[Tenant] = @__ef_filter__p_0) AND @__ef_filter__p_0 IS NOT NULL"); } public override void EntityTypeConfiguration_DbContext_property_chain_is_parameterized() @@ -250,13 +250,13 @@ public override void EntityTypeConfiguration_DbContext_property_chain_is_paramet SELECT [e].[Id], [e].[IsEnabled] FROM [EntityTypeConfigurationPropertyChainFilter] AS [e] -WHERE [e].[IsEnabled] = @__ef_filter__Enabled_0", +WHERE ([e].[IsEnabled] = @__ef_filter__Enabled_0) AND @__ef_filter__Enabled_0 IS NOT NULL", // @"@__ef_filter__Enabled_0='True' SELECT [e].[Id], [e].[IsEnabled] FROM [EntityTypeConfigurationPropertyChainFilter] AS [e] -WHERE [e].[IsEnabled] = @__ef_filter__Enabled_0"); +WHERE ([e].[IsEnabled] = @__ef_filter__Enabled_0) AND @__ef_filter__Enabled_0 IS NOT NULL"); } public override void Local_method_DbContext_field_is_parameterized() @@ -266,15 +266,15 @@ public override void Local_method_DbContext_field_is_parameterized() AssertSql( @"@__ef_filter__Field_0='False' -SELECT [e].[Id], [e].[IsEnabled] -FROM [LocalMethodFilter] AS [e] -WHERE [e].[IsEnabled] = @__ef_filter__Field_0", +SELECT [l].[Id], [l].[IsEnabled] +FROM [LocalMethodFilter] AS [l] +WHERE ([l].[IsEnabled] = @__ef_filter__Field_0) AND @__ef_filter__Field_0 IS NOT NULL", // @"@__ef_filter__Field_0='True' -SELECT [e].[Id], [e].[IsEnabled] -FROM [LocalMethodFilter] AS [e] -WHERE [e].[IsEnabled] = @__ef_filter__Field_0"); +SELECT [l].[Id], [l].[IsEnabled] +FROM [LocalMethodFilter] AS [l] +WHERE ([l].[IsEnabled] = @__ef_filter__Field_0) AND @__ef_filter__Field_0 IS NOT NULL"); } public override void Local_static_method_DbContext_property_is_parameterized() @@ -284,15 +284,15 @@ public override void Local_static_method_DbContext_property_is_parameterized() AssertSql( @"@__ef_filter__Property_0='False' -SELECT [e].[Id], [e].[IsEnabled] -FROM [LocalMethodParamsFilter] AS [e] -WHERE [e].[IsEnabled] = @__ef_filter__Property_0", +SELECT [l].[Id], [l].[IsEnabled] +FROM [LocalMethodParamsFilter] AS [l] +WHERE ([l].[IsEnabled] = @__ef_filter__Property_0) AND @__ef_filter__Property_0 IS NOT NULL", // @"@__ef_filter__Property_0='True' -SELECT [e].[Id], [e].[IsEnabled] -FROM [LocalMethodParamsFilter] AS [e] -WHERE [e].[IsEnabled] = @__ef_filter__Property_0"); +SELECT [l].[Id], [l].[IsEnabled] +FROM [LocalMethodParamsFilter] AS [l] +WHERE ([l].[IsEnabled] = @__ef_filter__Property_0) AND @__ef_filter__Property_0 IS NOT NULL"); } public override void Remote_method_DbContext_property_method_call_is_parameterized() @@ -302,9 +302,9 @@ public override void Remote_method_DbContext_property_method_call_is_parameteriz AssertSql( @"@__ef_filter__p_0='2' -SELECT [e].[Id], [e].[Tenant] -FROM [RemoteMethodParamsFilter] AS [e] -WHERE [e].[Tenant] = @__ef_filter__p_0"); +SELECT [r].[Id], [r].[Tenant] +FROM [RemoteMethodParamsFilter] AS [r] +WHERE ([r].[Tenant] = @__ef_filter__p_0) AND @__ef_filter__p_0 IS NOT NULL"); } public override void Extension_method_DbContext_field_is_parameterized() @@ -316,13 +316,13 @@ public override void Extension_method_DbContext_field_is_parameterized() SELECT [e].[Id], [e].[IsEnabled] FROM [ExtensionBuilderFilter] AS [e] -WHERE [e].[IsEnabled] = @__ef_filter__Field_0", +WHERE ([e].[IsEnabled] = @__ef_filter__Field_0) AND @__ef_filter__Field_0 IS NOT NULL", // @"@__ef_filter__Field_0='True' SELECT [e].[Id], [e].[IsEnabled] FROM [ExtensionBuilderFilter] AS [e] -WHERE [e].[IsEnabled] = @__ef_filter__Field_0"); +WHERE ([e].[IsEnabled] = @__ef_filter__Field_0) AND @__ef_filter__Field_0 IS NOT NULL"); } public override void Extension_method_DbContext_property_chain_is_parameterized() @@ -334,13 +334,13 @@ public override void Extension_method_DbContext_property_chain_is_parameterized( SELECT [e].[Id], [e].[IsEnabled] FROM [ExtensionContextFilter] AS [e] -WHERE [e].[IsEnabled] = @__ef_filter__Enabled_0", +WHERE ([e].[IsEnabled] = @__ef_filter__Enabled_0) AND @__ef_filter__Enabled_0 IS NOT NULL", // @"@__ef_filter__Enabled_0='True' SELECT [e].[Id], [e].[IsEnabled] FROM [ExtensionContextFilter] AS [e] -WHERE [e].[IsEnabled] = @__ef_filter__Enabled_0"); +WHERE ([e].[IsEnabled] = @__ef_filter__Enabled_0) AND @__ef_filter__Enabled_0 IS NOT NULL"); } public override void Using_DbSet_in_filter_works() @@ -374,9 +374,9 @@ public override void Static_member_from_dbContext_is_inlined() base.Static_member_from_dbContext_is_inlined(); AssertSql( - @"SELECT [e].[Id], [e].[UserId] -FROM [DbContextStaticMemberFilter] AS [e] -WHERE [e].[UserId] <> 1"); + @"SELECT [d].[Id], [d].[UserId] +FROM [DbContextStaticMemberFilter] AS [d] +WHERE [d].[UserId] <> 1"); } public override void Static_member_from_non_dbContext_is_inlined() @@ -384,9 +384,9 @@ public override void Static_member_from_non_dbContext_is_inlined() base.Static_member_from_non_dbContext_is_inlined(); AssertSql( - @"SELECT [b].[Id], [b].[IsEnabled] -FROM [StaticMemberFilter] AS [b] -WHERE [b].[IsEnabled] = CAST(1 AS bit)"); + @"SELECT [s].[Id], [s].[IsEnabled] +FROM [StaticMemberFilter] AS [s] +WHERE [s].[IsEnabled] = CAST(1 AS bit)"); } public override void Local_variable_from_OnModelCreating_is_inlined() @@ -394,9 +394,9 @@ public override void Local_variable_from_OnModelCreating_is_inlined() base.Local_variable_from_OnModelCreating_is_inlined(); AssertSql( - @"SELECT [e].[Id], [e].[IsEnabled] -FROM [LocalVariableFilter] AS [e] -WHERE [e].[IsEnabled] = CAST(1 AS bit)"); + @"SELECT [l].[Id], [l].[IsEnabled] +FROM [LocalVariableFilter] AS [l] +WHERE [l].[IsEnabled] = CAST(1 AS bit)"); } public override void Method_parameter_is_inlined() @@ -404,9 +404,9 @@ public override void Method_parameter_is_inlined() base.Method_parameter_is_inlined(); AssertSql( - @"SELECT [e].[Id], [e].[Tenant] -FROM [ParameterFilter] AS [e] -WHERE [e].[Tenant] = 0"); + @"SELECT [p].[Id], [p].[Tenant] +FROM [ParameterFilter] AS [p] +WHERE [p].[Tenant] = 0"); } public override void Using_multiple_context_in_filter_parametrize_only_current_context() @@ -416,15 +416,15 @@ public override void Using_multiple_context_in_filter_parametrize_only_current_c AssertSql( @"@__ef_filter__Property_0='False' -SELECT [e].[Id], [e].[BossId], [e].[IsEnabled] -FROM [MultiContextFilter] AS [e] -WHERE ([e].[IsEnabled] = @__ef_filter__Property_0) AND ([e].[BossId] = 1)", +SELECT [m].[Id], [m].[BossId], [m].[IsEnabled] +FROM [MultiContextFilter] AS [m] +WHERE (([m].[IsEnabled] = @__ef_filter__Property_0) AND @__ef_filter__Property_0 IS NOT NULL) AND ([m].[BossId] = 1)", // @"@__ef_filter__Property_0='True' -SELECT [e].[Id], [e].[BossId], [e].[IsEnabled] -FROM [MultiContextFilter] AS [e] -WHERE ([e].[IsEnabled] = @__ef_filter__Property_0) AND ([e].[BossId] = 1)"); +SELECT [m].[Id], [m].[BossId], [m].[IsEnabled] +FROM [MultiContextFilter] AS [m] +WHERE (([m].[IsEnabled] = @__ef_filter__Property_0) AND @__ef_filter__Property_0 IS NOT NULL) AND ([m].[BossId] = 1)"); } private void AssertSql(params string[] expected) diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerComplianceTest.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerComplianceTest.cs index fdae32b8bd2..d6578027928 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerComplianceTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerComplianceTest.cs @@ -16,10 +16,7 @@ public class SqlServerComplianceTest : RelationalComplianceTestBase typeof(GraphUpdatesTestBase<>), // issue #15318 typeof(ProxyGraphUpdatesTestBase<>), // issue #15318 typeof(ComplexNavigationsWeakQueryTestBase<>), // issue #15285 - typeof(FiltersInheritanceTestBase<>), // issue #15264 - typeof(FiltersTestBase<>), // issue #15264 typeof(OwnedQueryTestBase<>), // issue #15285 - typeof(QueryFilterFuncletizationTestBase<>), // issue #15264 typeof(RelationalOwnedQueryTestBase<>), // issue #15285 // Query pipeline typeof(ConcurrencyDetectorTestBase<>), diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/FiltersInheritanceSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/FiltersInheritanceSqliteTest.cs index 3a5364acb54..8f3491775b7 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/FiltersInheritanceSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/FiltersInheritanceSqliteTest.cs @@ -3,8 +3,7 @@ namespace Microsoft.EntityFrameworkCore.Query { - // issue #15264 - internal class FiltersInheritanceSqliteTest : FiltersInheritanceTestBase + public class FiltersInheritanceSqliteTest : FiltersInheritanceTestBase { public FiltersInheritanceSqliteTest(FiltersInheritanceSqliteFixture fixture) : base(fixture) diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/FiltersSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/FiltersSqliteTest.cs index 9caa9380e4a..432664208d0 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/FiltersSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/FiltersSqliteTest.cs @@ -5,8 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query { - // issue #15264 - internal class FiltersSqliteTest : FiltersTestBase> + public class FiltersSqliteTest : FiltersTestBase> { public FiltersSqliteTest(NorthwindQuerySqliteFixture fixture, ITestOutputHelper testOutputHelper) : base(fixture) @@ -18,12 +17,13 @@ public FiltersSqliteTest(NorthwindQuerySqliteFixture public override void Count_query() { base.Count_query(); + AssertSql( @"@__ef_filter__TenantPrefix_0='B' (Size = 1) SELECT COUNT(*) FROM ""Customers"" AS ""c"" -WHERE (""c"".""CompanyName"" LIKE @__ef_filter__TenantPrefix_0 || '%' AND (substr(""c"".""CompanyName"", 1, length(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0)) OR (@__ef_filter__TenantPrefix_0 = '')"); +WHERE ((@__ef_filter__TenantPrefix_0 = '') AND @__ef_filter__TenantPrefix_0 IS NOT NULL) OR (""c"".""CompanyName"" IS NOT NULL AND (@__ef_filter__TenantPrefix_0 IS NOT NULL AND (((""c"".""CompanyName"" LIKE ""c"".""CompanyName"" || '%') AND (((substr(""c"".""CompanyName"", 1, length(@__ef_filter__TenantPrefix_0)) = @__ef_filter__TenantPrefix_0) AND (substr(""c"".""CompanyName"", 1, length(@__ef_filter__TenantPrefix_0)) IS NOT NULL AND @__ef_filter__TenantPrefix_0 IS NOT NULL)) OR (substr(""c"".""CompanyName"", 1, length(@__ef_filter__TenantPrefix_0)) IS NULL AND @__ef_filter__TenantPrefix_0 IS NULL))) OR ((@__ef_filter__TenantPrefix_0 = '') AND @__ef_filter__TenantPrefix_0 IS NOT NULL))))"); } private void AssertSql(params string[] expected) diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/QueryFilterFuncletizationSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/QueryFilterFuncletizationSqliteTest.cs index 2af7d0ba001..85df1031c29 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/QueryFilterFuncletizationSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/QueryFilterFuncletizationSqliteTest.cs @@ -6,8 +6,7 @@ namespace Microsoft.EntityFrameworkCore.Query { - // issue #15264 - internal class QueryFilterFuncletizationSqliteTest + public class QueryFilterFuncletizationSqliteTest : QueryFilterFuncletizationTestBase { public QueryFilterFuncletizationSqliteTest( diff --git a/test/EFCore.Sqlite.FunctionalTests/SqliteComplianceTest.cs b/test/EFCore.Sqlite.FunctionalTests/SqliteComplianceTest.cs index 80fb5047c77..e122380831a 100644 --- a/test/EFCore.Sqlite.FunctionalTests/SqliteComplianceTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/SqliteComplianceTest.cs @@ -20,10 +20,7 @@ public class SqliteComplianceTest : RelationalComplianceTestBase typeof(GraphUpdatesTestBase<>), // issue #15318 typeof(ProxyGraphUpdatesTestBase<>), // issue #15318 typeof(ComplexNavigationsWeakQueryTestBase<>), // issue #15285 - typeof(FiltersInheritanceTestBase<>), // issue #15264 - typeof(FiltersTestBase<>), // issue #15264 typeof(OwnedQueryTestBase<>), // issue #15285 - typeof(QueryFilterFuncletizationTestBase<>), // issue #15264 typeof(RelationalOwnedQueryTestBase<>), // issue #15285 // Query pipeline typeof(ConcurrencyDetectorTestBase<>),