From 767d5388a1d5e08ba7512fa9840ed6ef272e581e Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Tue, 20 Aug 2019 13:06:35 -0700 Subject: [PATCH] Query: Implement First/Single/Last OrDefault throwing behavior Subquery produces top(1) for Single/SingleOrDefault Single/First/SingleOrDefault in subquery is treated as if FirstOrDefault Last/LastOrDefault throws when there is no ordering on SelectExpression to reverse Resolves #15559 --- .gitignore | 2 +- ...yableMethodTranslatingExpressionVisitor.cs | 12 +- .../Query/SimpleQueryCosmosTest.cs | 42 +++++ .../Query/IncludeInMemoryTest.cs | 6 + .../Query/SimpleQueryInMemoryTest.cs | 42 +++++ .../TransactionTestBase.cs | 16 +- .../ConcurrencyDetectorTestBase.cs | 4 +- .../Query/IncludeTestBase.cs | 22 +-- .../SimpleQueryTestBase.ResultOperators.cs | 17 +- .../Query/SimpleQueryTestBase.cs | 136 ++++++++++++--- .../Query/GearsOfWarQuerySqlServerTest.cs | 18 +- .../Query/IncludeSqlServerTest.cs | 19 --- .../Query/QueryNavigationsSqlServerTest.cs | 2 +- ...impleQuerySqlServerTest.ResultOperators.cs | 10 -- .../Query/SimpleQuerySqlServerTest.Select.cs | 2 +- .../Query/SimpleQuerySqlServerTest.cs | 158 ++++++++++++++---- 16 files changed, 387 insertions(+), 121 deletions(-) diff --git a/.gitignore b/.gitignore index 5417b71a61b..38f35c2a092 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -QueryBaseline.cs +QueryBaseline.txt ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index e4e593f6ea3..8c057fae99e 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -25,6 +25,7 @@ public class RelationalQueryableMethodTranslatingExpressionVisitor : QueryableMe private readonly RelationalProjectionBindingExpressionVisitor _projectionBindingExpressionVisitor; private readonly IModel _model; private readonly ISqlExpressionFactory _sqlExpressionFactory; + private readonly bool _subquery; public RelationalQueryableMethodTranslatingExpressionVisitor( QueryableMethodTranslatingExpressionVisitorDependencies dependencies, @@ -40,6 +41,7 @@ public RelationalQueryableMethodTranslatingExpressionVisitor( _projectionBindingExpressionVisitor = new RelationalProjectionBindingExpressionVisitor(this, _sqlTranslator); _model = model; _sqlExpressionFactory = sqlExpressionFactory; + _subquery = false; } protected virtual RelationalQueryableMethodTranslatingExpressionVisitorDependencies RelationalDependencies { get; } @@ -54,6 +56,7 @@ protected RelationalQueryableMethodTranslatingExpressionVisitor( _weakEntityExpandingExpressionVisitor = parentVisitor._weakEntityExpandingExpressionVisitor; _projectionBindingExpressionVisitor = new RelationalProjectionBindingExpressionVisitor(this, _sqlTranslator); _sqlExpressionFactory = parentVisitor._sqlExpressionFactory; + _subquery = true; } protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) @@ -555,12 +558,17 @@ private SqlBinaryExpression CreateJoinPredicate( protected override ShapedQueryExpression TranslateLastOrDefault( ShapedQueryExpression source, LambdaExpression predicate, Type returnType, bool returnDefault) { + var selectExpression = (SelectExpression)source.QueryExpression; + if (selectExpression.Orderings.Count == 0) + { + return null; + } + if (predicate != null) { source = TranslateWhere(source, predicate); } - var selectExpression = (SelectExpression)source.QueryExpression; selectExpression.ReverseOrderings(); selectExpression.ApplyLimit(TranslateExpression(Expression.Constant(1))); @@ -849,7 +857,7 @@ protected override ShapedQueryExpression TranslateSingleOrDefault(ShapedQueryExp } var selectExpression = (SelectExpression)source.QueryExpression; - selectExpression.ApplyLimit(TranslateExpression(Expression.Constant(2))); + selectExpression.ApplyLimit(TranslateExpression(Expression.Constant(_subquery ? 1 : 2))); if (source.ShaperExpression.Type != returnType) { diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.cs index 9a7256c87c9..595e27f8d4e 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.cs @@ -347,6 +347,12 @@ FROM root c WHERE (c[""Discriminator""] = ""Employee"")"); } + [ConditionalTheory(Skip = "Issue #17246")] + public override Task Where_query_composition_entity_equality_one_element_Single(bool isAsync) + { + return base.Where_query_composition_entity_equality_one_element_Single(isAsync); + } + [ConditionalTheory(Skip = "Issue#17246")] public override async Task Where_query_composition_entity_equality_one_element_FirstOrDefault(bool isAsync) { @@ -358,6 +364,12 @@ FROM root c WHERE (c[""Discriminator""] = ""Employee"")"); } + [ConditionalTheory(Skip = "Issue #17246")] + public override Task Where_query_composition_entity_equality_one_element_First(bool isAsync) + { + return base.Where_query_composition_entity_equality_one_element_First(isAsync); + } + [ConditionalTheory(Skip = "Issue #17246")] public override async Task Where_query_composition_entity_equality_no_elements_SingleOrDefault(bool isAsync) { @@ -369,6 +381,12 @@ FROM root c WHERE (c[""Discriminator""] = ""Employee"")"); } + [ConditionalTheory(Skip = "Issue #17246")] + public override Task Where_query_composition_entity_equality_no_elements_Single(bool isAsync) + { + return base.Where_query_composition_entity_equality_no_elements_Single(isAsync); + } + [ConditionalTheory(Skip = "Issue#17246")] public override async Task Where_query_composition_entity_equality_no_elements_FirstOrDefault(bool isAsync) { @@ -380,6 +398,24 @@ FROM root c WHERE (c[""Discriminator""] = ""Employee"")"); } + [ConditionalTheory(Skip = "Issue #17246")] + public override Task Where_query_composition_entity_equality_no_elements_First(bool isAsync) + { + return base.Where_query_composition_entity_equality_no_elements_First(isAsync); + } + + [ConditionalTheory(Skip = "Issue #17246")] + public override Task Where_query_composition_entity_equality_multiple_elements_SingleOrDefault(bool isAsync) + { + return base.Where_query_composition_entity_equality_multiple_elements_SingleOrDefault(isAsync); + } + + [ConditionalTheory(Skip = "Issue #17246")] + public override Task Where_query_composition_entity_equality_multiple_elements_Single(bool isAsync) + { + return base.Where_query_composition_entity_equality_multiple_elements_Single(isAsync); + } + [ConditionalTheory(Skip = "Issue#17246")] public override async Task Where_query_composition_entity_equality_multiple_elements_FirstOrDefault(bool isAsync) { @@ -391,6 +427,12 @@ FROM root c WHERE (c[""Discriminator""] = ""Employee"")"); } + [ConditionalTheory(Skip = "Issue #17246")] + public override Task Where_query_composition_entity_equality_multiple_elements_First(bool isAsync) + { + return base.Where_query_composition_entity_equality_multiple_elements_First(isAsync); + } + [ConditionalTheory(Skip = "Issue #17246")] public override async Task Where_query_composition2(bool isAsync) { diff --git a/test/EFCore.InMemory.FunctionalTests/Query/IncludeInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/IncludeInMemoryTest.cs index dc3a8fc3f24..ebd8b21042f 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/IncludeInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/IncludeInMemoryTest.cs @@ -18,5 +18,11 @@ public IncludeInMemoryTest(IncludeInMemoryFixture fixture, ITestOutputHelper tes public override void Include_collection_with_client_filter(bool useString) { } + + [ConditionalTheory(Skip = "Issue #16963")] + public override void Include_collection_with_last_no_orderby(bool useString) + { + base.Include_collection_with_last_no_orderby(useString); + } } } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs index bdaf898638d..f04122e35eb 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/SimpleQueryInMemoryTest.cs @@ -381,5 +381,47 @@ public override Task Projection_when_client_evald_subquery(bool isAsync) [ConditionalTheory(Skip = "Issue #16963")] public override Task Select_bool_closure_with_order_by_property_with_cast_to_nullable(bool isAsync) => null; + + [ConditionalTheory(Skip = "Issue #16963")] + public override Task Where_query_composition_entity_equality_one_element_Single(bool isAsync) + { + return base.Where_query_composition_entity_equality_one_element_Single(isAsync); + } + + [ConditionalTheory(Skip = "Issue #16963")] + public override Task Where_query_composition_entity_equality_one_element_First(bool isAsync) + { + return base.Where_query_composition_entity_equality_one_element_First(isAsync); + } + + [ConditionalTheory(Skip = "Issue #16963")] + public override Task Where_query_composition_entity_equality_no_elements_Single(bool isAsync) + { + return base.Where_query_composition_entity_equality_no_elements_Single(isAsync); + } + + [ConditionalTheory(Skip = "Issue #16963")] + public override Task Where_query_composition_entity_equality_no_elements_First(bool isAsync) + { + return base.Where_query_composition_entity_equality_no_elements_First(isAsync); + } + + [ConditionalTheory(Skip = "Issue #16963")] + public override Task Where_query_composition_entity_equality_multiple_elements_SingleOrDefault(bool isAsync) + { + return base.Where_query_composition_entity_equality_multiple_elements_SingleOrDefault(isAsync); + } + + [ConditionalTheory(Skip = "Issue #16963")] + public override Task Where_query_composition_entity_equality_multiple_elements_Single(bool isAsync) + { + return base.Where_query_composition_entity_equality_multiple_elements_Single(isAsync); + } + + [ConditionalTheory(Skip = "Issue #16963")] + public override Task Last_when_no_order_by(bool isAsync) + { + return base.Last_when_no_order_by(isAsync); + } } } diff --git a/test/EFCore.Relational.Specification.Tests/TransactionTestBase.cs b/test/EFCore.Relational.Specification.Tests/TransactionTestBase.cs index 28b20a36c8e..0c57fc3b605 100644 --- a/test/EFCore.Relational.Specification.Tests/TransactionTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/TransactionTestBase.cs @@ -51,7 +51,7 @@ public virtual void SaveChanges_can_be_used_with_no_transaction() }); - context.Entry(context.Set().Last()).State = EntityState.Added; + context.Entry(context.Set().OrderBy(c => c.Id).Last()).State = EntityState.Added; Assert.Throws(() => context.SaveChanges()); @@ -176,7 +176,7 @@ public virtual async Task SaveChanges_uses_enlisted_transaction(bool async, bool Name = "Bobble" }); - context.Entry(context.Set().Last()).State = EntityState.Added; + context.Entry(context.Set().OrderBy(c => c.Id).Last()).State = EntityState.Added; if (async) { @@ -250,7 +250,7 @@ public virtual async Task SaveChanges_uses_enlisted_transaction_after_connection Name = "Bobble" }); - context.Entry(context.Set().Last()).State = EntityState.Added; + context.Entry(context.Set().OrderBy(c => c.Id).Last()).State = EntityState.Added; context.Database.AutoTransactionsEnabled = true; } @@ -302,7 +302,7 @@ public virtual async Task SaveChanges_uses_enlisted_transaction_connectionString Name = "Bobble" }); - context.Entry(context.Set().Last()).State = EntityState.Added; + context.Entry(context.Set().OrderBy(c => c.Id).Last()).State = EntityState.Added; if (async) { @@ -347,7 +347,7 @@ public virtual async Task SaveChanges_uses_ambient_transaction(bool async, bool Name = "Bobble" }); - context.Entry(context.Set().Last()).State = EntityState.Added; + context.Entry(context.Set().OrderBy(c => c.Id).Last()).State = EntityState.Added; if (async) { @@ -421,7 +421,7 @@ public virtual async Task SaveChanges_uses_ambient_transaction_with_connectionSt Name = "Bobble" }); - context.Entry(context.Set().Last()).State = EntityState.Added; + context.Entry(context.Set().OrderBy(c => c.Id).Last()).State = EntityState.Added; if (async) { @@ -464,7 +464,7 @@ public virtual void SaveChanges_throws_for_suppressed_ambient_transactions(bool Name = "Bobble" }); - context.Entry(context.Set().Last()).State = EntityState.Added; + context.Entry(context.Set().OrderBy(c => c.Id).Last()).State = EntityState.Added; using (new TransactionScope(TransactionScopeOption.Suppress)) { @@ -501,7 +501,7 @@ public virtual void SaveChanges_uses_enlisted_transaction_after_ambient_transact Name = "Bobble" }); - context.Entry(context.Set().Last()).State = EntityState.Added; + context.Entry(context.Set().OrderBy(c => c.Id).Last()).State = EntityState.Added; } using (var transaction = new CommittableTransaction(TimeSpan.FromMinutes(10))) diff --git a/test/EFCore.Specification.Tests/ConcurrencyDetectorTestBase.cs b/test/EFCore.Specification.Tests/ConcurrencyDetectorTestBase.cs index 8213cf49a55..e3923df3053 100644 --- a/test/EFCore.Specification.Tests/ConcurrencyDetectorTestBase.cs +++ b/test/EFCore.Specification.Tests/ConcurrencyDetectorTestBase.cs @@ -98,7 +98,7 @@ public virtual Task Last_logs_concurrent_access_nonasync() return ConcurrencyDetectorTest( c => { - var result = c.Products.Last(); + var result = c.Products.OrderBy(p => p.ProductID).Last(); return Task.FromResult(false); }); } @@ -106,7 +106,7 @@ public virtual Task Last_logs_concurrent_access_nonasync() [ConditionalFact] public virtual Task Last_logs_concurrent_access_async() { - return ConcurrencyDetectorTest(c => c.Products.LastAsync()); + return ConcurrencyDetectorTest(c => c.Products.OrderBy(p => p.ProductID).LastAsync()); } [ConditionalFact] diff --git a/test/EFCore.Specification.Tests/Query/IncludeTestBase.cs b/test/EFCore.Specification.Tests/Query/IncludeTestBase.cs index ba1f0ff95e4..a9d1f8f5e25 100644 --- a/test/EFCore.Specification.Tests/Query/IncludeTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/IncludeTestBase.cs @@ -371,25 +371,19 @@ var customer } } - [ConditionalTheory(Skip = "Issue#15559")] + [ConditionalTheory] [InlineData(false)] [InlineData(true)] public virtual void Include_collection_with_last_no_orderby(bool useString) { using (var context = CreateContext()) { - var customer - = useString - ? context.Set() - .Include("Orders") - .Last() - : context.Set() - .Include(c => c.Orders) - .Last(); - - Assert.NotNull(customer); - Assert.Equal(7, customer.Orders.Count); - Assert.Equal(8, context.ChangeTracker.Entries().Count()); + Assert.Equal( + CoreStrings.TranslationFailed(@"Last(Select( source: DbSet, selector: (c) => IncludeExpression( c, MaterializeCollectionNavigation(Navigation: Customer.Orders (k__BackingField, List) Collection ToDependent Order Inverse: Customer PropertyAccessMode.Field, Where( source: DbSet, predicate: (o) => Property(c, ""CustomerID"") == Property(o, ""CustomerID""))), Orders)))"), + RemoveNewLines(Assert.Throws( + () => useString + ? context.Set().Include("Orders").Last() + : context.Set().Include(c => c.Orders).Last()).Message)); } } @@ -4327,6 +4321,8 @@ private static void CheckIsLoaded( } } + private string RemoveNewLines(string message) => message.Replace("\n", "").Replace("\r", ""); + protected virtual void ClearLog() { } diff --git a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.ResultOperators.cs b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.ResultOperators.cs index f007375308d..340ba8c7c0b 100644 --- a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.ResultOperators.cs +++ b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.ResultOperators.cs @@ -1084,12 +1084,16 @@ public virtual Task Last(bool isAsync) [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Last_when_no_order_by(bool isAsync) + public virtual async Task Last_when_no_order_by(bool isAsync) { - return AssertLast( - isAsync, - cs => cs.Where(c => c.CustomerID == "ALFKI"), - entryCount: 1); + Assert.Equal( + CoreStrings.TranslationFailed(@"Last(Select( source: Where( source: DbSet, predicate: (c) => c.CustomerID == ""ALFKI""), selector: (c) => (object)c))"), + RemoveNewLines( + (await Assert.ThrowsAsync( + () => AssertLast( + isAsync, + cs => cs.Where(c => c.CustomerID == "ALFKI"), + entryCount: 1))).Message)); } [ConditionalTheory] @@ -1492,7 +1496,8 @@ public virtual async Task Contains_with_local_anonymous_type_array_closure(bool o => ids.Contains( new { - Id1 = o.OrderID, Id2 = o.ProductID + Id1 = o.OrderID, + Id2 = o.ProductID })), entryCount: 1))).Message)); } diff --git a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs index 3b0dff722d8..c40e717e2c0 100644 --- a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs @@ -2035,8 +2035,28 @@ public virtual Task Where_query_composition_entity_equality_one_element_SingleOr return AssertQuery( isAsync, es => - from e1 in es.Take(3) + from e1 in es where es.SingleOrDefault(e2 => e2.EmployeeID == e1.ReportsTo) == new Employee() + select e1, + es => + from e1 in es + where es.FirstOrDefault(e2 => e2.EmployeeID == e1.ReportsTo) == new Employee() + select e1); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Where_query_composition_entity_equality_one_element_Single(bool isAsync) + { + return AssertQuery( + isAsync, + es => + from e1 in es + where es.Single(e2 => e2.EmployeeID == e1.ReportsTo) == new Employee() + select e1, + es => + from e1 in es + where es.FirstOrDefault(e2 => e2.EmployeeID == e1.ReportsTo) == new Employee() select e1); } @@ -2052,6 +2072,22 @@ from e1 in es select e1); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Where_query_composition_entity_equality_one_element_First(bool isAsync) + { + return AssertQuery( + isAsync, + es => + from e1 in es + where es.First(e2 => e2.EmployeeID == e1.ReportsTo) == new Employee() + select e1, + es => + from e1 in es + where es.FirstOrDefault(e2 => e2.EmployeeID == e1.ReportsTo) == new Employee() + select e1); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Where_query_composition_entity_equality_no_elements_SingleOrDefault(bool isAsync) @@ -2059,22 +2095,29 @@ public virtual Task Where_query_composition_entity_equality_no_elements_SingleOr return AssertQuery( isAsync, es => - from e1 in es.Take(3) + from e1 in es where es.SingleOrDefault(e2 => e2.EmployeeID == 42) == new Employee() + select e1, + es => + from e1 in es + where es.FirstOrDefault(e2 => e2.EmployeeID == 42) == new Employee() select e1); } - [ConditionalFact(Skip = "#15559")] - public virtual void Where_query_composition_entity_equality_no_elements_Single() + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Where_query_composition_entity_equality_no_elements_Single(bool isAsync) { - using (var ctx = CreateContext()) - { - var query = from e1 in ctx.Set().Take(5) - where ctx.Set().Single(e2 => e2.EmployeeID == 42) == new Employee() - select e1; - - Assert.Throws(() => query.ToList()); - } + return AssertQuery( + isAsync, + es => + from e1 in es + where es.Single(e2 => e2.EmployeeID == 42) == new Employee() + select e1, + es => + from e1 in es + where es.FirstOrDefault(e2 => e2.EmployeeID == 42) == new Employee() + select e1); } [ConditionalTheory] @@ -2089,17 +2132,52 @@ from e1 in es select e1); } - [ConditionalFact(Skip = "#15559")] - public virtual void Where_query_composition_entity_equality_multiple_elements_SingleOrDefault() + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Where_query_composition_entity_equality_no_elements_First(bool isAsync) { - using (var ctx = CreateContext()) - { - var query = from e1 in ctx.Set() - where ctx.Set().SingleOrDefault(e2 => e2.EmployeeID != e1.ReportsTo) == new Employee() - select e1; + return AssertQuery( + isAsync, + es => + from e1 in es + where es.First(e2 => e2.EmployeeID == 42) == new Employee() + select e1, + es => + from e1 in es + where es.FirstOrDefault(e2 => e2.EmployeeID == 42) == new Employee() + select e1); + } - Assert.Throws(() => query.ToList()); - } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Where_query_composition_entity_equality_multiple_elements_SingleOrDefault(bool isAsync) + { + return AssertQuery( + isAsync, + es => + from e1 in es + where es.SingleOrDefault(e2 => e2.EmployeeID != e1.ReportsTo) == new Employee() + select e1, + es => + from e1 in es + where es.FirstOrDefault(e2 => e2.EmployeeID != e1.ReportsTo) == new Employee() + select e1); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Where_query_composition_entity_equality_multiple_elements_Single(bool isAsync) + { + return AssertQuery( + isAsync, + es => + from e1 in es + where es.Single(e2 => e2.EmployeeID != e1.ReportsTo) == new Employee() + select e1, + es => + from e1 in es + where es.FirstOrDefault(e2 => e2.EmployeeID != e1.ReportsTo) == new Employee() + select e1); } [ConditionalTheory] @@ -2114,6 +2192,22 @@ from e1 in es select e1); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Where_query_composition_entity_equality_multiple_elements_First(bool isAsync) + { + return AssertQuery( + isAsync, + es => + from e1 in es + where es.First(e2 => e2.EmployeeID != e1.ReportsTo) == new Employee() + select e1, + es => + from e1 in es + where es.FirstOrDefault(e2 => e2.EmployeeID != e1.ReportsTo) == new Employee() + select e1); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Where_query_composition2(bool isAsync) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index 06a9fe1c85d..0eddf5ad25f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -1461,7 +1461,7 @@ public override async Task Where_subquery_distinct_singleordefault_boolean1(bool @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOrBirthName], [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 (([g].[HasSoulPatch] = CAST(1 AS bit)) AND (( - SELECT TOP(2) [t].[IsAutomatic] + SELECT TOP(1) [t].[IsAutomatic] FROM ( SELECT DISTINCT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapons] AS [w] @@ -1478,7 +1478,7 @@ public override async Task Where_subquery_distinct_singleordefault_boolean2(bool @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOrBirthName], [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 (([g].[HasSoulPatch] = CAST(1 AS bit)) AND (( - SELECT DISTINCT TOP(2) [w].[IsAutomatic] + SELECT DISTINCT TOP(1) [w].[IsAutomatic] FROM [Weapons] AS [w] WHERE (([g].[FullName] = [w].[OwnerFullName]) AND [w].[OwnerFullName] IS NOT NULL) AND (CHARINDEX(N'Lancer', [w].[Name]) > 0)) = CAST(1 AS bit))) ORDER BY [g].[Nickname]"); @@ -1492,7 +1492,7 @@ public override async Task Where_subquery_distinct_singleordefault_boolean_with_ @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOrBirthName], [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 (([g].[HasSoulPatch] = CAST(1 AS bit)) AND (( - SELECT TOP(2) [t].[IsAutomatic] + SELECT TOP(1) [t].[IsAutomatic] FROM ( SELECT DISTINCT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapons] AS [w] @@ -6044,7 +6044,7 @@ public override async Task Select_subquery_distinct_singleordefault_boolean1(boo AssertSql( @"SELECT ( - SELECT TOP(2) [t].[IsAutomatic] + SELECT TOP(1) [t].[IsAutomatic] FROM ( SELECT DISTINCT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapons] AS [w] @@ -6060,7 +6060,7 @@ public override async Task Select_subquery_distinct_singleordefault_boolean2(boo AssertSql( @"SELECT ( - SELECT DISTINCT TOP(2) [w].[IsAutomatic] + SELECT DISTINCT TOP(1) [w].[IsAutomatic] FROM [Weapons] AS [w] WHERE (([g].[FullName] = [w].[OwnerFullName]) AND [w].[OwnerFullName] IS NOT NULL) AND (CHARINDEX(N'Lancer', [w].[Name]) > 0)) FROM [Gears] AS [g] @@ -6073,7 +6073,7 @@ public override async Task Select_subquery_distinct_singleordefault_boolean_with AssertSql( @"SELECT ( - SELECT TOP(2) [t].[IsAutomatic] + SELECT TOP(1) [t].[IsAutomatic] FROM ( SELECT DISTINCT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapons] AS [w] @@ -6089,7 +6089,7 @@ public override async Task Select_subquery_distinct_singleordefault_boolean_empt AssertSql( @"SELECT ( - SELECT TOP(2) [t].[IsAutomatic] + SELECT TOP(1) [t].[IsAutomatic] FROM ( SELECT DISTINCT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapons] AS [w] @@ -6105,7 +6105,7 @@ public override async Task Select_subquery_distinct_singleordefault_boolean_empt AssertSql( @"SELECT ( - SELECT DISTINCT TOP(2) [w].[IsAutomatic] + SELECT DISTINCT TOP(1) [w].[IsAutomatic] FROM [Weapons] AS [w] WHERE (([g].[FullName] = [w].[OwnerFullName]) AND [w].[OwnerFullName] IS NOT NULL) AND (([w].[Name] = N'BFG') AND [w].[Name] IS NOT NULL)) FROM [Gears] AS [g] @@ -6118,7 +6118,7 @@ public override async Task Select_subquery_distinct_singleordefault_boolean_empt AssertSql( @"SELECT ( - SELECT TOP(2) [t].[IsAutomatic] + SELECT TOP(1) [t].[IsAutomatic] FROM ( SELECT DISTINCT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapons] AS [w] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/IncludeSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/IncludeSqlServerTest.cs index d0a4f22f991..d8ab3f81228 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/IncludeSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/IncludeSqlServerTest.cs @@ -83,25 +83,6 @@ ORDER BY [c].[CompanyName] DESC ORDER BY [t].[CompanyName] DESC, [t].[CustomerID], [o].[OrderID]"); } - public override void Include_collection_with_last_no_orderby(bool useString) - { - base.Include_collection_with_last_no_orderby(useString); - - AssertSql( - @"SELECT TOP(1) [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] -FROM [Customers] AS [c] -ORDER BY [c].[CustomerID] DESC", - // - @"SELECT [c.Orders].[OrderID], [c.Orders].[CustomerID], [c.Orders].[EmployeeID], [c.Orders].[OrderDate] -FROM [Orders] AS [c.Orders] -INNER JOIN ( - SELECT TOP(1) [c0].[CustomerID] - FROM [Customers] AS [c0] - ORDER BY [c0].[CustomerID] DESC -) AS [t] ON [c.Orders].[CustomerID] = [t].[CustomerID] -ORDER BY [t].[CustomerID] DESC"); - } - public override void Include_collection_skip_no_order_by(bool useString) { base.Include_collection_skip_no_order_by(useString); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryNavigationsSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryNavigationsSqlServerTest.cs index 11902e9eb09..77d1af30710 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryNavigationsSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryNavigationsSqlServerTest.cs @@ -769,7 +769,7 @@ public override async Task Collection_select_nav_prop_single_or_default_then_nav AssertSql( @"SELECT ( - SELECT TOP(2) [c].[City] + SELECT TOP(1) [c].[City] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] WHERE [o].[OrderID] = 10643) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.ResultOperators.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.ResultOperators.cs index 95eae4410b0..146cdbda4df 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.ResultOperators.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.ResultOperators.cs @@ -740,16 +740,6 @@ FROM [Customers] AS [c] ORDER BY [c].[ContactName] DESC"); } - public override async Task Last_when_no_order_by(bool isAsync) - { - await base.Last_when_no_order_by(isAsync); - - AssertSql( - @"SELECT TOP(1) [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] -FROM [Customers] AS [c] -WHERE [c].[CustomerID] = N'ALFKI'"); - } - public override async Task Last_Predicate(bool isAsync) { await base.Last_Predicate(isAsync); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Select.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Select.cs index a19f60e19a1..cf9858d1ce8 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Select.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Select.cs @@ -621,7 +621,7 @@ public override async Task Project_single_element_from_collection_with_OrderBy_T AssertSql( @"SELECT ( - SELECT TOP(2) [t].[CustomerID] + SELECT TOP(1) [t].[CustomerID] FROM ( SELECT TOP(1) [o].[CustomerID], [o].[OrderID] FROM [Orders] AS [o] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs index 93b0d69ad25..181001f4e9f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs @@ -457,7 +457,7 @@ FROM [Employees] AS [e] ORDER BY [e].[EmployeeID] ) AS [t] WHERE ( - SELECT TOP(2) [e0].[EmployeeID] + SELECT TOP(1) [e0].[EmployeeID] FROM [Employees] AS [e0] WHERE ([e0].[EmployeeID] = [t].[ReportsTo]) AND [t].[ReportsTo] IS NOT NULL) IS NULL ORDER BY [t].[EmployeeID]"); @@ -479,7 +479,7 @@ ORDER BY [e].[EmployeeID] OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY ) AS [t] WHERE ( - SELECT TOP(2) [e0].[EmployeeID] + SELECT TOP(1) [e0].[EmployeeID] FROM [Employees] AS [e0] WHERE ([e0].[EmployeeID] = [t].[ReportsTo]) AND [t].[ReportsTo] IS NOT NULL) IS NOT NULL ORDER BY [t].[EmployeeID]"); @@ -490,20 +490,31 @@ public override async Task Where_query_composition_entity_equality_one_element_S await base.Where_query_composition_entity_equality_one_element_SingleOrDefault(isAsync); AssertSql( - @"@__p_0='3' + @"SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] +FROM [Employees] AS [e] +WHERE (( + SELECT TOP(1) [e0].[EmployeeID] + FROM [Employees] AS [e0] + WHERE ([e0].[EmployeeID] = [e].[ReportsTo]) AND [e].[ReportsTo] IS NOT NULL) = 0) AND ( + SELECT TOP(1) [e0].[EmployeeID] + FROM [Employees] AS [e0] + WHERE ([e0].[EmployeeID] = [e].[ReportsTo]) AND [e].[ReportsTo] IS NOT NULL) IS NOT NULL"); + } -SELECT [t].[EmployeeID], [t].[City], [t].[Country], [t].[FirstName], [t].[ReportsTo], [t].[Title] -FROM ( - SELECT TOP(@__p_0) [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] - FROM [Employees] AS [e] -) AS [t] + public override async Task Where_query_composition_entity_equality_one_element_Single(bool isAsync) + { + await base.Where_query_composition_entity_equality_one_element_Single(isAsync); + + AssertSql( + @"SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] +FROM [Employees] AS [e] WHERE (( - SELECT TOP(2) [e0].[EmployeeID] + SELECT TOP(1) [e0].[EmployeeID] FROM [Employees] AS [e0] - WHERE ([e0].[EmployeeID] = [t].[ReportsTo]) AND [t].[ReportsTo] IS NOT NULL) = 0) AND ( - SELECT TOP(2) [e0].[EmployeeID] + WHERE ([e0].[EmployeeID] = [e].[ReportsTo]) AND [e].[ReportsTo] IS NOT NULL) = 0) AND ( + SELECT TOP(1) [e0].[EmployeeID] FROM [Employees] AS [e0] - WHERE ([e0].[EmployeeID] = [t].[ReportsTo]) AND [t].[ReportsTo] IS NOT NULL) IS NOT NULL"); + WHERE ([e0].[EmployeeID] = [e].[ReportsTo]) AND [e].[ReportsTo] IS NOT NULL) IS NOT NULL"); } public override async Task Where_query_composition_entity_equality_one_element_FirstOrDefault(bool isAsync) @@ -522,23 +533,50 @@ FROM [Employees] AS [e0] WHERE ([e0].[EmployeeID] = [e].[ReportsTo]) AND [e].[ReportsTo] IS NOT NULL) IS NOT NULL"); } + public override async Task Where_query_composition_entity_equality_one_element_First(bool isAsync) + { + await base.Where_query_composition_entity_equality_one_element_First(isAsync); + + AssertSql( + @"SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] +FROM [Employees] AS [e] +WHERE (( + SELECT TOP(1) [e0].[EmployeeID] + FROM [Employees] AS [e0] + WHERE ([e0].[EmployeeID] = [e].[ReportsTo]) AND [e].[ReportsTo] IS NOT NULL) = 0) AND ( + SELECT TOP(1) [e0].[EmployeeID] + FROM [Employees] AS [e0] + WHERE ([e0].[EmployeeID] = [e].[ReportsTo]) AND [e].[ReportsTo] IS NOT NULL) IS NOT NULL"); + } + public override async Task Where_query_composition_entity_equality_no_elements_SingleOrDefault(bool isAsync) { await base.Where_query_composition_entity_equality_no_elements_SingleOrDefault(isAsync); AssertSql( - @"@__p_0='3' + @"SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] +FROM [Employees] AS [e] +WHERE (( + SELECT TOP(1) [e0].[EmployeeID] + FROM [Employees] AS [e0] + WHERE [e0].[EmployeeID] = 42) = 0) AND ( + SELECT TOP(1) [e0].[EmployeeID] + FROM [Employees] AS [e0] + WHERE [e0].[EmployeeID] = 42) IS NOT NULL"); + } -SELECT [t].[EmployeeID], [t].[City], [t].[Country], [t].[FirstName], [t].[ReportsTo], [t].[Title] -FROM ( - SELECT TOP(@__p_0) [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] - FROM [Employees] AS [e] -) AS [t] + public override async Task Where_query_composition_entity_equality_no_elements_Single(bool isAsync) + { + await base.Where_query_composition_entity_equality_no_elements_Single(isAsync); + + AssertSql( + @"SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] +FROM [Employees] AS [e] WHERE (( - SELECT TOP(2) [e0].[EmployeeID] + SELECT TOP(1) [e0].[EmployeeID] FROM [Employees] AS [e0] WHERE [e0].[EmployeeID] = 42) = 0) AND ( - SELECT TOP(2) [e0].[EmployeeID] + SELECT TOP(1) [e0].[EmployeeID] FROM [Employees] AS [e0] WHERE [e0].[EmployeeID] = 42) IS NOT NULL"); } @@ -559,6 +597,54 @@ FROM [Employees] AS [e0] WHERE [e0].[EmployeeID] = 42) IS NOT NULL"); } + public override async Task Where_query_composition_entity_equality_no_elements_First(bool isAsync) + { + await base.Where_query_composition_entity_equality_no_elements_First(isAsync); + + AssertSql( + @"SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] +FROM [Employees] AS [e] +WHERE (( + SELECT TOP(1) [e0].[EmployeeID] + FROM [Employees] AS [e0] + WHERE [e0].[EmployeeID] = 42) = 0) AND ( + SELECT TOP(1) [e0].[EmployeeID] + FROM [Employees] AS [e0] + WHERE [e0].[EmployeeID] = 42) IS NOT NULL"); + } + + public override async Task Where_query_composition_entity_equality_multiple_elements_SingleOrDefault(bool isAsync) + { + await base.Where_query_composition_entity_equality_multiple_elements_SingleOrDefault(isAsync); + + AssertSql( + @"SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] +FROM [Employees] AS [e] +WHERE (( + SELECT TOP(1) [e0].[EmployeeID] + FROM [Employees] AS [e0] + WHERE ([e0].[EmployeeID] <> [e].[ReportsTo]) OR [e].[ReportsTo] IS NULL) = 0) AND ( + SELECT TOP(1) [e0].[EmployeeID] + FROM [Employees] AS [e0] + WHERE ([e0].[EmployeeID] <> [e].[ReportsTo]) OR [e].[ReportsTo] IS NULL) IS NOT NULL"); + } + + public override async Task Where_query_composition_entity_equality_multiple_elements_Single(bool isAsync) + { + await base.Where_query_composition_entity_equality_multiple_elements_Single(isAsync); + + AssertSql( + @"SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] +FROM [Employees] AS [e] +WHERE (( + SELECT TOP(1) [e0].[EmployeeID] + FROM [Employees] AS [e0] + WHERE ([e0].[EmployeeID] <> [e].[ReportsTo]) OR [e].[ReportsTo] IS NULL) = 0) AND ( + SELECT TOP(1) [e0].[EmployeeID] + FROM [Employees] AS [e0] + WHERE ([e0].[EmployeeID] <> [e].[ReportsTo]) OR [e].[ReportsTo] IS NULL) IS NOT NULL"); + } + public override async Task Where_query_composition_entity_equality_multiple_elements_FirstOrDefault(bool isAsync) { await base.Where_query_composition_entity_equality_multiple_elements_FirstOrDefault(isAsync); @@ -575,6 +661,22 @@ FROM [Employees] AS [e0] WHERE ([e0].[EmployeeID] <> [e].[ReportsTo]) OR [e].[ReportsTo] IS NULL) IS NOT NULL"); } + public override async Task Where_query_composition_entity_equality_multiple_elements_First(bool isAsync) + { + await base.Where_query_composition_entity_equality_multiple_elements_First(isAsync); + + AssertSql( + @"SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] +FROM [Employees] AS [e] +WHERE (( + SELECT TOP(1) [e0].[EmployeeID] + FROM [Employees] AS [e0] + WHERE ([e0].[EmployeeID] <> [e].[ReportsTo]) OR [e].[ReportsTo] IS NULL) = 0) AND ( + SELECT TOP(1) [e0].[EmployeeID] + FROM [Employees] AS [e0] + WHERE ([e0].[EmployeeID] <> [e].[ReportsTo]) OR [e].[ReportsTo] IS NULL) IS NOT NULL"); + } + public override async Task Where_query_composition2(bool isAsync) { await base.Where_query_composition2(isAsync); @@ -675,14 +777,14 @@ public override void Select_Where_Subquery_Deep_Single() SELECT TOP(@__p_0) [o].[OrderID], [o].[ProductID], [o].[Discount], [o].[Quantity], [o].[UnitPrice] FROM [Order Details] AS [o] WHERE ([o].[OrderID] = 10344) AND ((( - SELECT TOP(2) ( - SELECT TOP(2) [c].[City] + SELECT TOP(1) ( + SELECT TOP(1) [c].[City] FROM [Customers] AS [c] WHERE ([o0].[CustomerID] = [c].[CustomerID]) AND [o0].[CustomerID] IS NOT NULL) FROM [Orders] AS [o0] WHERE [o].[OrderID] = [o0].[OrderID]) = N'Seattle') AND ( - SELECT TOP(2) ( - SELECT TOP(2) [c].[City] + SELECT TOP(1) ( + SELECT TOP(1) [c].[City] FROM [Customers] AS [c] WHERE ([o0].[CustomerID] = [c].[CustomerID]) AND [o0].[CustomerID] IS NOT NULL) FROM [Orders] AS [o0] @@ -2961,7 +3063,7 @@ public override async Task Subquery_member_pushdown_does_not_change_original_sub @"@__p_0='3' SELECT [t].[OrderID] AS [OrderId], ( - SELECT TOP(2) [c].[City] + SELECT TOP(1) [c].[City] FROM [Customers] AS [c] WHERE ([c].[CustomerID] = [t].[CustomerID]) AND [t].[CustomerID] IS NOT NULL) AS [City] FROM ( @@ -2970,7 +3072,7 @@ FROM [Orders] AS [o] ORDER BY [o].[OrderID] ) AS [t] ORDER BY ( - SELECT TOP(2) [c0].[City] + SELECT TOP(1) [c0].[City] FROM [Customers] AS [c0] WHERE ([c0].[CustomerID] = [t].[CustomerID]) AND [t].[CustomerID] IS NOT NULL)"); } @@ -2983,7 +3085,7 @@ public override async Task Subquery_member_pushdown_does_not_change_original_sub @"@__p_0='3' SELECT [t].[OrderID] AS [OrderId], ( - SELECT TOP(2) [c].[City] + SELECT TOP(1) [c].[City] FROM [Customers] AS [c] WHERE ([c].[CustomerID] = [t].[CustomerID]) AND [t].[CustomerID] IS NOT NULL) AS [City] FROM ( @@ -2992,7 +3094,7 @@ FROM [Orders] AS [o] ORDER BY [o].[OrderID] ) AS [t] ORDER BY ( - SELECT TOP(2) [c0].[City] + SELECT TOP(1) [c0].[City] FROM [Customers] AS [c0] WHERE ([c0].[CustomerID] = [t].[CustomerID]) AND [t].[CustomerID] IS NOT NULL)"); }