From b84eec11504af27bc715cd0f0350caee2b0e4987 Mon Sep 17 00:00:00 2001 From: Maurycy Markowski Date: Mon, 27 Jan 2020 12:50:23 -0800 Subject: [PATCH] Fix to #19499 - Query: null semantics not applied on subquery.Contains(null) We used to translate this into `NULL IN subquery` pattern, but this doesn't work because it doesn't match if the subquery also contains null. Fix is to convert this into subquery.Any(e => e == NULL), which translates to EXISTS with predicate and we can correctly apply null semantics. Also it allows us to translate contains on entities with composite keys. Also made several small fixes: - marked EXISTS as never nullable for purpose of null semantics, - marked IN as never nullable for purpose of null semantics when both the subquery projection element and the item expression are not nullable, - optimized EXISITS (subquery) and IN (subquery) with predicate that resolves to false, directly into false, since empty subquery never exisits and doesn't contain any results, - improves expression printer output for IN expression in the subquery scenario. --- ...lityBasedSqlProcessingExpressionVisitor.cs | 40 ++- .../Query/SqlExpressions/InExpression.cs | 9 +- ...lAnyContainsRewritingExpressionVisitor.cs} | 23 +- .../Query/QueryTranslationPreprocessor.cs | 2 +- ...thwindAggregateOperatorsQueryCosmosTest.cs | 59 ++++- .../Query/NorthwindWhereQueryCosmosTest.cs | 6 + ...ComplexNavigationsWeakQueryInMemoryTest.cs | 12 + .../Query/NullSemanticsQueryTestBase.cs | 18 ++ .../Query/ComplexNavigationsQueryTestBase.cs | 60 +++++ .../Query/GearsOfWarQueryFixtureBase.cs | 5 + .../Query/GearsOfWarQueryTestBase.cs | 102 ++++++++ ...orthwindAggregateOperatorsQueryTestBase.cs | 96 ++++++-- .../Query/NorthwindWhereQueryTestBase.cs | 13 + .../Query/QueryTestBase.cs | 17 ++ .../GearsOfWarModel/GearsOfWarData.cs | 12 +- .../GearsOfWarModel/LocustLeader.cs | 2 + .../TestUtilities/QueryAsserter.cs | 19 ++ .../TestUtilities/QueryAsserterBase.cs | 8 + .../ComplexNavigationsQuerySqlServerTest.cs | 91 ++++++- .../Query/FromSqlQuerySqlServerTest.cs | 54 ++--- .../Query/GearsOfWarQuerySqlServerTest.cs | 227 ++++++++++++++---- ...indAggregateOperatorsQuerySqlServerTest.cs | 143 +++++++++-- .../NorthwindGroupByQuerySqlServerTest.cs | 7 +- ...orthwindMiscellaneousQuerySqlServerTest.cs | 20 +- .../Query/NorthwindWhereQuerySqlServerTest.cs | 105 ++++---- .../Query/NullSemanticsQuerySqlServerTest.cs | 20 ++ .../Query/QueryBugsTest.cs | 14 +- .../Query/GearsOfWarQuerySqliteTest.cs | 8 + 28 files changed, 987 insertions(+), 205 deletions(-) rename src/EFCore/Query/Internal/{AllAnyToContainsRewritingExpressionVisitor.cs => AllAnyContainsRewritingExpressionVisitor.cs} (77%) diff --git a/src/EFCore.Relational/Query/NullabilityBasedSqlProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/NullabilityBasedSqlProcessingExpressionVisitor.cs index 21491b5ac38..315d4d10042 100644 --- a/src/EFCore.Relational/Query/NullabilityBasedSqlProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/NullabilityBasedSqlProcessingExpressionVisitor.cs @@ -208,10 +208,20 @@ protected override Expression VisitExists(ExistsExpression existsExpression) { Check.NotNull(existsExpression, nameof(existsExpression)); - return existsExpression.Update( - VisitInternal(existsExpression.Subquery).ResultExpression); + var subquery = VisitInternal(existsExpression.Subquery).ResultExpression; + _nullable = false; + + // if subquery has predicate which evaluates to false, we can simply return false + return IsConstantFalse(subquery.Predicate) + ? subquery.Predicate + : existsExpression.Update(subquery); } + private static bool IsConstantFalse(SqlExpression expression) + => expression is SqlConstantExpression constantExpression + && constantExpression.Value is bool boolValue + && !boolValue; + protected override Expression VisitFromSql(FromSqlExpression fromSqlExpression) => Check.NotNull(fromSqlExpression, nameof(fromSqlExpression)); @@ -223,8 +233,23 @@ protected override Expression VisitIn(InExpression inExpression) if (inExpression.Subquery != null) { - var (subquery, subqueryNullable) = VisitInternal(inExpression.Subquery); - _nullable = itemNullable || subqueryNullable; + var subquery = VisitInternal(inExpression.Subquery).ResultExpression; + + // a IN (SELECT * FROM table WHERE false) => false + if (IsConstantFalse(subquery.Predicate)) + { + _nullable = false; + + return subquery.Predicate; + } + + // if item is not nullable, and subquery contains a non-nullable column we know the result can never be null + // note: in this case we could broaden the optimization if we knew the nullability of the projection + // but we don't keep that information and we want to avoid double visitation + _nullable = !(!itemNullable + && subquery.Projection.Count == 1 + && subquery.Projection[0].Expression is ColumnExpression columnProjection + && !columnProjection.IsNullable); return inExpression.Update(item, values: null, subquery); } @@ -234,8 +259,8 @@ protected override Expression VisitIn(InExpression inExpression) if (UseRelationalNulls || !(inExpression.Values is SqlConstantExpression || inExpression.Values is SqlParameterExpression)) { - var (values, valuesNullable) = VisitInternal(inExpression.Values); - _nullable = itemNullable || valuesNullable; + var values = VisitInternal(inExpression.Values).ResultExpression; + _nullable = false; return inExpression.Update(item, values, subquery: null); } @@ -266,7 +291,7 @@ protected override Expression VisitIn(InExpression inExpression) if (!itemNullable || (_allowOptimizedExpansion && !inExpression.IsNegated && !hasNullValue)) { - _nullable = itemNullable; + _nullable = false; // non_nullable IN (1, 2) -> non_nullable IN (1, 2) // non_nullable IN (1, 2, NULL) -> non_nullable IN (1, 2) @@ -276,7 +301,6 @@ protected override Expression VisitIn(InExpression inExpression) return inExpression.Update(item, inValuesExpression, subquery: null); } - // adding null comparison term to remove nulls completely from the resulting expression _nullable = false; // nullable IN (1, 2) -> nullable IN (1, 2) AND nullable IS NOT NULL (full) diff --git a/src/EFCore.Relational/Query/SqlExpressions/InExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/InExpression.cs index b24b659b259..52d3cd2f7b7 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/InExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/InExpression.cs @@ -88,7 +88,14 @@ public override void Print(ExpressionPrinter expressionPrinter) expressionPrinter.Append(IsNegated ? " NOT IN " : " IN "); expressionPrinter.Append("("); - if (Values is SqlConstantExpression constantValuesExpression + if (Subquery != null) + { + using (expressionPrinter.Indent()) + { + expressionPrinter.Visit(Subquery); + } + } + else if (Values is SqlConstantExpression constantValuesExpression && constantValuesExpression.Value is IEnumerable constantValues) { var first = true; diff --git a/src/EFCore/Query/Internal/AllAnyToContainsRewritingExpressionVisitor.cs b/src/EFCore/Query/Internal/AllAnyContainsRewritingExpressionVisitor.cs similarity index 77% rename from src/EFCore/Query/Internal/AllAnyToContainsRewritingExpressionVisitor.cs rename to src/EFCore/Query/Internal/AllAnyContainsRewritingExpressionVisitor.cs index 58425eaacc5..11eae8fc28e 100644 --- a/src/EFCore/Query/Internal/AllAnyToContainsRewritingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/AllAnyContainsRewritingExpressionVisitor.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal { - public class AllAnyToContainsRewritingExpressionVisitor : ExpressionVisitor + public class AllAnyContainsRewritingExpressionVisitor : ExpressionVisitor { private static bool IsExpressionOfFunc(Type type, int funcGenericArgs = 2) => type.IsGenericType @@ -44,6 +44,27 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp } } + if (methodCallExpression.Method.IsGenericMethod + && methodCallExpression.Method.GetGenericMethodDefinition() is MethodInfo containsMethodInfo + && containsMethodInfo.Equals(QueryableMethods.Contains) + && !(methodCallExpression.Arguments[0] is ParameterExpression) + && (!(methodCallExpression.Arguments[0] is ConstantExpression) || ((ConstantExpression)methodCallExpression.Arguments[0]).IsEntityQueryable()) + // special case Queryable.Contains(byte_array, byte) - we don't want those to be rewritten + && methodCallExpression.Arguments[1].Type != typeof(byte)) + { + var typeArgument = methodCallExpression.Method.GetGenericArguments()[0]; + var anyMethod = QueryableMethods.AnyWithPredicate.MakeGenericMethod(typeArgument); + + var anyLambdaParameter = Expression.Parameter(typeArgument, "p"); + var anyLambda = Expression.Lambda( + Expression.Equal( + anyLambdaParameter, + methodCallExpression.Arguments[1]), + anyLambdaParameter); + + return Expression.Call(null, anyMethod, new[] { methodCallExpression.Arguments[0], anyLambda }); + } + return base.VisitMethodCall(methodCallExpression); } diff --git a/src/EFCore/Query/QueryTranslationPreprocessor.cs b/src/EFCore/Query/QueryTranslationPreprocessor.cs index 6a008c3093c..cbe5efe6afd 100644 --- a/src/EFCore/Query/QueryTranslationPreprocessor.cs +++ b/src/EFCore/Query/QueryTranslationPreprocessor.cs @@ -37,7 +37,7 @@ public virtual Expression Process([NotNull] Expression query) query = NormalizeQueryableMethodCall(query); query = new VBToCSharpConvertingExpressionVisitor().Visit(query); - query = new AllAnyToContainsRewritingExpressionVisitor().Visit(query); + query = new AllAnyContainsRewritingExpressionVisitor().Visit(query); query = new NullCheckRemovingExpressionVisitor().Visit(query); query = new EntityEqualityRewritingExpressionVisitor(_queryCompilationContext).Rewrite(query); query = new SubqueryMemberPushdownExpressionVisitor(_queryCompilationContext.Model).Visit(query); diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs index 8b6ced06ba2..7af3a15cfc0 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs @@ -1366,15 +1366,10 @@ FROM root c WHERE ((c[""Discriminator""] = ""Customer"") AND c[""CustomerID""] IN (""ALFKI""))"); } - [ConditionalFact(Skip = "Issue#17246 (Contains over subquery is not supported)")] - public override void Contains_over_entityType_with_null_should_rewrite_to_identity_equality() + [ConditionalTheory(Skip = "Issue#17246 (Contains over subquery is not supported)")] + public override Task Contains_over_entityType_with_null_should_rewrite_to_false(bool async) { - base.Contains_over_entityType_with_null_should_rewrite_to_identity_equality(); - - AssertSql( - @"SELECT c -FROM root c -WHERE ((c[""Discriminator""] = ""Order"") AND (c[""OrderID""] = 10248))"); + return base.Contains_over_entityType_with_null_should_rewrite_to_false(async); } public override async Task String_FirstOrDefault_in_projection_does_client_eval(bool async) @@ -1539,6 +1534,54 @@ public override Task Sum_over_explicit_cast_over_column(bool async) return base.Sum_over_explicit_cast_over_column(async); } + [ConditionalTheory(Skip = "Issue#17246 (Contains over subquery is not supported)")] + public override Task Contains_over_scalar_with_null_should_rewrite_to_identity_equality_subquery(bool async) + { + return base.Contains_over_scalar_with_null_should_rewrite_to_identity_equality_subquery(async); + } + + [ConditionalTheory(Skip = "Issue#17246 (Contains over subquery is not supported)")] + public override Task Contains_over_nullable_scalar_with_null_in_subquery_translated_correctly(bool async) + { + return base.Contains_over_nullable_scalar_with_null_in_subquery_translated_correctly(async); + } + + [ConditionalTheory(Skip = "Issue#17246 (Contains over subquery is not supported)")] + public override Task Contains_over_non_nullable_scalar_with_null_in_subquery_simplifies_to_false(bool async) + { + return base.Contains_over_non_nullable_scalar_with_null_in_subquery_simplifies_to_false(async); + } + + [ConditionalTheory(Skip = "Issue#17246 (Contains over subquery is not supported)")] + public override Task Contains_over_entityType_with_null_should_rewrite_to_identity_equality_subquery(bool async) + { + return base.Contains_over_entityType_with_null_should_rewrite_to_identity_equality_subquery(async); + } + + [ConditionalTheory(Skip = "Issue#17246 (Contains over subquery is not supported)")] + public override Task Contains_over_entityType_with_null_should_rewrite_to_identity_equality_subquery_complex(bool async) + { + return base.Contains_over_entityType_with_null_should_rewrite_to_identity_equality_subquery_complex(async); + } + + [ConditionalTheory(Skip = "Issue#17246 (Contains over subquery is not supported)")] + public override Task Contains_over_entityType_with_null_should_rewrite_to_identity_equality_subquery_negated(bool async) + { + return base.Contains_over_entityType_with_null_should_rewrite_to_identity_equality_subquery_negated(async); + } + + [ConditionalTheory(Skip = "Issue#17246 (Contains over subquery is not supported)")] + public override Task Contains_over_entityType_should_materialize_when_composite(bool async) + { + return base.Contains_over_entityType_should_materialize_when_composite(async); + } + + [ConditionalTheory(Skip = "Issue#17246 (Contains over subquery is not supported)")] + public override Task Contains_over_entityType_should_materialize_when_composite2(bool async) + { + return base.Contains_over_entityType_should_materialize_when_composite2(async); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs index 5a897c4a891..da476fbc348 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs @@ -2053,6 +2053,12 @@ public override Task Where_collection_navigation_ToArray_Length_member(bool asyn return base.Where_collection_navigation_ToArray_Length_member(async); } + [ConditionalTheory(Skip = "Issue#17246 (Contains over subquery is not supported")] + public override Task Where_Queryable_AsEnumerable_Contains_negated(bool async) + { + return base.Where_Queryable_AsEnumerable_Contains_negated(async); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs index 69b8ceab94d..5bf97e7336f 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs @@ -83,5 +83,17 @@ public override Task Nested_SelectMany_correlated_with_join_table_correctly_tran { return base.Nested_SelectMany_correlated_with_join_table_correctly_translated_to_apply(async); } + + [ConditionalTheory(Skip = "issue #19742")] + public override Task Contains_over_optional_navigation_with_null_column(bool async) + { + return base.Contains_over_optional_navigation_with_null_column(async); + } + + [ConditionalTheory(Skip = "issue #19742")] + public override Task Contains_over_optional_navigation_with_null_entity_reference(bool async) + { + return base.Contains_over_optional_navigation_with_null_entity_reference(async); + } } } diff --git a/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs index 59949392329..a38970d24da 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs @@ -1229,6 +1229,24 @@ public virtual async Task String_concat_with_both_arguments_being_null(bool asyn await AssertQuery(async, ss => ss.Set().Select(x => x.NullableStringB + x.NullableStringA)); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Empty_subquery_with_contains_returns_false(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(e => ss.Set().Where(x => false).Select(x => x.NullableIntA).Contains(e.NullableIntA))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Empty_subquery_with_contains_negated_returns_true(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(e => !ss.Set().Where(x => false).Select(x => x.NullableIntA).Contains(e.NullableIntA))); + } + private string NormalizeDelimitersInRawString(string sql) => Fixture.TestStore.NormalizeDelimitersInRawString(sql); diff --git a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs index 558fb2420c8..2f9a7699522 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs @@ -2452,6 +2452,16 @@ public virtual Task Contains_with_subquery_optional_navigation_and_constant_item l1 => l1.OneToOne_Optional_FK1.OneToMany_Optional2.MaybeScalar(x => x.Distinct().Select(l3 => l3.Id).Contains(1)) == true)); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_with_subquery_optional_navigation_scalar_distinct_and_constant_item(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(l1 => l1.OneToOne_Optional_FK1.OneToMany_Optional2.Select(l3 => l3.Name.Length).Distinct().Contains(1)), + ss => ss.Set().Where(l1 => l1.OneToOne_Optional_FK1.OneToMany_Optional2.MaybeScalar(x => x.Select(l3 => l3.Name.Length).Distinct().Contains(1)) == true)); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual async Task Complex_query_with_optional_navigations_and_client_side_evaluation(bool async) @@ -4920,5 +4930,55 @@ public virtual Task Nested_SelectMany_correlated_with_join_table_correctly_trans l2 => l2.OneToOne_Required_PK2.OneToMany_Optional3.DefaultIfEmpty() .Select(l4 => new { l1Name = l1.Name, l2Name = l2.OneToOne_Required_PK2.Name, l3Name = l4.OneToOne_Optional_PK_Inverse4.Name })))); } + + [ConditionalFact] + public virtual void Contains_over_optional_navigation_with_null_constant() + { + using var ctx = CreateContext(); + var result = ctx.Set().Select(l1 => l1.OneToOne_Optional_FK1).Contains(null); + var expected = Fixture.QueryAsserter.ExpectedData.Set().Select(l1 => l1.OneToOne_Optional_FK1).Contains(null); + + Assert.Equal(expected, result); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_over_optional_navigation_with_null_parameter(bool async) + { + return AssertContains( + async, + ss => ss.Set().Select(l1 => l1.OneToOne_Optional_FK1), + null); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_over_optional_navigation_with_null_column(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Select(l1 => new + { + l1.Name, + OptionalName = l1.OneToOne_Optional_FK1.Name, + Contains = ss.Set().Select(x => x.OneToOne_Optional_FK1.Name).Contains(l1.OneToOne_Optional_FK1.Name) + }), + elementSorter: e => (e.Name, e.OptionalName, e.Contains)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_over_optional_navigation_with_null_entity_reference(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Select(l1 => new + { + l1.Name, + OptionalName = l1.OneToOne_Optional_FK1.Name, + Contains = ss.Set().Select(x => x.OneToOne_Optional_FK1).Contains(l1.OneToOne_Optional_PK1) + }), + elementSorter: e => (e.Name, e.OptionalName, e.Contains)); + } } } diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryFixtureBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryFixtureBase.cs index e0f59787cce..2884dd1fdc1 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryFixtureBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryFixtureBase.cs @@ -162,6 +162,9 @@ protected GearsOfWarQueryFixtureBase() Assert.Equal(ee.Name, aa.Name); Assert.Equal(ee.ThreatLevel, aa.ThreatLevel); + Assert.Equal(ee.ThreatLevelByte, aa.ThreatLevelByte); + Assert.Equal(ee.ThreatLevelNullableByte, aa.ThreatLevelNullableByte); + if (e is LocustCommander locustCommander) { var actualLocustCommander = (LocustCommander)aa; @@ -183,6 +186,8 @@ protected GearsOfWarQueryFixtureBase() Assert.Equal(ee.Name, aa.Name); Assert.Equal(ee.ThreatLevel, aa.ThreatLevel); + Assert.Equal(ee.ThreatLevelByte, aa.ThreatLevelByte); + Assert.Equal(ee.ThreatLevelNullableByte, aa.ThreatLevelNullableByte); Assert.Equal(ee.DefeatedByNickname, aa.DefeatedByNickname); Assert.Equal(ee.DefeatedBySquadId, aa.DefeatedBySquadId); } diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index 969c74280a0..ddd7f472a18 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -7326,6 +7326,108 @@ public virtual Task Where_TimeSpan_Milliseconds(bool async) .Where(m => m.Duration.Milliseconds == 1)); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_on_collection_of_byte_subquery(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(l => ss.Set().Select(ll => ll.ThreatLevelByte).Contains(l.ThreatLevelByte))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_on_collection_of_nullable_byte_subquery(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(l => ss.Set().Select(ll => ll.ThreatLevelNullableByte).Contains(l.ThreatLevelNullableByte))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_on_collection_of_nullable_byte_subquery_null_constant(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(l => ss.Set().Select(ll => ll.ThreatLevelNullableByte).Contains(null))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_on_collection_of_nullable_byte_subquery_null_parameter(bool async) + { + var prm = default(byte?); + + return AssertQuery( + async, + ss => ss.Set().Where(l => ss.Set().Select(ll => ll.ThreatLevelNullableByte).Contains(prm))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_on_byte_array_property_using_byte_column(bool async) + { + return AssertQuery( + async, + ss => from s in ss.Set() + from l in ss.Set() + where s.Banner.Contains(l.ThreatLevelByte) + select new { s, l }, + elementSorter: e => (e.s.Id, e.l.Name), + elementAsserter: (e, a) => + { + AssertEqual(e.s, a.s); + AssertEqual(e.l, a.l); + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Subquery_projecting_non_nullable_scalar_contains_non_nullable_value_doesnt_need_null_expansion(bool async) + { + return AssertQuery( + async, + ss => ss.Set() + .SelectMany(l => ss.Set() + .Where(g => ss.Set().Select(x => x.ThreatLevelByte).Contains(l.ThreatLevelByte)))); + } + + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Subquery_projecting_non_nullable_scalar_contains_non_nullable_value_doesnt_need_null_expansion_negated(bool async) + { + return AssertQuery( + async, + ss => ss.Set() + .SelectMany(l => ss.Set() + .Where(g => !ss.Set().Select(x => x.ThreatLevelByte).Contains(l.ThreatLevelByte)))); + } + + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Subquery_projecting_nullable_scalar_contains_nullable_value_needs_null_expansion(bool async) + { + return AssertQuery( + async, + ss => ss.Set() + .SelectMany(l => ss.Set() + .Where(g => ss.Set().Select(x => x.ThreatLevelNullableByte).Contains(l.ThreatLevelNullableByte)))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Subquery_projecting_nullable_scalar_contains_nullable_value_needs_null_expansion_negated(bool async) + { + return AssertQuery( + async, + ss => ss.Set() + .SelectMany(l => ss.Set() + .Where(g => !ss.Set().Select(x => x.ThreatLevelNullableByte).Contains(l.ThreatLevelNullableByte)))); + } + protected GearsOfWarContext CreateContext() => Fixture.CreateContext(); protected virtual void ClearLog() diff --git a/test/EFCore.Specification.Tests/Query/NorthwindAggregateOperatorsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindAggregateOperatorsQueryTestBase.cs index 1cc015fd53f..ec8b55fd2e0 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindAggregateOperatorsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindAggregateOperatorsQueryTestBase.cs @@ -1551,26 +1551,92 @@ public virtual void Contains_over_keyless_entity_throws() Assert.Throws(() => context.CustomerQueries.Contains(new CustomerView())); } - [ConditionalFact] - public virtual void Contains_over_entityType_with_null_should_rewrite_to_identity_equality() + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_over_entityType_with_null_should_rewrite_to_false(bool async) { - using var context = CreateContext(); - var query - = context.Orders.Where(o => o.CustomerID == "VINET") - .Contains(null); + return AssertContains( + async, + ss => ss.Set().Where(o => o.CustomerID == "VINET"), + null); + } - Assert.False(query); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_over_entityType_with_null_should_rewrite_to_identity_equality_subquery(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(o => ss.Set().Where(o => o.CustomerID == "VINET").Contains(null))); } - [ConditionalFact] - public virtual void Contains_over_entityType_should_materialize_when_composite() + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_over_scalar_with_null_should_rewrite_to_identity_equality_subquery(bool async) { - using var context = CreateContext(); - Assert.Equal( - "Cannot translate a Contains() operator on entity 'OrderDetail' because it has a composite key.", - Assert.Throws( - () => context.OrderDetails.Where(o => o.ProductID == 42) - .Contains(context.OrderDetails.First(o => o.OrderID == 10248 && o.ProductID == 42))).Message); + return AssertQuery( + async, + ss => ss.Set().Where(o => ss.Set().Where(o => o.CustomerID == "VINET").Select(o => o.CustomerID).Contains(null))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_over_entityType_with_null_should_rewrite_to_identity_equality_subquery_negated(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(o => !ss.Set().Where(o => o.CustomerID == "VINET").Select(o => o.CustomerID).Contains(null)), + entryCount: 830); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_over_entityType_with_null_should_rewrite_to_identity_equality_subquery_complex(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(o => ss.Set().Where(o => o.CustomerID == "VINET").Select(o => o.CustomerID) + .Contains(null) == ss.Set().Where(o => o.CustomerID != "VINET").Select(o => o.CustomerID) + .Contains(null)), + entryCount: 830); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_over_nullable_scalar_with_null_in_subquery_translated_correctly(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => ss.Set().Where(o => o.CustomerID == "VINET").Select(o => o.CustomerID).Contains(null))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_over_non_nullable_scalar_with_null_in_subquery_simplifies_to_false(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => ss.Set().Where(o => o.CustomerID != "VINET").Select(o => o.CustomerID).Contains(null))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_over_entityType_should_materialize_when_composite(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(o => o.ProductID == 42 && ss.Set().Contains(o)), + entryCount: 30); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_over_entityType_should_materialize_when_composite2(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(o => o.ProductID == 42 && ss.Set().Where(x => x.OrderID > 42).Contains(o)), + entryCount: 30); } [ConditionalTheory] diff --git a/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs index de7415846b0..ac640fb6ba5 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs @@ -2090,6 +2090,19 @@ public virtual Task Where_Queryable_AsEnumerable_Contains(bool async) .Where(e => e.Contains("ALFKI"))); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Where_Queryable_AsEnumerable_Contains_negated(bool async) + { + return AssertQuery( + async, + ss => ss.Set() + .Select(c => new { c.CustomerID, Subquery = ss.Set().Where(o => o.CustomerID == c.CustomerID).Select(o => o.CustomerID).AsEnumerable() }) + .Where(e => !e.Subquery.Contains("ALFKI")), + elementSorter: e => e.CustomerID, + elementAsserter: (e, a) => AssertCollection(e.Subquery, a.Subquery)); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Where_Queryable_ToList_Count_member(bool async) diff --git a/test/EFCore.Specification.Tests/Query/QueryTestBase.cs b/test/EFCore.Specification.Tests/Query/QueryTestBase.cs index aa7f3c4d078..5fa2f444eee 100644 --- a/test/EFCore.Specification.Tests/Query/QueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/QueryTestBase.cs @@ -1102,6 +1102,23 @@ protected Task AssertAverage( => Fixture.QueryAsserter.AssertAverage( actualQuery, expectedQuery, actualSelector, expectedSelector, asserter, async); + protected Task AssertContains( + bool async, + Func> query, + TElement element, + Action asserter = null) + => AssertContains(async, query, query, element, element, asserter); + + protected Task AssertContains( + bool async, + Func> actualQuery, + Func> expectedQuery, + TElement actualElement, + TElement expectedElement, + Action asserter = null) + => Fixture.QueryAsserter.AssertContains( + actualQuery, expectedQuery, actualElement, expectedElement, asserter, async); + #endregion #region Helpers diff --git a/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/GearsOfWarData.cs b/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/GearsOfWarData.cs index 9cb8363e760..230d80d0b52 100644 --- a/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/GearsOfWarData.cs +++ b/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/GearsOfWarData.cs @@ -324,12 +324,12 @@ public static IReadOnlyList CreateGears() public static IReadOnlyList CreateLocustLeaders() => new List { - new LocustLeader { Name = "General Karn", ThreatLevel = 3 }, - new LocustLeader { Name = "General RAAM", ThreatLevel = 4 }, - new LocustLeader { Name = "High Priest Skorge", ThreatLevel = 1 }, - new LocustCommander { Name = "Queen Myrrah", ThreatLevel = 5 }, - new LocustLeader { Name = "The Speaker", ThreatLevel = 3 }, - new LocustCommander { Name = "Unknown", ThreatLevel = 0 } + new LocustLeader { Name = "General Karn", ThreatLevel = 3, ThreatLevelByte = 3, ThreatLevelNullableByte = 3 }, + new LocustLeader { Name = "General RAAM", ThreatLevel = 4, ThreatLevelByte = 4, ThreatLevelNullableByte = 4 }, + new LocustLeader { Name = "High Priest Skorge", ThreatLevel = 1, ThreatLevelByte = 1, ThreatLevelNullableByte = 1 }, + new LocustCommander { Name = "Queen Myrrah", ThreatLevel = 5, ThreatLevelByte = 5, ThreatLevelNullableByte = 5 }, + new LocustLeader { Name = "The Speaker", ThreatLevel = 3, ThreatLevelByte = 3, ThreatLevelNullableByte = 3 }, + new LocustCommander { Name = "Unknown", ThreatLevel = 0, ThreatLevelByte = 0, ThreatLevelNullableByte = null } }; public static IReadOnlyList CreateFactions() diff --git a/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/LocustLeader.cs b/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/LocustLeader.cs index b3861e00065..00051b249b0 100644 --- a/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/LocustLeader.cs +++ b/test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/LocustLeader.cs @@ -7,5 +7,7 @@ public class LocustLeader { public string Name { get; set; } public short ThreatLevel { get; set; } + public byte ThreatLevelByte { get; set; } + public byte? ThreatLevelNullableByte { get; set; } } } diff --git a/test/EFCore.Specification.Tests/TestUtilities/QueryAsserter.cs b/test/EFCore.Specification.Tests/TestUtilities/QueryAsserter.cs index 3c935667927..0ab093e1c47 100644 --- a/test/EFCore.Specification.Tests/TestUtilities/QueryAsserter.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/QueryAsserter.cs @@ -1473,6 +1473,25 @@ public override async Task AssertAverage( Assert.Empty(context.ChangeTracker.Entries()); } + public override async Task AssertContains( + Func> actualQuery, + Func> expectedQuery, + TElement actualElement, + TElement expectedElement, + Action asserter = null, + bool async = false) + { + using var context = _contextCreator(); + var actual = async + ? await actualQuery(SetSourceCreator(context)).ContainsAsync(actualElement) + : actualQuery(SetSourceCreator(context)).Contains(actualElement); + + var expected = expectedQuery(ExpectedData).Contains(expectedElement); + + AssertEqual(expected, actual, asserter); + Assert.Empty(context.ChangeTracker.Entries()); + } + #endregion #region Helpers diff --git a/test/EFCore.Specification.Tests/TestUtilities/QueryAsserterBase.cs b/test/EFCore.Specification.Tests/TestUtilities/QueryAsserterBase.cs index 3874ec23c8c..bda272fbc92 100644 --- a/test/EFCore.Specification.Tests/TestUtilities/QueryAsserterBase.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/QueryAsserterBase.cs @@ -515,6 +515,14 @@ public abstract Task AssertAverage( Action asserter = null, bool async = false); + public abstract Task AssertContains( + Func> actualQuery, + Func> expectedQuery, + TElement actualElement, + TElement expectedElement, + Action asserter = null, + bool async = false); + #endregion #region Helpers diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs index 0ff6dac3d5b..cca5bd3da88 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs @@ -2200,14 +2200,24 @@ public override async Task Contains_with_subquery_optional_navigation_and_consta @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id] FROM [LevelOne] AS [l] LEFT JOIN [LevelTwo] AS [l0] ON [l].[Id] = [l0].[Level1_Optional_Id] -WHERE 1 IN ( - SELECT [t].[Id] - FROM ( - SELECT DISTINCT [l1].[Id], [l1].[Level2_Optional_Id], [l1].[Level2_Required_Id], [l1].[Name], [l1].[OneToMany_Optional_Inverse3Id], [l1].[OneToMany_Optional_Self_Inverse3Id], [l1].[OneToMany_Required_Inverse3Id], [l1].[OneToMany_Required_Self_Inverse3Id], [l1].[OneToOne_Optional_PK_Inverse3Id], [l1].[OneToOne_Optional_Self3Id] - FROM [LevelThree] AS [l1] - WHERE [l0].[Id] IS NOT NULL AND ([l0].[Id] = [l1].[OneToMany_Optional_Inverse3Id]) - ) AS [t] -)"); +WHERE EXISTS ( + SELECT DISTINCT 1 + FROM [LevelThree] AS [l1] + WHERE ([l0].[Id] IS NOT NULL AND ([l0].[Id] = [l1].[OneToMany_Optional_Inverse3Id])) AND ([l1].[Id] = 1))"); + } + + public override async Task Contains_with_subquery_optional_navigation_scalar_distinct_and_constant_item(bool async) + { + await base.Contains_with_subquery_optional_navigation_scalar_distinct_and_constant_item(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id] +FROM [LevelOne] AS [l] +LEFT JOIN [LevelTwo] AS [l0] ON [l].[Id] = [l0].[Level1_Optional_Id] +WHERE EXISTS ( + SELECT DISTINCT 1 + FROM [LevelThree] AS [l1] + WHERE ([l0].[Id] IS NOT NULL AND ([l0].[Id] = [l1].[OneToMany_Optional_Inverse3Id])) AND (CAST(LEN([l1].[Name]) AS int) = 1))"); } public override async Task Required_navigation_on_a_subquery_with_First_in_projection(bool async) @@ -4423,6 +4433,71 @@ public override async Task Nested_SelectMany_correlated_with_join_table_correctl @""); } + public override void Contains_over_optional_navigation_with_null_constant() + { + base.Contains_over_optional_navigation_with_null_constant(); + + AssertSql( + @"SELECT CASE + WHEN EXISTS ( + SELECT 1 + FROM [LevelOne] AS [l] + LEFT JOIN [LevelTwo] AS [l0] ON [l].[Id] = [l0].[Level1_Optional_Id] + WHERE [l0].[Id] IS NULL) THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END"); + } + + public override async Task Contains_over_optional_navigation_with_null_parameter(bool async) + { + await base.Contains_over_optional_navigation_with_null_parameter(async); + + AssertSql( + @"SELECT CASE + WHEN EXISTS ( + SELECT 1 + FROM [LevelOne] AS [l] + LEFT JOIN [LevelTwo] AS [l0] ON [l].[Id] = [l0].[Level1_Optional_Id] + WHERE [l0].[Id] IS NULL) THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END"); + } + + public override async Task Contains_over_optional_navigation_with_null_column(bool async) + { + await base.Contains_over_optional_navigation_with_null_column(async); + + AssertSql( + @"SELECT [l1].[Name], [l2].[Name] AS [OptionalName], CASE + WHEN EXISTS ( + SELECT 1 + FROM [LevelOne] AS [l] + LEFT JOIN [LevelTwo] AS [l0] ON [l].[Id] = [l0].[Level1_Optional_Id] + WHERE ([l0].[Name] = [l2].[Name]) OR ([l0].[Name] IS NULL AND [l2].[Name] IS NULL)) THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END AS [Contains] +FROM [LevelOne] AS [l1] +LEFT JOIN [LevelTwo] AS [l2] ON [l1].[Id] = [l2].[Level1_Optional_Id]"); + } + + public override async Task Contains_over_optional_navigation_with_null_entity_reference(bool async) + { + await base.Contains_over_optional_navigation_with_null_entity_reference(async); + + AssertSql( + @"SELECT [l1].[Name], [l2].[Name] AS [OptionalName], CASE + WHEN EXISTS ( + SELECT 1 + FROM [LevelOne] AS [l] + LEFT JOIN [LevelTwo] AS [l0] ON [l].[Id] = [l0].[Level1_Optional_Id] + WHERE ([l0].[Id] = [l3].[Id]) OR ([l0].[Id] IS NULL AND [l3].[Id] IS NULL)) THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END AS [Contains] +FROM [LevelOne] AS [l1] +LEFT JOIN [LevelTwo] AS [l2] ON [l1].[Id] = [l2].[Level1_Optional_Id] +LEFT JOIN [LevelTwo] AS [l3] ON [l1].[Id] = [l3].[OneToOne_Optional_PK_Inverse2Id]"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlQuerySqlServerTest.cs index 1500fdff948..db766652466 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/FromSqlQuerySqlServerTest.cs @@ -135,12 +135,12 @@ public override void FromSqlRaw_composed_contains() AssertSql( @"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].[CustomerID] IN ( - SELECT [o].[CustomerID] +WHERE EXISTS ( + SELECT 1 FROM ( SELECT * FROM ""Orders"" ) AS [o] -)"); + WHERE [o].[CustomerID] = [c].[CustomerID])"); } public override void FromSqlRaw_composed_contains2() @@ -150,12 +150,12 @@ public override void FromSqlRaw_composed_contains2() AssertSql( @"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].[CustomerID] = N'ALFKI') AND [c].[CustomerID] IN ( - SELECT [o].[CustomerID] +WHERE ([c].[CustomerID] = N'ALFKI') AND EXISTS ( + SELECT 1 FROM ( SELECT * FROM ""Orders"" ) AS [o] -)"); + WHERE [o].[CustomerID] = [c].[CustomerID])"); } public override void FromSqlRaw_queryable_multiple_composed() @@ -581,12 +581,12 @@ public virtual void FromSqlRaw_in_subquery_with_dbParameter() SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE [o].[CustomerID] IN ( - SELECT [c].[CustomerID] +WHERE EXISTS ( + SELECT 1 FROM ( SELECT * FROM ""Customers"" WHERE ""City"" = @city ) AS [c] -)"); + WHERE [c].[CustomerID] = [o].[CustomerID])"); } [ConditionalFact] @@ -611,12 +611,12 @@ public virtual void FromSqlRaw_in_subquery_with_positional_dbParameter_without_n SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE [o].[CustomerID] IN ( - SELECT [c].[CustomerID] +WHERE EXISTS ( + SELECT 1 FROM ( SELECT * FROM ""Customers"" WHERE ""City"" = @p0 ) AS [c] -)"); + WHERE [c].[CustomerID] = [o].[CustomerID])"); } [ConditionalFact] @@ -641,12 +641,12 @@ public virtual void FromSqlRaw_in_subquery_with_positional_dbParameter_with_name SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE [o].[CustomerID] IN ( - SELECT [c].[CustomerID] +WHERE EXISTS ( + SELECT 1 FROM ( SELECT * FROM ""Customers"" WHERE ""City"" = @city ) AS [c] -)"); + WHERE [c].[CustomerID] = [o].[CustomerID])"); } [ConditionalFact] @@ -690,24 +690,24 @@ public virtual void FromSqlRaw_with_dbParameter_mixed_in_subquery() SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE [o].[CustomerID] IN ( - SELECT [c].[CustomerID] +WHERE EXISTS ( + SELECT 1 FROM ( SELECT * FROM ""Customers"" WHERE ""City"" = @p0 AND ""ContactTitle"" = @title ) AS [c] -)", + WHERE [c].[CustomerID] = [o].[CustomerID])", // @"@city='London' (Nullable = false) (Size = 6) p1='Sales Representative' (Size = 4000) SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE [o].[CustomerID] IN ( - SELECT [c].[CustomerID] +WHERE EXISTS ( + SELECT 1 FROM ( SELECT * FROM ""Customers"" WHERE ""City"" = @city AND ""ContactTitle"" = @p1 ) AS [c] -)"); + WHERE [c].[CustomerID] = [o].[CustomerID])"); } public override void FromSqlInterpolated_parameterization_issue_12213() @@ -724,24 +724,24 @@ public override void FromSqlInterpolated_parameterization_issue_12213() SELECT [o].[OrderID] FROM [Orders] AS [o] -WHERE ([o].[OrderID] <= @__max_0) AND [o].[OrderID] IN ( - SELECT [o0].[OrderID] +WHERE ([o].[OrderID] <= @__max_0) AND EXISTS ( + SELECT 1 FROM ( SELECT * FROM ""Orders"" WHERE ""OrderID"" >= @p0 ) AS [o0] -)", + WHERE [o0].[OrderID] = [o].[OrderID])", // @"@__max_0='10400' p0='10300' SELECT [o].[OrderID] FROM [Orders] AS [o] -WHERE ([o].[OrderID] <= @__max_0) AND [o].[OrderID] IN ( - SELECT [o0].[OrderID] +WHERE ([o].[OrderID] <= @__max_0) AND EXISTS ( + SELECT 1 FROM ( SELECT * FROM ""Orders"" WHERE ""OrderID"" >= @p0 ) AS [o0] -)"); + WHERE [o0].[OrderID] = [o].[OrderID])"); } public override void FromSqlRaw_does_not_parameterize_interpolated_string() diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index 3bab0d87056..bed7165dc42 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -2364,11 +2364,10 @@ LEFT JOIN ( FROM [Gears] AS [g] WHERE [g].[Discriminator] IN (N'Gear', N'Officer') ) AS [t0] ON ([t].[GearNickName] = [t0].[Nickname]) AND ([t].[GearSquadId] = [t0].[SquadId]) -WHERE (([t].[Note] <> N'K.I.A.') OR [t].[Note] IS NULL) AND [t0].[SquadId] IN ( - SELECT [g0].[SquadId] +WHERE (([t].[Note] <> N'K.I.A.') OR [t].[Note] IS NULL) AND EXISTS ( + SELECT 1 FROM [Gears] AS [g0] - WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') -)"); + WHERE [g0].[Discriminator] IN (N'Gear', N'Officer') AND ([g0].[SquadId] = [t0].[SquadId]))"); } public override async Task Optional_navigation_type_compensation_works_with_skip(bool async) @@ -3255,7 +3254,7 @@ public override async Task Navigation_access_on_derived_entity_using_cast(bool a @"SELECT [f].[Name], [t].[ThreatLevel] AS [Threat] FROM [Factions] AS [f] LEFT JOIN ( - SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] + SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] AS [l] WHERE [l].[Discriminator] = N'LocustCommander' ) AS [t] ON [f].[CommanderName] = [t].[Name] @@ -3271,7 +3270,7 @@ public override async Task Navigation_access_on_derived_materialized_entity_usin @"SELECT [f].[Id], [f].[CapitalName], [f].[Discriminator], [f].[Name], [f].[CommanderName], [f].[Eradicated], [t].[ThreatLevel] AS [Threat] FROM [Factions] AS [f] LEFT JOIN ( - SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] + SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] AS [l] WHERE [l].[Discriminator] = N'LocustCommander' ) AS [t] ON [f].[CommanderName] = [t].[Name] @@ -3287,7 +3286,7 @@ public override async Task Navigation_access_via_EFProperty_on_derived_entity_us @"SELECT [f].[Name], [t].[ThreatLevel] AS [Threat] FROM [Factions] AS [f] LEFT JOIN ( - SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] + SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] AS [l] WHERE [l].[Discriminator] = N'LocustCommander' ) AS [t] ON [f].[CommanderName] = [t].[Name] @@ -3303,7 +3302,7 @@ public override async Task Navigation_access_fk_on_derived_entity_using_cast(boo @"SELECT [f].[Name], [t].[Name] AS [CommanderName] FROM [Factions] AS [f] LEFT JOIN ( - SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] + SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] AS [l] WHERE [l].[Discriminator] = N'LocustCommander' ) AS [t] ON [f].[CommanderName] = [t].[Name] @@ -3333,7 +3332,7 @@ public override async Task Collection_navigation_access_on_derived_entity_using_ @"SELECT [f].[Name], [t].[Name] AS [LeaderName] FROM [Factions] AS [f] INNER JOIN ( - SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] + SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] AS [l] WHERE [l].[Discriminator] IN (N'LocustLeader', N'LocustCommander') ) AS [t] ON [f].[Id] = [t].[LocustHordeId] @@ -3346,15 +3345,15 @@ public override async Task Include_on_derived_entity_using_OfType(bool async) await base.Include_on_derived_entity_using_OfType(async); AssertSql( - @"SELECT [f].[Id], [f].[CapitalName], [f].[Discriminator], [f].[Name], [f].[CommanderName], [f].[Eradicated], [t].[Name], [t].[Discriminator], [t].[LocustHordeId], [t].[ThreatLevel], [t].[DefeatedByNickname], [t].[DefeatedBySquadId], [t].[HighCommandId], [t0].[Name], [t0].[Discriminator], [t0].[LocustHordeId], [t0].[ThreatLevel], [t0].[DefeatedByNickname], [t0].[DefeatedBySquadId], [t0].[HighCommandId] + @"SELECT [f].[Id], [f].[CapitalName], [f].[Discriminator], [f].[Name], [f].[CommanderName], [f].[Eradicated], [t].[Name], [t].[Discriminator], [t].[LocustHordeId], [t].[ThreatLevel], [t].[ThreatLevelByte], [t].[ThreatLevelNullableByte], [t].[DefeatedByNickname], [t].[DefeatedBySquadId], [t].[HighCommandId], [t0].[Name], [t0].[Discriminator], [t0].[LocustHordeId], [t0].[ThreatLevel], [t0].[ThreatLevelByte], [t0].[ThreatLevelNullableByte], [t0].[DefeatedByNickname], [t0].[DefeatedBySquadId], [t0].[HighCommandId] FROM [Factions] AS [f] LEFT JOIN ( - SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] + SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] AS [l] WHERE [l].[Discriminator] = N'LocustCommander' ) AS [t] ON [f].[CommanderName] = [t].[Name] LEFT JOIN ( - SELECT [l0].[Name], [l0].[Discriminator], [l0].[LocustHordeId], [l0].[ThreatLevel], [l0].[DefeatedByNickname], [l0].[DefeatedBySquadId], [l0].[HighCommandId] + SELECT [l0].[Name], [l0].[Discriminator], [l0].[LocustHordeId], [l0].[ThreatLevel], [l0].[ThreatLevelByte], [l0].[ThreatLevelNullableByte], [l0].[DefeatedByNickname], [l0].[DefeatedBySquadId], [l0].[HighCommandId] FROM [LocustLeaders] AS [l0] WHERE [l0].[Discriminator] IN (N'LocustLeader', N'LocustCommander') ) AS [t0] ON [f].[Id] = [t0].[LocustHordeId] @@ -3511,7 +3510,7 @@ FROM [Gears] AS [g] WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ([g].[Discriminator] = N'Officer') ) AS [t] LEFT JOIN ( - SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] + SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] AS [l] WHERE [l].[Discriminator] = N'LocustCommander' ) AS [t0] ON [f].[CommanderName] = [t0].[Name] @@ -3599,10 +3598,10 @@ public override async Task Project_collection_navigation_with_inheritance1(bool await base.Project_collection_navigation_with_inheritance1(async); AssertSql( - @"SELECT [f].[Id], [t1].[Name], [t1].[Discriminator], [t1].[LocustHordeId], [t1].[ThreatLevel], [t1].[DefeatedByNickname], [t1].[DefeatedBySquadId], [t1].[HighCommandId] + @"SELECT [f].[Id], [t1].[Name], [t1].[Discriminator], [t1].[LocustHordeId], [t1].[ThreatLevel], [t1].[ThreatLevelByte], [t1].[ThreatLevelNullableByte], [t1].[DefeatedByNickname], [t1].[DefeatedBySquadId], [t1].[HighCommandId] FROM [Factions] AS [f] LEFT JOIN ( - SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] + SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] AS [l] WHERE [l].[Discriminator] = N'LocustCommander' ) AS [t] ON [f].[CommanderName] = [t].[Name] @@ -3612,7 +3611,7 @@ FROM [Factions] AS [f0] WHERE [f0].[Discriminator] = N'LocustHorde' ) AS [t0] ON [t].[Name] = [t0].[CommanderName] LEFT JOIN ( - SELECT [l0].[Name], [l0].[Discriminator], [l0].[LocustHordeId], [l0].[ThreatLevel], [l0].[DefeatedByNickname], [l0].[DefeatedBySquadId], [l0].[HighCommandId] + SELECT [l0].[Name], [l0].[Discriminator], [l0].[LocustHordeId], [l0].[ThreatLevel], [l0].[ThreatLevelByte], [l0].[ThreatLevelNullableByte], [l0].[DefeatedByNickname], [l0].[DefeatedBySquadId], [l0].[HighCommandId] FROM [LocustLeaders] AS [l0] WHERE [l0].[Discriminator] IN (N'LocustLeader', N'LocustCommander') ) AS [t1] ON [t0].[Id] = [t1].[LocustHordeId] @@ -3628,7 +3627,7 @@ public override async Task Project_collection_navigation_with_inheritance2(bool @"SELECT [f].[Id], [t1].[Nickname], [t1].[SquadId], [t1].[AssignedCityName], [t1].[CityOfBirthName], [t1].[Discriminator], [t1].[FullName], [t1].[HasSoulPatch], [t1].[LeaderNickname], [t1].[LeaderSquadId], [t1].[Rank] FROM [Factions] AS [f] LEFT JOIN ( - SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] + SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] AS [l] WHERE [l].[Discriminator] = N'LocustCommander' ) AS [t] ON [f].[CommanderName] = [t].[Name] @@ -3654,7 +3653,7 @@ public override async Task Project_collection_navigation_with_inheritance3(bool @"SELECT [f].[Id], [t1].[Nickname], [t1].[SquadId], [t1].[AssignedCityName], [t1].[CityOfBirthName], [t1].[Discriminator], [t1].[FullName], [t1].[HasSoulPatch], [t1].[LeaderNickname], [t1].[LeaderSquadId], [t1].[Rank] FROM [Factions] AS [f] LEFT JOIN ( - SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] + SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] AS [l] WHERE [l].[Discriminator] = N'LocustCommander' ) AS [t] ON [f].[CommanderName] = [t].[Name] @@ -3677,7 +3676,7 @@ public override async Task Include_reference_on_derived_type_using_string(bool a await base.Include_reference_on_derived_type_using_string(async); AssertSql( - @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId], [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[Discriminator], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank] + @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId], [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[Discriminator], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank] FROM [LocustLeaders] AS [l] LEFT JOIN ( SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] @@ -3692,7 +3691,7 @@ public override async Task Include_reference_on_derived_type_using_string_nested await base.Include_reference_on_derived_type_using_string_nested1(async); AssertSql( - @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId], [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[Discriminator], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank], [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] + @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId], [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[Discriminator], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank], [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] FROM [LocustLeaders] AS [l] LEFT JOIN ( SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] @@ -3708,7 +3707,7 @@ public override async Task Include_reference_on_derived_type_using_string_nested await base.Include_reference_on_derived_type_using_string_nested2(async); AssertSql( - @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId], [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[Discriminator], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank], [t0].[Nickname], [t0].[SquadId], [t0].[AssignedCityName], [t0].[CityOfBirthName], [t0].[Discriminator], [t0].[FullName], [t0].[HasSoulPatch], [t0].[LeaderNickname], [t0].[LeaderSquadId], [t0].[Rank], [t0].[Name], [t0].[Location], [t0].[Nation] + @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId], [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[Discriminator], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank], [t0].[Nickname], [t0].[SquadId], [t0].[AssignedCityName], [t0].[CityOfBirthName], [t0].[Discriminator], [t0].[FullName], [t0].[HasSoulPatch], [t0].[LeaderNickname], [t0].[LeaderSquadId], [t0].[Rank], [t0].[Name], [t0].[Location], [t0].[Nation] FROM [LocustLeaders] AS [l] LEFT JOIN ( SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] @@ -3730,7 +3729,7 @@ public override async Task Include_reference_on_derived_type_using_lambda(bool a await base.Include_reference_on_derived_type_using_lambda(async); AssertSql( - @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId], [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[Discriminator], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank] + @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId], [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[Discriminator], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank] FROM [LocustLeaders] AS [l] LEFT JOIN ( SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] @@ -3745,7 +3744,7 @@ public override async Task Include_reference_on_derived_type_using_lambda_with_s await base.Include_reference_on_derived_type_using_lambda_with_soft_cast(async); AssertSql( - @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId], [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[Discriminator], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank] + @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId], [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[Discriminator], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank] FROM [LocustLeaders] AS [l] LEFT JOIN ( SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] @@ -3760,7 +3759,7 @@ public override async Task Include_reference_on_derived_type_using_lambda_with_t await base.Include_reference_on_derived_type_using_lambda_with_tracking(async); AssertSql( - @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId], [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[Discriminator], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank] + @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId], [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[Discriminator], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank] FROM [LocustLeaders] AS [l] LEFT JOIN ( SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] @@ -3852,10 +3851,10 @@ public override async Task ThenInclude_collection_on_derived_after_derived_refer await base.ThenInclude_collection_on_derived_after_derived_reference(async); AssertSql( - @"SELECT [f].[Id], [f].[CapitalName], [f].[Discriminator], [f].[Name], [f].[CommanderName], [f].[Eradicated], [t].[Name], [t].[Discriminator], [t].[LocustHordeId], [t].[ThreatLevel], [t].[DefeatedByNickname], [t].[DefeatedBySquadId], [t].[HighCommandId], [t0].[Nickname], [t0].[SquadId], [t0].[AssignedCityName], [t0].[CityOfBirthName], [t0].[Discriminator], [t0].[FullName], [t0].[HasSoulPatch], [t0].[LeaderNickname], [t0].[LeaderSquadId], [t0].[Rank], [t1].[Nickname], [t1].[SquadId], [t1].[AssignedCityName], [t1].[CityOfBirthName], [t1].[Discriminator], [t1].[FullName], [t1].[HasSoulPatch], [t1].[LeaderNickname], [t1].[LeaderSquadId], [t1].[Rank] + @"SELECT [f].[Id], [f].[CapitalName], [f].[Discriminator], [f].[Name], [f].[CommanderName], [f].[Eradicated], [t].[Name], [t].[Discriminator], [t].[LocustHordeId], [t].[ThreatLevel], [t].[ThreatLevelByte], [t].[ThreatLevelNullableByte], [t].[DefeatedByNickname], [t].[DefeatedBySquadId], [t].[HighCommandId], [t0].[Nickname], [t0].[SquadId], [t0].[AssignedCityName], [t0].[CityOfBirthName], [t0].[Discriminator], [t0].[FullName], [t0].[HasSoulPatch], [t0].[LeaderNickname], [t0].[LeaderSquadId], [t0].[Rank], [t1].[Nickname], [t1].[SquadId], [t1].[AssignedCityName], [t1].[CityOfBirthName], [t1].[Discriminator], [t1].[FullName], [t1].[HasSoulPatch], [t1].[LeaderNickname], [t1].[LeaderSquadId], [t1].[Rank] FROM [Factions] AS [f] LEFT JOIN ( - SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] + SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] AS [l] WHERE [l].[Discriminator] = N'LocustCommander' ) AS [t] ON [f].[CommanderName] = [t].[Name] @@ -3899,10 +3898,10 @@ public override async Task ThenInclude_reference_on_derived_after_derived_collec await base.ThenInclude_reference_on_derived_after_derived_collection(async); AssertSql( - @"SELECT [f].[Id], [f].[CapitalName], [f].[Discriminator], [f].[Name], [f].[CommanderName], [f].[Eradicated], [t0].[Name], [t0].[Discriminator], [t0].[LocustHordeId], [t0].[ThreatLevel], [t0].[DefeatedByNickname], [t0].[DefeatedBySquadId], [t0].[HighCommandId], [t0].[Nickname], [t0].[SquadId], [t0].[AssignedCityName], [t0].[CityOfBirthName], [t0].[Discriminator0], [t0].[FullName], [t0].[HasSoulPatch], [t0].[LeaderNickname], [t0].[LeaderSquadId], [t0].[Rank] + @"SELECT [f].[Id], [f].[CapitalName], [f].[Discriminator], [f].[Name], [f].[CommanderName], [f].[Eradicated], [t0].[Name], [t0].[Discriminator], [t0].[LocustHordeId], [t0].[ThreatLevel], [t0].[ThreatLevelByte], [t0].[ThreatLevelNullableByte], [t0].[DefeatedByNickname], [t0].[DefeatedBySquadId], [t0].[HighCommandId], [t0].[Nickname], [t0].[SquadId], [t0].[AssignedCityName], [t0].[CityOfBirthName], [t0].[Discriminator0], [t0].[FullName], [t0].[HasSoulPatch], [t0].[LeaderNickname], [t0].[LeaderSquadId], [t0].[Rank] FROM [Factions] AS [f] LEFT JOIN ( - SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId], [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[Discriminator] AS [Discriminator0], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank] + SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId], [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[Discriminator] AS [Discriminator0], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank] FROM [LocustLeaders] AS [l] LEFT JOIN ( SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] @@ -3920,10 +3919,10 @@ public override async Task Multiple_derived_included_on_one_method(bool async) await base.Multiple_derived_included_on_one_method(async); AssertSql( - @"SELECT [f].[Id], [f].[CapitalName], [f].[Discriminator], [f].[Name], [f].[CommanderName], [f].[Eradicated], [t].[Name], [t].[Discriminator], [t].[LocustHordeId], [t].[ThreatLevel], [t].[DefeatedByNickname], [t].[DefeatedBySquadId], [t].[HighCommandId], [t0].[Nickname], [t0].[SquadId], [t0].[AssignedCityName], [t0].[CityOfBirthName], [t0].[Discriminator], [t0].[FullName], [t0].[HasSoulPatch], [t0].[LeaderNickname], [t0].[LeaderSquadId], [t0].[Rank], [t1].[Nickname], [t1].[SquadId], [t1].[AssignedCityName], [t1].[CityOfBirthName], [t1].[Discriminator], [t1].[FullName], [t1].[HasSoulPatch], [t1].[LeaderNickname], [t1].[LeaderSquadId], [t1].[Rank] + @"SELECT [f].[Id], [f].[CapitalName], [f].[Discriminator], [f].[Name], [f].[CommanderName], [f].[Eradicated], [t].[Name], [t].[Discriminator], [t].[LocustHordeId], [t].[ThreatLevel], [t].[ThreatLevelByte], [t].[ThreatLevelNullableByte], [t].[DefeatedByNickname], [t].[DefeatedBySquadId], [t].[HighCommandId], [t0].[Nickname], [t0].[SquadId], [t0].[AssignedCityName], [t0].[CityOfBirthName], [t0].[Discriminator], [t0].[FullName], [t0].[HasSoulPatch], [t0].[LeaderNickname], [t0].[LeaderSquadId], [t0].[Rank], [t1].[Nickname], [t1].[SquadId], [t1].[AssignedCityName], [t1].[CityOfBirthName], [t1].[Discriminator], [t1].[FullName], [t1].[HasSoulPatch], [t1].[LeaderNickname], [t1].[LeaderSquadId], [t1].[Rank] FROM [Factions] AS [f] LEFT JOIN ( - SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] + SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] AS [l] WHERE [l].[Discriminator] = N'LocustCommander' ) AS [t] ON [f].[CommanderName] = [t].[Name] @@ -4980,9 +4979,9 @@ public override async Task Include_on_derived_type_with_order_by_and_paging(bool AssertSql( @"@__p_0='10' -SELECT [t1].[Name], [t1].[Discriminator], [t1].[LocustHordeId], [t1].[ThreatLevel], [t1].[DefeatedByNickname], [t1].[DefeatedBySquadId], [t1].[HighCommandId], [t1].[Nickname], [t1].[SquadId], [t1].[AssignedCityName], [t1].[CityOfBirthName], [t1].[Discriminator0], [t1].[FullName], [t1].[HasSoulPatch], [t1].[LeaderNickname], [t1].[LeaderSquadId], [t1].[Rank], [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +SELECT [t1].[Name], [t1].[Discriminator], [t1].[LocustHordeId], [t1].[ThreatLevel], [t1].[ThreatLevelByte], [t1].[ThreatLevelNullableByte], [t1].[DefeatedByNickname], [t1].[DefeatedBySquadId], [t1].[HighCommandId], [t1].[Nickname], [t1].[SquadId], [t1].[AssignedCityName], [t1].[CityOfBirthName], [t1].[Discriminator0], [t1].[FullName], [t1].[HasSoulPatch], [t1].[LeaderNickname], [t1].[LeaderSquadId], [t1].[Rank], [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM ( - SELECT TOP(@__p_0) [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId], [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[Discriminator] AS [Discriminator0], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank], [t0].[Note] + SELECT TOP(@__p_0) [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId], [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[Discriminator] AS [Discriminator0], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank], [t0].[Note] FROM [LocustLeaders] AS [l] LEFT JOIN ( SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] @@ -5024,7 +5023,7 @@ public override async Task Where_required_navigation_on_derived_type(bool async) await base.Where_required_navigation_on_derived_type(async); AssertSql( - @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] + @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] AS [l] LEFT JOIN [LocustHighCommands] AS [l0] ON [l].[HighCommandId] = [l0].[Id] WHERE [l].[Discriminator] IN (N'LocustLeader', N'LocustCommander') AND ([l0].[IsOperational] = CAST(1 AS bit))"); @@ -6441,11 +6440,11 @@ public override async Task Nav_rewrite_with_convert1(bool async) await base.Nav_rewrite_with_convert1(async); AssertSql( - @"SELECT [t].[Name], [t].[Discriminator], [t].[LocustHordeId], [t].[ThreatLevel], [t].[DefeatedByNickname], [t].[DefeatedBySquadId], [t].[HighCommandId] + @"SELECT [t].[Name], [t].[Discriminator], [t].[LocustHordeId], [t].[ThreatLevel], [t].[ThreatLevelByte], [t].[ThreatLevelNullableByte], [t].[DefeatedByNickname], [t].[DefeatedBySquadId], [t].[HighCommandId] FROM [Factions] AS [f] LEFT JOIN [Cities] AS [c] ON [f].[CapitalName] = [c].[Name] LEFT JOIN ( - SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] + SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] AS [l] WHERE [l].[Discriminator] = N'LocustCommander' ) AS [t] ON [f].[CommanderName] = [t].[Name] @@ -6461,7 +6460,7 @@ public override async Task Nav_rewrite_with_convert2(bool async) FROM [Factions] AS [f] LEFT JOIN [Cities] AS [c] ON [f].[CapitalName] = [c].[Name] LEFT JOIN ( - SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] + SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] AS [l] WHERE [l].[Discriminator] = N'LocustCommander' ) AS [t] ON [f].[CommanderName] = [t].[Name] @@ -6477,7 +6476,7 @@ public override async Task Nav_rewrite_with_convert3(bool async) FROM [Factions] AS [f] LEFT JOIN [Cities] AS [c] ON [f].[CapitalName] = [c].[Name] LEFT JOIN ( - SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] + SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] AS [l] WHERE [l].[Discriminator] = N'LocustCommander' ) AS [t] ON [f].[CommanderName] = [t].[Name] @@ -6719,7 +6718,7 @@ public override async Task Navigation_based_on_complex_expression2(bool async) @"SELECT [f].[Id], [f].[CapitalName], [f].[Discriminator], [f].[Name], [f].[CommanderName], [f].[Eradicated] FROM [Factions] AS [f] LEFT JOIN ( - SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] + SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] AS [l] WHERE [l].[Discriminator] = N'LocustCommander' ) AS [t] ON [f].[CommanderName] = [t].[Name] @@ -6731,10 +6730,10 @@ public override async Task Navigation_based_on_complex_expression3(bool async) await base.Navigation_based_on_complex_expression3(async); AssertSql( - @"SELECT [t].[Name], [t].[Discriminator], [t].[LocustHordeId], [t].[ThreatLevel], [t].[DefeatedByNickname], [t].[DefeatedBySquadId], [t].[HighCommandId] + @"SELECT [t].[Name], [t].[Discriminator], [t].[LocustHordeId], [t].[ThreatLevel], [t].[ThreatLevelByte], [t].[ThreatLevelNullableByte], [t].[DefeatedByNickname], [t].[DefeatedBySquadId], [t].[HighCommandId] FROM [Factions] AS [f] LEFT JOIN ( - SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] + SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] AS [l] WHERE [l].[Discriminator] = N'LocustCommander' ) AS [t] ON [f].[CommanderName] = [t].[Name] @@ -6770,7 +6769,7 @@ public override async Task Select_as_operator(bool async) await base.Select_as_operator(async); AssertSql( - @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] + @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] AS [l] WHERE [l].[Discriminator] IN (N'LocustLeader', N'LocustCommander')"); } @@ -7477,7 +7476,7 @@ public override async Task Checked_context_with_cast_does_not_fail(bool isAsync) await base.Checked_context_with_cast_does_not_fail(isAsync); AssertSql( - @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] + @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] AS [l] WHERE [l].[Discriminator] IN (N'LocustLeader', N'LocustCommander') AND (CAST([l].[ThreatLevel] AS tinyint) >= CAST(5 AS tinyint))"); } @@ -7487,7 +7486,7 @@ public override async Task Checked_context_with_addition_does_not_fail(bool isAs await base.Checked_context_with_addition_does_not_fail(isAsync); AssertSql( - @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] + @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] FROM [LocustLeaders] AS [l] WHERE [l].[Discriminator] IN (N'LocustLeader', N'LocustCommander') AND (CAST([l].[ThreatLevel] AS bigint) >= (CAST(5 AS bigint) + CAST([l].[ThreatLevel] AS bigint)))"); } @@ -7568,6 +7567,148 @@ FROM [Missions] AS [m] WHERE DATEPART(millisecond, [m].[Duration]) = 1"); } + public override async Task Contains_on_collection_of_byte_subquery(bool async) + { + await base.Contains_on_collection_of_byte_subquery(async); + + AssertSql( + @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] +FROM [LocustLeaders] AS [l] +WHERE [l].[Discriminator] IN (N'LocustLeader', N'LocustCommander') AND [l].[ThreatLevelByte] IN ( + SELECT [l0].[ThreatLevelByte] + FROM [LocustLeaders] AS [l0] + WHERE [l0].[Discriminator] IN (N'LocustLeader', N'LocustCommander') +)"); + } + + public override async Task Contains_on_collection_of_nullable_byte_subquery(bool async) + { + await base.Contains_on_collection_of_nullable_byte_subquery(async); + + AssertSql( + @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] +FROM [LocustLeaders] AS [l] +WHERE [l].[Discriminator] IN (N'LocustLeader', N'LocustCommander') AND EXISTS ( + SELECT 1 + FROM [LocustLeaders] AS [l0] + WHERE [l0].[Discriminator] IN (N'LocustLeader', N'LocustCommander') AND (([l0].[ThreatLevelNullableByte] = [l].[ThreatLevelNullableByte]) OR ([l0].[ThreatLevelNullableByte] IS NULL AND [l].[ThreatLevelNullableByte] IS NULL)))"); + } + + public override async Task Contains_on_collection_of_nullable_byte_subquery_null_constant(bool async) + { + await base.Contains_on_collection_of_nullable_byte_subquery_null_constant(async); + + AssertSql( + @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] +FROM [LocustLeaders] AS [l] +WHERE [l].[Discriminator] IN (N'LocustLeader', N'LocustCommander') AND EXISTS ( + SELECT 1 + FROM [LocustLeaders] AS [l0] + WHERE [l0].[Discriminator] IN (N'LocustLeader', N'LocustCommander') AND [l0].[ThreatLevelNullableByte] IS NULL)"); + } + + public override async Task Contains_on_collection_of_nullable_byte_subquery_null_parameter(bool async) + { + await base.Contains_on_collection_of_nullable_byte_subquery_null_parameter(async); + + AssertSql( + @"SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] +FROM [LocustLeaders] AS [l] +WHERE [l].[Discriminator] IN (N'LocustLeader', N'LocustCommander') AND EXISTS ( + SELECT 1 + FROM [LocustLeaders] AS [l0] + WHERE [l0].[Discriminator] IN (N'LocustLeader', N'LocustCommander') AND [l0].[ThreatLevelNullableByte] IS NULL)"); + } + + public override async Task Contains_on_byte_array_property_using_byte_column(bool async) + { + await base.Contains_on_byte_array_property_using_byte_column(async); + + AssertSql( + @"SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name], [t].[Name], [t].[Discriminator], [t].[LocustHordeId], [t].[ThreatLevel], [t].[ThreatLevelByte], [t].[ThreatLevelNullableByte], [t].[DefeatedByNickname], [t].[DefeatedBySquadId], [t].[HighCommandId] +FROM [Squads] AS [s] +CROSS JOIN ( + SELECT [l].[Name], [l].[Discriminator], [l].[LocustHordeId], [l].[ThreatLevel], [l].[ThreatLevelByte], [l].[ThreatLevelNullableByte], [l].[DefeatedByNickname], [l].[DefeatedBySquadId], [l].[HighCommandId] + FROM [LocustLeaders] AS [l] + WHERE [l].[Discriminator] IN (N'LocustLeader', N'LocustCommander') +) AS [t] +WHERE CHARINDEX(CAST([t].[ThreatLevelByte] AS varbinary(max)), [s].[Banner]) > 0"); + } + + public override async Task Subquery_projecting_non_nullable_scalar_contains_non_nullable_value_doesnt_need_null_expansion(bool async) + { + await base.Subquery_projecting_non_nullable_scalar_contains_non_nullable_value_doesnt_need_null_expansion(async); + + AssertSql( + @"SELECT [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[Discriminator], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank] +FROM [LocustLeaders] AS [l] +CROSS APPLY ( + SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] + FROM [Gears] AS [g] + WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND [l].[ThreatLevelByte] IN ( + SELECT [l0].[ThreatLevelByte] + FROM [LocustLeaders] AS [l0] + WHERE [l0].[Discriminator] IN (N'LocustLeader', N'LocustCommander') + ) +) AS [t] +WHERE [l].[Discriminator] IN (N'LocustLeader', N'LocustCommander')"); + } + + public override async Task Subquery_projecting_non_nullable_scalar_contains_non_nullable_value_doesnt_need_null_expansion_negated(bool async) + { + await base.Subquery_projecting_non_nullable_scalar_contains_non_nullable_value_doesnt_need_null_expansion_negated(async); + + AssertSql( + @"SELECT [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[Discriminator], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank] +FROM [LocustLeaders] AS [l] +CROSS APPLY ( + SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] + FROM [Gears] AS [g] + WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND [l].[ThreatLevelByte] NOT IN ( + SELECT [l0].[ThreatLevelByte] + FROM [LocustLeaders] AS [l0] + WHERE [l0].[Discriminator] IN (N'LocustLeader', N'LocustCommander') + ) +) AS [t] +WHERE [l].[Discriminator] IN (N'LocustLeader', N'LocustCommander')"); + } + + public override async Task Subquery_projecting_nullable_scalar_contains_nullable_value_needs_null_expansion(bool async) + { + await base.Subquery_projecting_nullable_scalar_contains_nullable_value_needs_null_expansion(async); + + AssertSql( + @"SELECT [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[Discriminator], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank] +FROM [LocustLeaders] AS [l] +CROSS APPLY ( + SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] + FROM [Gears] AS [g] + WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND EXISTS ( + SELECT 1 + FROM [LocustLeaders] AS [l0] + WHERE [l0].[Discriminator] IN (N'LocustLeader', N'LocustCommander') AND (([l0].[ThreatLevelNullableByte] = [l].[ThreatLevelNullableByte]) OR ([l0].[ThreatLevelNullableByte] IS NULL AND [l].[ThreatLevelNullableByte] IS NULL))) +) AS [t] +WHERE [l].[Discriminator] IN (N'LocustLeader', N'LocustCommander')"); + } + + public override async Task Subquery_projecting_nullable_scalar_contains_nullable_value_needs_null_expansion_negated(bool async) + { + await base.Subquery_projecting_nullable_scalar_contains_nullable_value_needs_null_expansion_negated(async); + + AssertSql( + @"SELECT [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[Discriminator], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank] +FROM [LocustLeaders] AS [l] +CROSS APPLY ( + SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] + FROM [Gears] AS [g] + WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND NOT (EXISTS ( + SELECT 1 + FROM [LocustLeaders] AS [l0] + WHERE [l0].[Discriminator] IN (N'LocustLeader', N'LocustCommander') AND (([l0].[ThreatLevelNullableByte] = [l].[ThreatLevelNullableByte]) OR ([l0].[ThreatLevelNullableByte] IS NULL AND [l].[ThreatLevelNullableByte] IS NULL)))) +) AS [t] +WHERE [l].[Discriminator] IN (N'LocustLeader', N'LocustCommander')"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs index 1df8c9ce4f8..88956f55b2c 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindAggregateOperatorsQuerySqlServerTest.cs @@ -716,10 +716,10 @@ public override async Task Contains_with_subquery(bool async) AssertSql( @"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].[CustomerID] IN ( - SELECT [o].[CustomerID] +WHERE EXISTS ( + SELECT 1 FROM [Orders] AS [o] -)"); + WHERE [o].[CustomerID] = [c].[CustomerID])"); } public override async Task Contains_with_local_array_closure(bool async) @@ -959,13 +959,13 @@ public override async Task Contains_top_level(bool async) await base.Contains_top_level(async); AssertSql( - @"@__p_0='ALFKI' (Size = 4000) + @"@__p_0='ALFKI' (Size = 5) (DbType = StringFixedLength) SELECT CASE - WHEN @__p_0 IN ( - SELECT [c].[CustomerID] + WHEN EXISTS ( + SELECT 1 FROM [Customers] AS [c] - ) THEN CAST(1 AS bit) + WHERE [c].[CustomerID] = @__p_0) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END"); } @@ -1079,11 +1079,10 @@ FROM [Orders] AS [o] @"@__entity_equality_p_0_OrderID='10248' (Nullable = true) SELECT CASE - WHEN @__entity_equality_p_0_OrderID IN ( - SELECT [o].[OrderID] + WHEN EXISTS ( + SELECT 1 FROM [Orders] AS [o] - WHERE [o].[CustomerID] = N'VINET' - ) THEN CAST(1 AS bit) + WHERE ([o].[CustomerID] = N'VINET') AND ([o].[OrderID] = @__entity_equality_p_0_OrderID)) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END"); } @@ -1097,11 +1096,10 @@ public override async Task List_Contains_over_entityType_should_rewrite_to_ident 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 @__entity_equality_someOrder_0_OrderID IN ( - SELECT [o].[OrderID] +WHERE EXISTS ( + SELECT 1 FROM [Orders] AS [o] - WHERE [c].[CustomerID] = [o].[CustomerID] -)"); + WHERE ([c].[CustomerID] = [o].[CustomerID]) AND ([o].[OrderID] = @__entity_equality_someOrder_0_OrderID))"); } public override async Task List_Contains_with_constant_list(bool async) @@ -1164,19 +1162,120 @@ FROM [Customers] AS [c] WHERE [c].[CustomerID] IN (N'ALFKI')"); } - public override void Contains_over_entityType_with_null_should_rewrite_to_identity_equality() + public override async Task Contains_over_entityType_with_null_should_rewrite_to_false(bool async) + { + await base.Contains_over_entityType_with_null_should_rewrite_to_false(async); + + AssertSql( + @"SELECT CAST(0 AS bit)"); + } + + public override async Task Contains_over_entityType_with_null_should_rewrite_to_identity_equality_subquery(bool async) + { + await base.Contains_over_entityType_with_null_should_rewrite_to_identity_equality_subquery(async); + + AssertSql( + @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] +FROM [Orders] AS [o] +WHERE 0 = 1"); + } + + public override async Task Contains_over_scalar_with_null_should_rewrite_to_identity_equality_subquery(bool async) + { + await base.Contains_over_scalar_with_null_should_rewrite_to_identity_equality_subquery(async); + + AssertSql( + @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] +FROM [Orders] AS [o] +WHERE EXISTS ( + SELECT 1 + FROM [Orders] AS [o0] + WHERE ([o0].[CustomerID] = N'VINET') AND [o0].[CustomerID] IS NULL)"); + } + + public override async Task Contains_over_entityType_with_null_should_rewrite_to_identity_equality_subquery_negated(bool async) { - base.Contains_over_entityType_with_null_should_rewrite_to_identity_equality(); + await base.Contains_over_entityType_with_null_should_rewrite_to_identity_equality_subquery_negated(async); + + AssertSql( + @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] +FROM [Orders] AS [o] +WHERE NOT (EXISTS ( + SELECT 1 + FROM [Orders] AS [o0] + WHERE ([o0].[CustomerID] = N'VINET') AND [o0].[CustomerID] IS NULL))"); + } + + public override async Task Contains_over_entityType_with_null_should_rewrite_to_identity_equality_subquery_complex(bool async) + { + await base.Contains_over_entityType_with_null_should_rewrite_to_identity_equality_subquery_complex(async); + + AssertSql( + @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] +FROM [Orders] AS [o] +WHERE CASE + WHEN EXISTS ( + SELECT 1 + FROM [Orders] AS [o0] + WHERE ([o0].[CustomerID] = N'VINET') AND [o0].[CustomerID] IS NULL) THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END = CASE + WHEN EXISTS ( + SELECT 1 + FROM [Orders] AS [o1] + WHERE (([o1].[CustomerID] <> N'VINET') OR [o1].[CustomerID] IS NULL) AND [o1].[CustomerID] IS NULL) THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END"); + } + + public override async Task Contains_over_nullable_scalar_with_null_in_subquery_translated_correctly(bool async) + { + await base.Contains_over_nullable_scalar_with_null_in_subquery_translated_correctly(async); AssertSql( @"SELECT CASE - WHEN NULL IN ( - SELECT [o].[OrderID] + WHEN EXISTS ( + SELECT 1 FROM [Orders] AS [o] - WHERE [o].[CustomerID] = N'VINET' - ) THEN CAST(1 AS bit) + WHERE ([o].[CustomerID] = N'VINET') AND [o].[CustomerID] IS NULL) THEN CAST(1 AS bit) ELSE CAST(0 AS bit) -END"); +END +FROM [Orders] AS [o0]"); + } + + public override async Task Contains_over_non_nullable_scalar_with_null_in_subquery_simplifies_to_false(bool async) + { + await base.Contains_over_non_nullable_scalar_with_null_in_subquery_simplifies_to_false(async); + + AssertSql( + @"SELECT CAST(0 AS bit) +FROM [Orders] AS [o]"); + } + + public override async Task Contains_over_entityType_should_materialize_when_composite(bool async) + { + await base.Contains_over_entityType_should_materialize_when_composite(async); + + AssertSql( + @"SELECT [o].[OrderID], [o].[ProductID], [o].[Discount], [o].[Quantity], [o].[UnitPrice] +FROM [Order Details] AS [o] +WHERE ([o].[ProductID] = 42) AND EXISTS ( + SELECT 1 + FROM [Order Details] AS [o0] + WHERE ([o0].[OrderID] = [o].[OrderID]) AND ([o0].[ProductID] = [o].[ProductID]))"); + } + + public override async Task Contains_over_entityType_should_materialize_when_composite2(bool async) + { + await base.Contains_over_entityType_should_materialize_when_composite2(async); + + AssertSql( + @"SELECT [o].[OrderID], [o].[ProductID], [o].[Discount], [o].[Quantity], [o].[UnitPrice] +FROM [Order Details] AS [o] +WHERE ([o].[ProductID] = 42) AND EXISTS ( + SELECT 1 + FROM [Order Details] AS [o0] + WHERE ([o0].[OrderID] > 42) AND (([o0].[OrderID] = [o].[OrderID]) AND ([o0].[ProductID] = [o].[ProductID])))"); } public override async Task String_FirstOrDefault_in_projection_does_client_eval(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs index fa422b7d443..8c3fdd88c61 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs @@ -1169,12 +1169,11 @@ public override async Task GroupBy_aggregate_Contains(bool async) AssertSql( @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE [o].[CustomerID] IN ( - SELECT [o0].[CustomerID] +WHERE EXISTS ( + SELECT 1 FROM [Orders] AS [o0] GROUP BY [o0].[CustomerID] - HAVING COUNT(*) > 30 -)"); + HAVING (COUNT(*) > 30) AND (([o0].[CustomerID] = [o].[CustomerID]) OR ([o0].[CustomerID] IS NULL AND [o].[CustomerID] IS NULL)))"); } public override async Task GroupBy_aggregate_Pushdown(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs index 07ab4d38c3c..aea30e6b1a6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs @@ -2474,10 +2474,10 @@ public override async Task Where_subquery_on_bool(bool async) AssertSql( @"SELECT [p].[ProductID], [p].[Discontinued], [p].[ProductName], [p].[SupplierID], [p].[UnitPrice], [p].[UnitsInStock] FROM [Products] AS [p] -WHERE N'Chai' IN ( - SELECT [p0].[ProductName] +WHERE EXISTS ( + SELECT 1 FROM [Products] AS [p0] -)"); + WHERE [p0].[ProductName] = N'Chai')"); } public override async Task Where_subquery_on_collection(bool async) @@ -2487,11 +2487,10 @@ public override async Task Where_subquery_on_collection(bool async) AssertSql( @"SELECT [p].[ProductID], [p].[Discontinued], [p].[ProductName], [p].[SupplierID], [p].[UnitPrice], [p].[UnitsInStock] FROM [Products] AS [p] -WHERE CAST(5 AS smallint) IN ( - SELECT [o].[Quantity] +WHERE EXISTS ( + SELECT 1 FROM [Order Details] AS [o] - WHERE [o].[ProductID] = [p].[ProductID] -)"); + WHERE ([o].[ProductID] = [p].[ProductID]) AND ([o].[Quantity] = CAST(5 AS smallint)))"); } public override async Task Select_many_cross_join_same_collection(bool async) @@ -3634,12 +3633,11 @@ public override async Task Contains_with_subquery_involving_join_binds_to_correc AssertSql( @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] -WHERE ([o].[OrderID] > 11000) AND [o].[OrderID] IN ( - SELECT [o0].[OrderID] +WHERE ([o].[OrderID] > 11000) AND EXISTS ( + SELECT 1 FROM [Order Details] AS [o0] INNER JOIN [Products] AS [p] ON [o0].[ProductID] = [p].[ProductID] - WHERE [p].[ProductName] = N'Chai' -)"); + WHERE ([p].[ProductName] = N'Chai') AND ([o0].[OrderID] = [o].[OrderID]))"); } public override async Task Complex_query_with_repeated_query_model_compiles_correctly(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs index 8a1ff942efb..fd392709b44 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs @@ -1569,15 +1569,21 @@ public override async Task Where_multiple_contains_in_subquery_with_or(bool asyn AssertSql( @"SELECT [o].[OrderID], [o].[ProductID], [o].[Discount], [o].[Quantity], [o].[UnitPrice] FROM [Order Details] AS [o] -WHERE [o].[ProductID] IN ( - SELECT TOP(1) [p].[ProductID] - FROM [Products] AS [p] - ORDER BY [p].[ProductID] -) OR [o].[OrderID] IN ( - SELECT TOP(1) [o0].[OrderID] - FROM [Orders] AS [o0] - ORDER BY [o0].[OrderID] -)"); +WHERE EXISTS ( + SELECT 1 + FROM ( + SELECT TOP(1) [p].[ProductID], [p].[Discontinued], [p].[ProductName], [p].[SupplierID], [p].[UnitPrice], [p].[UnitsInStock] + FROM [Products] AS [p] + ORDER BY [p].[ProductID] + ) AS [t] + WHERE [t].[ProductID] = [o].[ProductID]) OR EXISTS ( + SELECT 1 + FROM ( + SELECT TOP(1) [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate] + FROM [Orders] AS [o0] + ORDER BY [o0].[OrderID] + ) AS [t0] + WHERE [t0].[OrderID] = [o].[OrderID])"); } public override async Task Where_multiple_contains_in_subquery_with_and(bool async) @@ -1587,15 +1593,21 @@ public override async Task Where_multiple_contains_in_subquery_with_and(bool asy AssertSql( @"SELECT [o].[OrderID], [o].[ProductID], [o].[Discount], [o].[Quantity], [o].[UnitPrice] FROM [Order Details] AS [o] -WHERE [o].[ProductID] IN ( - SELECT TOP(20) [p].[ProductID] - FROM [Products] AS [p] - ORDER BY [p].[ProductID] -) AND [o].[OrderID] IN ( - SELECT TOP(10) [o0].[OrderID] - FROM [Orders] AS [o0] - ORDER BY [o0].[OrderID] -)"); +WHERE EXISTS ( + SELECT 1 + FROM ( + SELECT TOP(20) [p].[ProductID], [p].[Discontinued], [p].[ProductName], [p].[SupplierID], [p].[UnitPrice], [p].[UnitsInStock] + FROM [Products] AS [p] + ORDER BY [p].[ProductID] + ) AS [t] + WHERE [t].[ProductID] = [o].[ProductID]) AND EXISTS ( + SELECT 1 + FROM ( + SELECT TOP(10) [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate] + FROM [Orders] AS [o0] + ORDER BY [o0].[OrderID] + ) AS [t0] + WHERE [t0].[OrderID] = [o].[OrderID])"); } public override async Task Where_contains_on_navigation(bool async) @@ -1608,11 +1620,10 @@ FROM [Orders] AS [o] WHERE EXISTS ( SELECT 1 FROM [Customers] AS [c] - WHERE [o].[OrderID] IN ( - SELECT [o0].[OrderID] + WHERE EXISTS ( + SELECT 1 FROM [Orders] AS [o0] - WHERE [c].[CustomerID] = [o0].[CustomerID] - ))"); + WHERE ([c].[CustomerID] = [o0].[CustomerID]) AND ([o0].[OrderID] = [o].[OrderID])))"); } public override async Task Where_subquery_FirstOrDefault_is_null(bool async) @@ -1792,11 +1803,10 @@ public override async Task Where_Queryable_ToList_Contains(bool async) @"SELECT [c].[CustomerID], [o].[CustomerID], [o].[OrderID] FROM [Customers] AS [c] LEFT JOIN [Orders] AS [o] ON [c].[CustomerID] = [o].[CustomerID] -WHERE N'ALFKI' IN ( - SELECT [o0].[CustomerID] +WHERE EXISTS ( + SELECT 1 FROM [Orders] AS [o0] - WHERE [o0].[CustomerID] = [c].[CustomerID] -) + WHERE ([o0].[CustomerID] = [c].[CustomerID]) AND ([o0].[CustomerID] = N'ALFKI')) ORDER BY [c].[CustomerID], [o].[OrderID]"); } @@ -1823,11 +1833,10 @@ public override async Task Where_Queryable_ToArray_Contains(bool async) @"SELECT [c].[CustomerID], [o].[CustomerID], [o].[OrderID] FROM [Customers] AS [c] LEFT JOIN [Orders] AS [o] ON [c].[CustomerID] = [o].[CustomerID] -WHERE N'ALFKI' IN ( - SELECT [o0].[CustomerID] +WHERE EXISTS ( + SELECT 1 FROM [Orders] AS [o0] - WHERE [o0].[CustomerID] = [c].[CustomerID] -) + WHERE ([o0].[CustomerID] = [c].[CustomerID]) AND ([o0].[CustomerID] = N'ALFKI')) ORDER BY [c].[CustomerID], [o].[OrderID]"); } @@ -1854,11 +1863,25 @@ public override async Task Where_Queryable_AsEnumerable_Contains(bool async) @"SELECT [c].[CustomerID], [o].[CustomerID], [o].[OrderID] FROM [Customers] AS [c] LEFT JOIN [Orders] AS [o] ON [c].[CustomerID] = [o].[CustomerID] -WHERE N'ALFKI' IN ( - SELECT [o0].[CustomerID] +WHERE EXISTS ( + SELECT 1 FROM [Orders] AS [o0] - WHERE [o0].[CustomerID] = [c].[CustomerID] -) + WHERE ([o0].[CustomerID] = [c].[CustomerID]) AND ([o0].[CustomerID] = N'ALFKI')) +ORDER BY [c].[CustomerID], [o].[OrderID]"); + } + + public override async Task Where_Queryable_AsEnumerable_Contains_negated(bool async) + { + await base.Where_Queryable_AsEnumerable_Contains_negated(async); + + AssertSql( + @"SELECT [c].[CustomerID], [o].[CustomerID], [o].[OrderID] +FROM [Customers] AS [c] +LEFT JOIN [Orders] AS [o] ON [c].[CustomerID] = [o].[CustomerID] +WHERE NOT (EXISTS ( + SELECT 1 + FROM [Orders] AS [o0] + WHERE ([o0].[CustomerID] = [c].[CustomerID]) AND ([o0].[CustomerID] = N'ALFKI'))) ORDER BY [c].[CustomerID], [o].[OrderID]"); } @@ -1909,11 +1932,10 @@ public override async Task Where_collection_navigation_ToList_Contains(bool asyn SELECT [c].[CustomerID], [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Customers] AS [c] LEFT JOIN [Orders] AS [o] ON [c].[CustomerID] = [o].[CustomerID] -WHERE @__entity_equality_order_0_OrderID IN ( - SELECT [o0].[OrderID] +WHERE EXISTS ( + SELECT 1 FROM [Orders] AS [o0] - WHERE [c].[CustomerID] = [o0].[CustomerID] -) + WHERE ([c].[CustomerID] = [o0].[CustomerID]) AND ([o0].[OrderID] = @__entity_equality_order_0_OrderID)) ORDER BY [c].[CustomerID], [o].[OrderID]"); } @@ -1964,11 +1986,10 @@ public override async Task Where_collection_navigation_AsEnumerable_Contains(boo SELECT [c].[CustomerID], [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Customers] AS [c] LEFT JOIN [Orders] AS [o] ON [c].[CustomerID] = [o].[CustomerID] -WHERE @__entity_equality_order_0_OrderID IN ( - SELECT [o0].[OrderID] +WHERE EXISTS ( + SELECT 1 FROM [Orders] AS [o0] - WHERE [c].[CustomerID] = [o0].[CustomerID] -) + WHERE ([c].[CustomerID] = [o0].[CustomerID]) AND ([o0].[OrderID] = @__entity_equality_order_0_OrderID)) ORDER BY [c].[CustomerID], [o].[OrderID]"); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs index 8b3f97c6f02..afa66071614 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs @@ -1781,6 +1781,26 @@ FROM [Entities1] AS [e] WHERE (([e].[NullableStringA] IS NOT NULL AND [e].[NullableBoolB] IS NOT NULL) AND [e].[NullableStringC] IS NOT NULL) AND (([e].[NullableBoolB] <> [e].[NullableBoolC]) OR [e].[NullableBoolC] IS NULL)"); } + public override async Task Empty_subquery_with_contains_returns_false(bool async) + { + await base.Empty_subquery_with_contains_returns_false(async); + + AssertSql( + @"SELECT [e].[Id], [e].[BoolA], [e].[BoolB], [e].[BoolC], [e].[IntA], [e].[IntB], [e].[IntC], [e].[NullableBoolA], [e].[NullableBoolB], [e].[NullableBoolC], [e].[NullableIntA], [e].[NullableIntB], [e].[NullableIntC], [e].[NullableStringA], [e].[NullableStringB], [e].[NullableStringC], [e].[StringA], [e].[StringB], [e].[StringC] +FROM [Entities1] AS [e] +WHERE 0 = 1"); + } + + + public override async Task Empty_subquery_with_contains_negated_returns_true(bool async) + { + await base.Empty_subquery_with_contains_negated_returns_true(async); + + AssertSql( + @"SELECT [e].[Id], [e].[BoolA], [e].[BoolB], [e].[BoolC], [e].[IntA], [e].[IntB], [e].[IntC], [e].[NullableBoolA], [e].[NullableBoolB], [e].[NullableBoolC], [e].[NullableIntA], [e].[NullableIntB], [e].[NullableIntC], [e].[NullableStringA], [e].[NullableStringB], [e].[NullableStringC], [e].[StringA], [e].[StringB], [e].[StringC] +FROM [Entities1] AS [e]"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs index 52e770493d0..0dd562477a5 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -2140,21 +2140,19 @@ public virtual void Variable_from_multi_level_nested_closure_is_parametrized() SELECT [e].[Id], [e].[Name] FROM [Entities] AS [e] -WHERE [e].[Id] IN ( - SELECT [e0].[Id] +WHERE EXISTS ( + SELECT 1 FROM [Entities] AS [e0] - WHERE [e0].[Id] = @__id_0 -)", + WHERE ([e0].[Id] = @__id_0) AND ([e0].[Id] = [e].[Id]))", // @"@__id_0='2' SELECT [e].[Id], [e].[Name] FROM [Entities] AS [e] -WHERE [e].[Id] IN ( - SELECT [e0].[Id] +WHERE EXISTS ( + SELECT 1 FROM [Entities] AS [e0] - WHERE [e0].[Id] = @__id_0 -)"); + WHERE ([e0].[Id] = @__id_0) AND ([e0].[Id] = [e].[Id]))"); } } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs index 1cf70165549..c584a7f307b 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs @@ -70,6 +70,14 @@ public override Task DateTimeOffset_Date_returns_datetime(bool async) public override Task Outer_parameter_in_join_key_inner_and_outer(bool async) => null; + public override Task Subquery_projecting_nullable_scalar_contains_nullable_value_needs_null_expansion(bool async) => null; + + public override Task Subquery_projecting_nullable_scalar_contains_nullable_value_needs_null_expansion_negated(bool async) => null; + + public override Task Subquery_projecting_non_nullable_scalar_contains_non_nullable_value_doesnt_need_null_expansion(bool async) => null; + + public override Task Subquery_projecting_non_nullable_scalar_contains_non_nullable_value_doesnt_need_null_expansion_negated(bool async) => null; + [ConditionalTheory(Skip = "Issue #17230")] public override Task Project_collection_navigation_nested_with_take_composite_key(bool async) => base.Project_collection_navigation_nested_with_take_composite_key(async);