From 8a331642cf4353ec84c4c95f2fd3330c4c3c0853 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Wed, 11 May 2022 16:45:02 -0700 Subject: [PATCH] Query: Translate GetType == type on hierarchy Resolves #13424 --- .../CosmosSqlTranslatingExpressionVisitor.cs | 89 ++++++++++ ...yExpressionTranslatingExpressionVisitor.cs | 87 ++++++++++ ...lationalSqlTranslatingExpressionVisitor.cs | 160 ++++++++++++++++++ .../Query/InheritanceQueryCosmosTest.cs | 60 +++++++ .../Query/NorthwindWhereQueryCosmosTest.cs | 44 ++++- .../Query/InheritanceQueryTestBase.cs | 46 +++++ .../Query/ManyToManyQueryTestBase.cs | 32 ++++ .../Query/NorthwindWhereQueryTestBase.cs | 35 +++- ...eteMappingInheritanceQuerySqlServerTest.cs | 61 +++++++ .../Query/InheritanceQuerySqlServerTest.cs | 61 +++++++ .../Query/NorthwindWhereQuerySqlServerTest.cs | 38 +++++ .../Query/TPCInheritanceQuerySqlServerTest.cs | 76 +++++++++ ...CManyToManyNoTrackingQuerySqlServerTest.cs | 43 +++++ .../Query/TPCManyToManyQuerySqlServerTest.cs | 43 +++++ .../Query/TPTInheritanceQuerySqlServerTest.cs | 97 +++++++++++ ...TManyToManyNoTrackingQuerySqlServerTest.cs | 59 +++++++ .../Query/TPTManyToManyQuerySqlServerTest.cs | 58 +++++++ 17 files changed, 1085 insertions(+), 4 deletions(-) diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs index b477fee7ce4..e8651170d52 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs @@ -33,6 +33,8 @@ private static readonly MethodInfo StringEqualsWithStringComparison private static readonly MethodInfo StringEqualsWithStringComparisonStatic = typeof(string).GetRuntimeMethod(nameof(string.Equals), new[] { typeof(string), typeof(string), typeof(StringComparison) }); + private static readonly MethodInfo GetTypeMethodInfo = typeof(object).GetTypeInfo().GetDeclaredMethod(nameof(object.GetType))!; + private readonly QueryCompilationContext _queryCompilationContext; private readonly IModel _model; private readonly ISqlExpressionFactory _sqlExpressionFactory; @@ -145,6 +147,22 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) ifFalse)); } + if (binaryExpression.NodeType == ExpressionType.Equal || binaryExpression.NodeType == ExpressionType.NotEqual + && binaryExpression.Left.Type == typeof(Type)) + { + if (IsGetTypeMethodCall(binaryExpression.Left, out var entityReference1) + && IsTypeConstant(binaryExpression.Right, out var type1)) + { + return ProcessGetType(entityReference1!, type1!, binaryExpression.NodeType == ExpressionType.Equal); + } + + if (IsGetTypeMethodCall(binaryExpression.Right, out var entityReference2) + && IsTypeConstant(binaryExpression.Left, out var type2)) + { + return ProcessGetType(entityReference2!, type2!, binaryExpression.NodeType == ExpressionType.Equal); + } + } + var left = TryRemoveImplicitConvert(binaryExpression.Left); var right = TryRemoveImplicitConvert(binaryExpression.Right); @@ -199,6 +217,77 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) sqlRight, null); + Expression ProcessGetType(EntityReferenceExpression entityReferenceExpression, Type comparisonType, bool match) + { + var entityType = entityReferenceExpression.EntityType; + + if (entityType.BaseType == null + && !entityType.GetDirectlyDerivedTypes().Any()) + { + // No hierarchy + return _sqlExpressionFactory.Constant((entityType.ClrType == comparisonType) == match); + } + + if (entityType.GetAllBaseTypes().Any(e => e.ClrType == comparisonType)) + { + // EntitySet will never contain a type of base type + return _sqlExpressionFactory.Constant(!match); + } + + var derivedType = entityType.GetDerivedTypesInclusive().SingleOrDefault(et => et.ClrType == comparisonType); + // If no derived type matches then fail the translation + if (derivedType != null) + { + // If the derived type is abstract type then predicate will always be false + if (derivedType.IsAbstract()) + { + return _sqlExpressionFactory.Constant(!match); + } + + // Or add predicate for matching that particular type discriminator value + // All hierarchies have discriminator property + if (TryBindMember(entityReferenceExpression, MemberIdentity.Create(entityType.GetDiscriminatorPropertyName())) + is SqlExpression discriminatorColumn) + { + return match + ? _sqlExpressionFactory.Equal( + discriminatorColumn, + _sqlExpressionFactory.Constant(derivedType.GetDiscriminatorValue())) + : _sqlExpressionFactory.NotEqual( + discriminatorColumn, + _sqlExpressionFactory.Constant(derivedType.GetDiscriminatorValue())); + } + } + + return QueryCompilationContext.NotTranslatedExpression; + } + + bool IsGetTypeMethodCall(Expression expression, out EntityReferenceExpression entityReferenceExpression) + { + entityReferenceExpression = null; + if (expression is not MethodCallExpression methodCallExpression + || methodCallExpression.Method != GetTypeMethodInfo) + { + return false; + } + + entityReferenceExpression = Visit(methodCallExpression.Object) as EntityReferenceExpression; + return entityReferenceExpression != null; + } + + static bool IsTypeConstant(Expression expression, out Type type) + { + type = null; + if (expression is not UnaryExpression { NodeType: ExpressionType.Convert or ExpressionType.ConvertChecked } unaryExpression + || unaryExpression.Operand is not ConstantExpression constantExpression) + { + return false; + } + + type = constantExpression.Value as Type; + return type != null; + } + static bool TryUnwrapConvertToObject(Expression expression, out Expression operand) { if (expression is UnaryExpression convertExpression diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs index 25957b2b815..79d7e5bda50 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs @@ -64,6 +64,8 @@ public class InMemoryExpressionTranslatingExpressionVisitor : ExpressionVisitor private static readonly MethodInfo InMemoryLikeMethodInfo = typeof(InMemoryExpressionTranslatingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(InMemoryLike))!; + private static readonly MethodInfo GetTypeMethodInfo = typeof(object).GetTypeInfo().GetDeclaredMethod(nameof(object.GetType))!; + // Regex special chars defined here: // https://msdn.microsoft.com/en-us/library/4edbef7e(v=vs.110).aspx private static readonly char[] RegexSpecialChars @@ -205,6 +207,22 @@ static Expression RemoveConvert(Expression e) } } + if (binaryExpression.NodeType == ExpressionType.Equal || binaryExpression.NodeType == ExpressionType.NotEqual + && binaryExpression.Left.Type == typeof(Type)) + { + if (IsGetTypeMethodCall(binaryExpression.Left, out var entityReference1) + && IsTypeConstant(binaryExpression.Right, out var type1)) + { + return ProcessGetType(entityReference1!, type1!, binaryExpression.NodeType == ExpressionType.Equal); + } + + if (IsGetTypeMethodCall(binaryExpression.Right, out var entityReference2) + && IsTypeConstant(binaryExpression.Left, out var type2)) + { + return ProcessGetType(entityReference2!, type2!, binaryExpression.NodeType == ExpressionType.Equal); + } + } + var newLeft = Visit(binaryExpression.Left); var newRight = Visit(binaryExpression.Right); @@ -317,6 +335,75 @@ static Expression RemoveConvert(Expression e) binaryExpression.IsLiftedToNull, binaryExpression.Method, binaryExpression.Conversion); + + Expression ProcessGetType(EntityReferenceExpression entityReferenceExpression, Type comparisonType, bool match) + { + var entityType = entityReferenceExpression.EntityType; + + if (entityType.BaseType == null + && !entityType.GetDirectlyDerivedTypes().Any()) + { + // No hierarchy + return Expression.Constant((entityType.ClrType == comparisonType) == match); + } + if (entityType.GetAllBaseTypes().Any(e => e.ClrType == comparisonType)) + { + // EntitySet will never contain a type of base type + return Expression.Constant(!match); + } + + var derivedType = entityType.GetDerivedTypesInclusive().SingleOrDefault(et => et.ClrType == comparisonType); + // If no derived type matches then fail the translation + if (derivedType != null) + { + // If the derived type is abstract type then predicate will always be false + if (derivedType.IsAbstract()) + { + return Expression.Constant(!match); + } + + // Or add predicate for matching that particular type discriminator value + // All hierarchies have discriminator property + var discriminatorProperty = entityType.FindDiscriminatorProperty()!; + var boundProperty = BindProperty(entityReferenceExpression, discriminatorProperty, discriminatorProperty.ClrType); + // KeyValueComparer is not null at runtime + var valueComparer = discriminatorProperty.GetKeyValueComparer(); + + var result = valueComparer.ExtractEqualsBody( + boundProperty!, + Expression.Constant(derivedType.GetDiscriminatorValue(), discriminatorProperty.ClrType)); + + return match ? result : Expression.Not(result); + } + + return QueryCompilationContext.NotTranslatedExpression; + } + + bool IsGetTypeMethodCall(Expression expression, out EntityReferenceExpression? entityReferenceExpression) + { + entityReferenceExpression = null; + if (expression is not MethodCallExpression methodCallExpression + || methodCallExpression.Method != GetTypeMethodInfo) + { + return false; + } + + entityReferenceExpression = Visit(methodCallExpression.Object) as EntityReferenceExpression; + return entityReferenceExpression != null; + } + + static bool IsTypeConstant(Expression expression, out Type? type) + { + type = null; + if (expression is not UnaryExpression { NodeType: ExpressionType.Convert or ExpressionType.ConvertChecked } unaryExpression + || unaryExpression.Operand is not ConstantExpression constantExpression) + { + return false; + } + + type = constantExpression.Value as Type; + return type != null; + } } /// diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index fb1205c170f..dd6922d2749 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -62,6 +62,8 @@ private static readonly MethodInfo StringEqualsWithStringComparisonStatic private static readonly MethodInfo ObjectEqualsMethodInfo = typeof(object).GetRuntimeMethod(nameof(object.Equals), new[] { typeof(object), typeof(object) })!; + private static readonly MethodInfo GetTypeMethodInfo = typeof(object).GetTypeInfo().GetDeclaredMethod(nameof(object.GetType))!; + private readonly QueryCompilationContext _queryCompilationContext; private readonly IModel _model; private readonly ISqlExpressionFactory _sqlExpressionFactory; @@ -290,6 +292,22 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) return Visit(ConvertObjectArrayEqualityComparison(binaryExpression.Left, binaryExpression.Right)); } + if (binaryExpression.NodeType == ExpressionType.Equal || binaryExpression.NodeType == ExpressionType.NotEqual + && binaryExpression.Left.Type == typeof(Type)) + { + if (IsGetTypeMethodCall(binaryExpression.Left, out var entityReference1) + && IsTypeConstant(binaryExpression.Right, out var type1)) + { + return ProcessGetType(entityReference1!, type1!, binaryExpression.NodeType == ExpressionType.Equal); + } + + if (IsGetTypeMethodCall(binaryExpression.Right, out var entityReference2) + && IsTypeConstant(binaryExpression.Left, out var type2)) + { + return ProcessGetType(entityReference2!, type2!, binaryExpression.NodeType == ExpressionType.Equal); + } + } + var left = TryRemoveImplicitConvert(binaryExpression.Left); var right = TryRemoveImplicitConvert(binaryExpression.Right); @@ -389,6 +407,148 @@ static Expression RemoveConvert(Expression e) null) ?? QueryCompilationContext.NotTranslatedExpression; + Expression ProcessGetType(EntityReferenceExpression entityReferenceExpression, Type comparisonType, bool match) + { + var entityType = entityReferenceExpression.EntityType; + + if (entityType.BaseType == null + && !entityType.GetDirectlyDerivedTypes().Any()) + { + // No hierarchy + return _sqlExpressionFactory.Constant((entityType.ClrType == comparisonType) == match); + } + + if (entityType.GetAllBaseTypes().Any(e => e.ClrType == comparisonType)) + { + // EntitySet will never contain a type of base type + return _sqlExpressionFactory.Constant(!match); + } + + var derivedType = entityType.GetDerivedTypesInclusive().SingleOrDefault(et => et.ClrType == comparisonType); + // If no derived type matches then fail the translation + if (derivedType != null) + { + // If the derived type is abstract type then predicate will always be false + if (derivedType.IsAbstract()) + { + return _sqlExpressionFactory.Constant(!match); + } + + // Or add predicate for matching that particular type discriminator value + var discriminatorProperty = entityType.FindDiscriminatorProperty(); + if (discriminatorProperty == null) + { + // TPT or TPC + var discriminatorValue = derivedType.ShortName(); + if (entityReferenceExpression.SubqueryEntity != null) + { + var entityShaper = (EntityShaperExpression)entityReferenceExpression.SubqueryEntity.ShaperExpression; + var entityProjection = (EntityProjectionExpression)Visit(entityShaper.ValueBufferExpression); + var subSelectExpression = (SelectExpression)entityReferenceExpression.SubqueryEntity.QueryExpression; + + var predicate = GeneratePredicateTpt(entityProjection); + + subSelectExpression.ApplyPredicate(predicate); + subSelectExpression.ReplaceProjection(new List()); + subSelectExpression.ApplyProjection(); + if (subSelectExpression.Limit == null + && subSelectExpression.Offset == null) + { + subSelectExpression.ClearOrdering(); + } + + return _sqlExpressionFactory.Exists(subSelectExpression, false); + } + + if (entityReferenceExpression.ParameterEntity != null) + { + var entityProjection = (EntityProjectionExpression)Visit( + entityReferenceExpression.ParameterEntity.ValueBufferExpression); + + return GeneratePredicateTpt(entityProjection); + } + + SqlExpression GeneratePredicateTpt(EntityProjectionExpression entityProjectionExpression) + { + if (entityProjectionExpression.DiscriminatorExpression is CaseExpression caseExpression) + { + // TPT case + // Most root type doesn't have matching case + // All derived types needs to be excluded + var derivedTypeValues = derivedType.GetDerivedTypes().Where(e => !e.IsAbstract()).Select(e => e.ShortName()).ToList(); + var predicates = new List(); + foreach (var caseWhenClause in caseExpression.WhenClauses) + { + var value = (string)((SqlConstantExpression)caseWhenClause.Result).Value!; + if (value == discriminatorValue) + { + predicates.Add(caseWhenClause.Test); + } + else if (derivedTypeValues.Contains(value)) + { + predicates.Add(_sqlExpressionFactory.Not(caseWhenClause.Test)); + } + } + + var result = predicates.Aggregate((a, b) => _sqlExpressionFactory.AndAlso(a, b)); + + return match ? result : _sqlExpressionFactory.Not(result); + } + + return match + ? _sqlExpressionFactory.Equal( + entityProjectionExpression.DiscriminatorExpression!, + _sqlExpressionFactory.Constant(discriminatorValue)) + : _sqlExpressionFactory.NotEqual( + entityProjectionExpression.DiscriminatorExpression!, + _sqlExpressionFactory.Constant(discriminatorValue)); + } + } + else + { + var discriminatorColumn = BindProperty(entityReferenceExpression, discriminatorProperty); + if (discriminatorColumn != null) + { + return match + ? _sqlExpressionFactory.Equal( + discriminatorColumn, + _sqlExpressionFactory.Constant(derivedType.GetDiscriminatorValue())) + : _sqlExpressionFactory.NotEqual( + discriminatorColumn, + _sqlExpressionFactory.Constant(derivedType.GetDiscriminatorValue())); + } + } + } + + return QueryCompilationContext.NotTranslatedExpression; + } + + bool IsGetTypeMethodCall(Expression expression, out EntityReferenceExpression? entityReferenceExpression) + { + entityReferenceExpression = null; + if (expression is not MethodCallExpression methodCallExpression + || methodCallExpression.Method != GetTypeMethodInfo) + { + return false; + } + + entityReferenceExpression = Visit(methodCallExpression.Object) as EntityReferenceExpression; + return entityReferenceExpression != null; + } + + static bool IsTypeConstant(Expression expression, out Type? type) + { + type = null; + if (expression is not UnaryExpression { NodeType: ExpressionType.Convert or ExpressionType.ConvertChecked } unaryExpression + || unaryExpression.Operand is not ConstantExpression constantExpression) + { + return false; + } + + type = constantExpression.Value as Type; + return type != null; + } + static bool TryUnwrapConvertToObject(Expression expression, out Expression? operand) { if (expression is UnaryExpression convertExpression diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/InheritanceQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/InheritanceQueryCosmosTest.cs index 37c0acb0ee3..3161e07ca44 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/InheritanceQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/InheritanceQueryCosmosTest.cs @@ -438,6 +438,66 @@ FROM root c WHERE c[""Discriminator""] IN (""Eagle"", ""Kiwi"")"); } + public override async Task GetType_in_hierarchy_in_abstract_base_type(bool async) + { + await base.GetType_in_hierarchy_in_abstract_base_type(async); + + AssertSql( + @"SELECT c +FROM root c +WHERE (c[""Discriminator""] IN (""Eagle"", ""Kiwi"") AND false)"); + } + + public override async Task GetType_in_hierarchy_in_intermediate_type(bool async) + { + await base.GetType_in_hierarchy_in_intermediate_type(async); + + AssertSql( + @"SELECT c +FROM root c +WHERE (c[""Discriminator""] IN (""Eagle"", ""Kiwi"") AND false)"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type_with_sibling(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type_with_sibling(async); + + AssertSql( + @"SELECT c +FROM root c +WHERE (c[""Discriminator""] IN (""Eagle"", ""Kiwi"") AND (c[""Discriminator""] = ""Eagle""))"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type_with_sibling2(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type_with_sibling2(async); + + AssertSql( + @"SELECT c +FROM root c +WHERE (c[""Discriminator""] IN (""Eagle"", ""Kiwi"") AND (c[""Discriminator""] = ""Kiwi""))"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type_with_sibling2_reverse(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type_with_sibling2_reverse(async); + + AssertSql( + @"SELECT c +FROM root c +WHERE (c[""Discriminator""] IN (""Eagle"", ""Kiwi"") AND (c[""Discriminator""] = ""Kiwi""))"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type_with_sibling2_not_equal(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type_with_sibling2_not_equal(async); + + AssertSql( + @"SELECT c +FROM root c +WHERE (c[""Discriminator""] IN (""Eagle"", ""Kiwi"") AND (c[""Discriminator""] != ""Kiwi""))"); + } + protected override bool EnforcesFkConstraints => false; diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs index 1bb07656307..8a813c9c982 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs @@ -2205,20 +2205,60 @@ FROM root c WHERE ((c[""Discriminator""] = ""Customer"") AND (c[""CustomerID""] IN (""ALFKI"", ""FISSA"") OR (c[""City""] = ""Seattle"")))"); } - public async override Task Where_Like_and_comparison(bool async) + public override async Task Where_Like_and_comparison(bool async) { await AssertTranslationFailed(() => base.Where_Like_and_comparison(async)); AssertSql(); } - public async override Task Where_Like_or_comparison(bool async) + public override async Task Where_Like_or_comparison(bool async) { await AssertTranslationFailed(() => base.Where_Like_or_comparison(async)); AssertSql(); } + public override async Task GetType_on_non_hierarchy1(bool async) + { + await base.GetType_on_non_hierarchy1(async); + + AssertSql( + @"SELECT c +FROM root c +WHERE (c[""Discriminator""] = ""Customer"")"); + } + + public override async Task GetType_on_non_hierarchy2(bool async) + { + await base.GetType_on_non_hierarchy2(async); + + AssertSql( + @"SELECT c +FROM root c +WHERE ((c[""Discriminator""] = ""Customer"") AND false)"); + } + + public override async Task GetType_on_non_hierarchy3(bool async) + { + await base.GetType_on_non_hierarchy3(async); + + AssertSql( + @"SELECT c +FROM root c +WHERE ((c[""Discriminator""] = ""Customer"") AND false)"); + } + + public override async Task GetType_on_non_hierarchy4(bool async) + { + await base.GetType_on_non_hierarchy4(async); + + AssertSql( + @"SELECT c +FROM root c +WHERE (c[""Discriminator""] = ""Customer"")"); + } + public override async Task Where_compare_null_with_cast_to_object(bool async) { await base.Where_compare_null_with_cast_to_object(async); diff --git a/test/EFCore.Specification.Tests/Query/InheritanceQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/InheritanceQueryTestBase.cs index 47cf1f981ae..d7780895293 100644 --- a/test/EFCore.Specification.Tests/Query/InheritanceQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/InheritanceQueryTestBase.cs @@ -505,6 +505,52 @@ public virtual Task Using_OfType_on_multiple_type_with_no_result(bool async) ss => ss.Set().OfType().OfType(), elementSorter: e => e.Name)); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GetType_in_hierarchy_in_abstract_base_type(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.GetType() == typeof(Animal))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GetType_in_hierarchy_in_intermediate_type(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.GetType() == typeof(Bird))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GetType_in_hierarchy_in_leaf_type_with_sibling(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.GetType() == typeof(Eagle)), + entryCount: 1); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GetType_in_hierarchy_in_leaf_type_with_sibling2(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.GetType() == typeof(Kiwi)), + entryCount: 1); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GetType_in_hierarchy_in_leaf_type_with_sibling2_reverse(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => typeof(Kiwi) == e.GetType()), + entryCount: 1); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GetType_in_hierarchy_in_leaf_type_with_sibling2_not_equal(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => typeof(Kiwi) != e.GetType()), + entryCount: 1); + protected InheritanceContext CreateContext() => Fixture.CreateContext(); diff --git a/test/EFCore.Specification.Tests/Query/ManyToManyQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ManyToManyQueryTestBase.cs index d89b1ad8c4c..9e14fa2c1c9 100644 --- a/test/EFCore.Specification.Tests/Query/ManyToManyQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ManyToManyQueryTestBase.cs @@ -725,6 +725,38 @@ public virtual Task Contains_on_skip_collection_navigation(bool async) entryCount: 11); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GetType_in_hierarchy_in_base_type(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.GetType() == typeof(EntityRoot)), + entryCount: 10); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GetType_in_hierarchy_in_intermediate_type(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.GetType() == typeof(EntityBranch)), + entryCount: 6); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GetType_in_hierarchy_in_leaf_type(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.GetType() == typeof(EntityLeaf)), + entryCount: 4); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GetType_in_hierarchy_in_querying_base_type(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.GetType() == typeof(EntityRoot)), + entryCount: 0); + // When adding include test here always add a tracking version and a split version in relational layer. // Keep this line at the bottom for next dev writing tests to see. diff --git a/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs index c0009e3327b..8ffd92192e3 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs @@ -478,7 +478,8 @@ public virtual Task Where_bitwise_and(bool async) ss => ss.Set().Where(c => c.CustomerID == "ALFKI" & c.CustomerID == "ANATR")); [ConditionalTheory] - [InlineData(false)] public virtual Task Where_bitwise_xor(bool async) + [InlineData(false)] + public virtual Task Where_bitwise_xor(bool async) => AssertQuery( async, ss => ss.Set().Where(c => (c.CustomerID == "ALFKI") ^ true), @@ -1065,7 +1066,7 @@ public virtual Task Where_select_many_and(bool async) ss => from c in ss.Set() from e in ss.Set() - // ReSharper disable ArrangeRedundantParentheses + // ReSharper disable ArrangeRedundantParentheses #pragma warning disable RCS1032 // Remove redundant parentheses. where (c.City == "London" && c.Country == "UK") && (e.City == "London" && e.Country == "UK") @@ -2402,4 +2403,34 @@ public virtual Task Where_Like_or_comparison(bool async) ss => ss.Set().Where(c => EF.Functions.Like(c.CustomerID, "F%") || c.City == "Seattle"), ss => ss.Set().Where(c => c.CustomerID.StartsWith("F") || c.City == "Seattle"), entryCount: 9); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GetType_on_non_hierarchy1(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(c => c.GetType() == typeof(Customer)), + entryCount: 91); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GetType_on_non_hierarchy2(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(c => c.GetType() != typeof(Customer))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GetType_on_non_hierarchy3(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(c => c.GetType() == typeof(Order))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GetType_on_non_hierarchy4(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(c => c.GetType() != typeof(Order)), + entryCount: 91); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/IncompleteMappingInheritanceQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/IncompleteMappingInheritanceQuerySqlServerTest.cs index 32dd91e7965..adc75cf5ab3 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/IncompleteMappingInheritanceQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/IncompleteMappingInheritanceQuerySqlServerTest.cs @@ -629,6 +629,7 @@ FROM [Animals] AS [a] INSERT INTO [Animals] ([Species], [CountryId], [Discriminator], [EagleId], [Group], [IsFlightless], [Name]) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6);"); } + public override async Task Using_is_operator_on_multiple_type_with_no_result(bool async) { await base.Using_is_operator_on_multiple_type_with_no_result(async); @@ -656,6 +657,66 @@ public override async Task Using_OfType_on_multiple_type_with_no_result(bool asy AssertSql(); } + public override async Task GetType_in_hierarchy_in_abstract_base_type(bool async) + { + await base.GetType_in_hierarchy_in_abstract_base_type(async); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM [Animals] AS [a] +WHERE 0 = 1"); + } + + public override async Task GetType_in_hierarchy_in_intermediate_type(bool async) + { + await base.GetType_in_hierarchy_in_intermediate_type(async); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM [Animals] AS [a] +WHERE 0 = 1"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type_with_sibling(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type_with_sibling(async); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM [Animals] AS [a] +WHERE [a].[Discriminator] IN (N'Eagle', N'Kiwi') AND [a].[Discriminator] = N'Eagle'"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type_with_sibling2(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type_with_sibling2(async); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM [Animals] AS [a] +WHERE [a].[Discriminator] IN (N'Eagle', N'Kiwi') AND [a].[Discriminator] = N'Kiwi'"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type_with_sibling2_reverse(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type_with_sibling2_reverse(async); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM [Animals] AS [a] +WHERE [a].[Discriminator] IN (N'Eagle', N'Kiwi') AND [a].[Discriminator] = N'Kiwi'"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type_with_sibling2_not_equal(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type_with_sibling2_not_equal(async); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM [Animals] AS [a] +WHERE [a].[Discriminator] IN (N'Eagle', N'Kiwi') AND [a].[Discriminator] <> N'Kiwi'"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/InheritanceQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/InheritanceQuerySqlServerTest.cs index 7c50a4239c9..7f2dabb7c9b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/InheritanceQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/InheritanceQuerySqlServerTest.cs @@ -600,6 +600,7 @@ FROM [Animals] AS [a] INSERT INTO [Animals] ([Species], [CountryId], [Discriminator], [EagleId], [Group], [IsFlightless], [Name]) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6);"); } + public override async Task Using_is_operator_on_multiple_type_with_no_result(bool async) { await base.Using_is_operator_on_multiple_type_with_no_result(async); @@ -627,6 +628,66 @@ public override async Task Using_OfType_on_multiple_type_with_no_result(bool asy AssertSql(); } + public override async Task GetType_in_hierarchy_in_abstract_base_type(bool async) + { + await base.GetType_in_hierarchy_in_abstract_base_type(async); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM [Animals] AS [a] +WHERE 0 = 1"); + } + + public override async Task GetType_in_hierarchy_in_intermediate_type(bool async) + { + await base.GetType_in_hierarchy_in_intermediate_type(async); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM [Animals] AS [a] +WHERE 0 = 1"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type_with_sibling(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type_with_sibling(async); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM [Animals] AS [a] +WHERE [a].[Discriminator] = N'Eagle'"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type_with_sibling2(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type_with_sibling2(async); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM [Animals] AS [a] +WHERE [a].[Discriminator] = N'Kiwi'"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type_with_sibling2_reverse(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type_with_sibling2_reverse(async); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM [Animals] AS [a] +WHERE [a].[Discriminator] = N'Kiwi'"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type_with_sibling2_not_equal(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type_with_sibling2_not_equal(async); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Discriminator], [a].[Name], [a].[EagleId], [a].[IsFlightless], [a].[Group], [a].[FoundOn] +FROM [Animals] AS [a] +WHERE [a].[Discriminator] <> N'Kiwi'"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs index c320a7d8dda..fc685881a10 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs @@ -2489,6 +2489,44 @@ FROM [Customers] AS [c] WHERE ([c].[CustomerID] LIKE N'F%') OR [c].[City] = N'Seattle'"); } + public override async Task GetType_on_non_hierarchy1(bool async) + { + await base.GetType_on_non_hierarchy1(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]"); + } + + public override async Task GetType_on_non_hierarchy2(bool async) + { + await base.GetType_on_non_hierarchy2(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 0 = 1"); + } + + public override async Task GetType_on_non_hierarchy3(bool async) + { + await base.GetType_on_non_hierarchy3(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 0 = 1"); + } + + public override async Task GetType_on_non_hierarchy4(bool async) + { + await base.GetType_on_non_hierarchy4(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]"); + } + public override async Task Where_poco_closure(bool async) { await base.Where_poco_closure(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCInheritanceQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCInheritanceQuerySqlServerTest.cs index 1b181be055b..9f3c6de706a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCInheritanceQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCInheritanceQuerySqlServerTest.cs @@ -615,6 +615,7 @@ public override void Using_from_sql_throws() AssertSql(); } + public override async Task Using_is_operator_on_multiple_type_with_no_result(bool async) { await base.Using_is_operator_on_multiple_type_with_no_result(async); @@ -648,6 +649,81 @@ public override async Task Using_OfType_on_multiple_type_with_no_result(bool asy AssertSql(); } + public override async Task GetType_in_hierarchy_in_abstract_base_type(bool async) + { + await base.GetType_in_hierarchy_in_abstract_base_type(async); + + AssertSql( + @"SELECT [t].[Species], [t].[CountryId], [t].[Name], [t].[EagleId], [t].[IsFlightless], [t].[Group], [t].[FoundOn], [t].[Discriminator] +FROM ( + SELECT [e].[Species], [e].[CountryId], [e].[Name], [e].[EagleId], [e].[IsFlightless], [e].[Group], NULL AS [FoundOn], N'Eagle' AS [Discriminator] + FROM [Eagle] AS [e] + UNION ALL + SELECT [k].[Species], [k].[CountryId], [k].[Name], [k].[EagleId], [k].[IsFlightless], NULL AS [Group], [k].[FoundOn], N'Kiwi' AS [Discriminator] + FROM [Kiwi] AS [k] +) AS [t] +WHERE 0 = 1"); + } + + public override async Task GetType_in_hierarchy_in_intermediate_type(bool async) + { + await base.GetType_in_hierarchy_in_intermediate_type(async); + + AssertSql( + @"SELECT [t].[Species], [t].[CountryId], [t].[Name], [t].[EagleId], [t].[IsFlightless], [t].[Group], [t].[FoundOn], [t].[Discriminator] +FROM ( + SELECT [e].[Species], [e].[CountryId], [e].[Name], [e].[EagleId], [e].[IsFlightless], [e].[Group], NULL AS [FoundOn], N'Eagle' AS [Discriminator] + FROM [Eagle] AS [e] + UNION ALL + SELECT [k].[Species], [k].[CountryId], [k].[Name], [k].[EagleId], [k].[IsFlightless], NULL AS [Group], [k].[FoundOn], N'Kiwi' AS [Discriminator] + FROM [Kiwi] AS [k] +) AS [t] +WHERE 0 = 1"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type_with_sibling(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type_with_sibling(async); + + AssertSql( + @"SELECT [e].[Species], [e].[CountryId], [e].[Name], [e].[EagleId], [e].[IsFlightless], [e].[Group], NULL AS [FoundOn], N'Eagle' AS [Discriminator] +FROM [Eagle] AS [e]"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type_with_sibling2(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type_with_sibling2(async); + + AssertSql( + @"SELECT [k].[Species], [k].[CountryId], [k].[Name], [k].[EagleId], [k].[IsFlightless], NULL AS [Group], [k].[FoundOn], N'Kiwi' AS [Discriminator] +FROM [Kiwi] AS [k]"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type_with_sibling2_reverse(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type_with_sibling2_reverse(async); + + AssertSql( + @"SELECT [k].[Species], [k].[CountryId], [k].[Name], [k].[EagleId], [k].[IsFlightless], NULL AS [Group], [k].[FoundOn], N'Kiwi' AS [Discriminator] +FROM [Kiwi] AS [k]"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type_with_sibling2_not_equal(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type_with_sibling2_not_equal(async); + + AssertSql( + @"SELECT [t].[Species], [t].[CountryId], [t].[Name], [t].[EagleId], [t].[IsFlightless], [t].[Group], [t].[FoundOn], [t].[Discriminator] +FROM ( + SELECT [e].[Species], [e].[CountryId], [e].[Name], [e].[EagleId], [e].[IsFlightless], [e].[Group], NULL AS [FoundOn], N'Eagle' AS [Discriminator] + FROM [Eagle] AS [e] + UNION ALL + SELECT [k].[Species], [k].[CountryId], [k].[Name], [k].[EagleId], [k].[IsFlightless], NULL AS [Group], [k].[FoundOn], N'Kiwi' AS [Discriminator] + FROM [Kiwi] AS [k] +) AS [t] +WHERE [t].[Discriminator] <> N'Kiwi'"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyNoTrackingQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyNoTrackingQuerySqlServerTest.cs index abc01dfda2c..e1ee9b4e194 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyNoTrackingQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyNoTrackingQuerySqlServerTest.cs @@ -1759,6 +1759,49 @@ FROM [JoinOneToTwo] AS [j] WHERE [e].[Id] = [j].[OneId] AND [e0].[Id] = @__entity_equality_two_0_Id)"); } + public override async Task GetType_in_hierarchy_in_base_type(bool async) + { + await base.GetType_in_hierarchy_in_base_type(async); + + AssertSql( + @"SELECT [r].[Id], [r].[Name], NULL AS [Number], NULL AS [IsGreen], N'EntityRoot' AS [Discriminator] +FROM [Roots] AS [r]"); + } + + public override async Task GetType_in_hierarchy_in_intermediate_type(bool async) + { + await base.GetType_in_hierarchy_in_intermediate_type(async); + + AssertSql( + @"SELECT [b].[Id], [b].[Name], [b].[Number], NULL AS [IsGreen], N'EntityBranch' AS [Discriminator] +FROM [Branches] AS [b]"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Name], [l].[Number], [l].[IsGreen], N'EntityLeaf' AS [Discriminator] +FROM [Leaves] AS [l]"); + } + + public override async Task GetType_in_hierarchy_in_querying_base_type(bool async) + { + await base.GetType_in_hierarchy_in_querying_base_type(async); + + AssertSql( + @"SELECT [t].[Id], [t].[Name], [t].[Number], [t].[IsGreen], [t].[Discriminator] +FROM ( + SELECT [b].[Id], [b].[Name], [b].[Number], NULL AS [IsGreen], N'EntityBranch' AS [Discriminator] + FROM [Branches] AS [b] + UNION ALL + SELECT [l].[Id], [l].[Name], [l].[Number], [l].[IsGreen], N'EntityLeaf' AS [Discriminator] + FROM [Leaves] AS [l] +) AS [t] +WHERE 0 = 1"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyQuerySqlServerTest.cs index de9dbe7e76c..20993104767 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyQuerySqlServerTest.cs @@ -1765,6 +1765,49 @@ FROM [JoinOneToTwo] AS [j] WHERE [e].[Id] = [j].[OneId] AND [e0].[Id] = @__entity_equality_two_0_Id)"); } + public override async Task GetType_in_hierarchy_in_base_type(bool async) + { + await base.GetType_in_hierarchy_in_base_type(async); + + AssertSql( + @"SELECT [r].[Id], [r].[Name], NULL AS [Number], NULL AS [IsGreen], N'EntityRoot' AS [Discriminator] +FROM [Roots] AS [r]"); + } + + public override async Task GetType_in_hierarchy_in_intermediate_type(bool async) + { + await base.GetType_in_hierarchy_in_intermediate_type(async); + + AssertSql( + @"SELECT [b].[Id], [b].[Name], [b].[Number], NULL AS [IsGreen], N'EntityBranch' AS [Discriminator] +FROM [Branches] AS [b]"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Name], [l].[Number], [l].[IsGreen], N'EntityLeaf' AS [Discriminator] +FROM [Leaves] AS [l]"); + } + + public override async Task GetType_in_hierarchy_in_querying_base_type(bool async) + { + await base.GetType_in_hierarchy_in_querying_base_type(async); + + AssertSql( + @"SELECT [t].[Id], [t].[Name], [t].[Number], [t].[IsGreen], [t].[Discriminator] +FROM ( + SELECT [b].[Id], [b].[Name], [b].[Number], NULL AS [IsGreen], N'EntityBranch' AS [Discriminator] + FROM [Branches] AS [b] + UNION ALL + SELECT [l].[Id], [l].[Name], [l].[Number], [l].[IsGreen], N'EntityLeaf' AS [Discriminator] + FROM [Leaves] AS [l] +) AS [t] +WHERE 0 = 1"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTInheritanceQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTInheritanceQuerySqlServerTest.cs index bce0e2563f2..9af129e64c5 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTInheritanceQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTInheritanceQuerySqlServerTest.cs @@ -660,6 +660,7 @@ public override void Using_from_sql_throws() AssertSql(); } + public override async Task Using_is_operator_on_multiple_type_with_no_result(bool async) { await base.Using_is_operator_on_multiple_type_with_no_result(async); @@ -698,6 +699,102 @@ public override async Task Using_OfType_on_multiple_type_with_no_result(bool asy AssertSql(); } + public override async Task GetType_in_hierarchy_in_abstract_base_type(bool async) + { + await base.GetType_in_hierarchy_in_abstract_base_type(async); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Name], [b].[EagleId], [b].[IsFlightless], [e].[Group], [k].[FoundOn], CASE + WHEN [k].[Species] IS NOT NULL THEN N'Kiwi' + WHEN [e].[Species] IS NOT NULL THEN N'Eagle' +END AS [Discriminator] +FROM [Animals] AS [a] +LEFT JOIN [Birds] AS [b] ON [a].[Species] = [b].[Species] +LEFT JOIN [Eagle] AS [e] ON [a].[Species] = [e].[Species] +LEFT JOIN [Kiwi] AS [k] ON [a].[Species] = [k].[Species] +WHERE 0 = 1"); + } + + public override async Task GetType_in_hierarchy_in_intermediate_type(bool async) + { + await base.GetType_in_hierarchy_in_intermediate_type(async); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Name], [b].[EagleId], [b].[IsFlightless], [e].[Group], [k].[FoundOn], CASE + WHEN [k].[Species] IS NOT NULL THEN N'Kiwi' + WHEN [e].[Species] IS NOT NULL THEN N'Eagle' +END AS [Discriminator] +FROM [Animals] AS [a] +LEFT JOIN [Birds] AS [b] ON [a].[Species] = [b].[Species] +LEFT JOIN [Eagle] AS [e] ON [a].[Species] = [e].[Species] +LEFT JOIN [Kiwi] AS [k] ON [a].[Species] = [k].[Species] +WHERE 0 = 1"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type_with_sibling(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type_with_sibling(async); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Name], [b].[EagleId], [b].[IsFlightless], [e].[Group], [k].[FoundOn], CASE + WHEN [k].[Species] IS NOT NULL THEN N'Kiwi' + WHEN [e].[Species] IS NOT NULL THEN N'Eagle' +END AS [Discriminator] +FROM [Animals] AS [a] +LEFT JOIN [Birds] AS [b] ON [a].[Species] = [b].[Species] +LEFT JOIN [Eagle] AS [e] ON [a].[Species] = [e].[Species] +LEFT JOIN [Kiwi] AS [k] ON [a].[Species] = [k].[Species] +WHERE [e].[Species] IS NOT NULL"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type_with_sibling2(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type_with_sibling2(async); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Name], [b].[EagleId], [b].[IsFlightless], [e].[Group], [k].[FoundOn], CASE + WHEN [k].[Species] IS NOT NULL THEN N'Kiwi' + WHEN [e].[Species] IS NOT NULL THEN N'Eagle' +END AS [Discriminator] +FROM [Animals] AS [a] +LEFT JOIN [Birds] AS [b] ON [a].[Species] = [b].[Species] +LEFT JOIN [Eagle] AS [e] ON [a].[Species] = [e].[Species] +LEFT JOIN [Kiwi] AS [k] ON [a].[Species] = [k].[Species] +WHERE [k].[Species] IS NOT NULL"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type_with_sibling2_reverse(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type_with_sibling2_reverse(async); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Name], [b].[EagleId], [b].[IsFlightless], [e].[Group], [k].[FoundOn], CASE + WHEN [k].[Species] IS NOT NULL THEN N'Kiwi' + WHEN [e].[Species] IS NOT NULL THEN N'Eagle' +END AS [Discriminator] +FROM [Animals] AS [a] +LEFT JOIN [Birds] AS [b] ON [a].[Species] = [b].[Species] +LEFT JOIN [Eagle] AS [e] ON [a].[Species] = [e].[Species] +LEFT JOIN [Kiwi] AS [k] ON [a].[Species] = [k].[Species] +WHERE [k].[Species] IS NOT NULL"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type_with_sibling2_not_equal(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type_with_sibling2_not_equal(async); + + AssertSql( + @"SELECT [a].[Species], [a].[CountryId], [a].[Name], [b].[EagleId], [b].[IsFlightless], [e].[Group], [k].[FoundOn], CASE + WHEN [k].[Species] IS NOT NULL THEN N'Kiwi' + WHEN [e].[Species] IS NOT NULL THEN N'Eagle' +END AS [Discriminator] +FROM [Animals] AS [a] +LEFT JOIN [Birds] AS [b] ON [a].[Species] = [b].[Species] +LEFT JOIN [Eagle] AS [e] ON [a].[Species] = [e].[Species] +LEFT JOIN [Kiwi] AS [k] ON [a].[Species] = [k].[Species] +WHERE [k].[Species] IS NULL"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyNoTrackingQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyNoTrackingQuerySqlServerTest.cs index 42c7da8c566..3204b77d70b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyNoTrackingQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyNoTrackingQuerySqlServerTest.cs @@ -1709,6 +1709,65 @@ FROM [JoinOneToTwo] AS [j] WHERE [e].[Id] = [j].[OneId] AND [e0].[Id] = @__entity_equality_two_0_Id)"); } + public override async Task GetType_in_hierarchy_in_base_type(bool async) + { + await base.GetType_in_hierarchy_in_base_type(async); + + AssertSql( + @"SELECT [r].[Id], [r].[Name], [b].[Number], [l].[IsGreen], CASE + WHEN [l].[Id] IS NOT NULL THEN N'EntityLeaf' + WHEN [b].[Id] IS NOT NULL THEN N'EntityBranch' +END AS [Discriminator] +FROM [Roots] AS [r] +LEFT JOIN [Branches] AS [b] ON [r].[Id] = [b].[Id] +LEFT JOIN [Leaves] AS [l] ON [r].[Id] = [l].[Id] +WHERE [l].[Id] IS NULL AND [b].[Id] IS NULL"); + } + + public override async Task GetType_in_hierarchy_in_intermediate_type(bool async) + { + await base.GetType_in_hierarchy_in_intermediate_type(async); + + AssertSql( + @"SELECT [r].[Id], [r].[Name], [b].[Number], [l].[IsGreen], CASE + WHEN [l].[Id] IS NOT NULL THEN N'EntityLeaf' + WHEN [b].[Id] IS NOT NULL THEN N'EntityBranch' +END AS [Discriminator] +FROM [Roots] AS [r] +LEFT JOIN [Branches] AS [b] ON [r].[Id] = [b].[Id] +LEFT JOIN [Leaves] AS [l] ON [r].[Id] = [l].[Id] +WHERE [l].[Id] IS NULL AND [b].[Id] IS NOT NULL"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type(async); + + AssertSql( + @"SELECT [r].[Id], [r].[Name], [b].[Number], [l].[IsGreen], CASE + WHEN [l].[Id] IS NOT NULL THEN N'EntityLeaf' + WHEN [b].[Id] IS NOT NULL THEN N'EntityBranch' +END AS [Discriminator] +FROM [Roots] AS [r] +LEFT JOIN [Branches] AS [b] ON [r].[Id] = [b].[Id] +LEFT JOIN [Leaves] AS [l] ON [r].[Id] = [l].[Id] +WHERE [l].[Id] IS NOT NULL"); + } + + public override async Task GetType_in_hierarchy_in_querying_base_type(bool async) + { + await base.GetType_in_hierarchy_in_querying_base_type(async); + + AssertSql( + @"SELECT [r].[Id], [r].[Name], [b].[Number], [l].[IsGreen], CASE + WHEN [l].[Id] IS NOT NULL THEN N'EntityLeaf' +END AS [Discriminator] +FROM [Roots] AS [r] +INNER JOIN [Branches] AS [b] ON [r].[Id] = [b].[Id] +LEFT JOIN [Leaves] AS [l] ON [r].[Id] = [l].[Id] +WHERE 0 = 1"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyQuerySqlServerTest.cs index edfddbe4b3a..a1acbdcb09d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyQuerySqlServerTest.cs @@ -1713,6 +1713,64 @@ FROM [JoinOneToTwo] AS [j] INNER JOIN [EntityTwos] AS [e0] ON [j].[TwoId] = [e0].[Id] WHERE [e].[Id] = [j].[OneId] AND [e0].[Id] = @__entity_equality_two_0_Id)"); } + public override async Task GetType_in_hierarchy_in_base_type(bool async) + { + await base.GetType_in_hierarchy_in_base_type(async); + + AssertSql( + @"SELECT [r].[Id], [r].[Name], [b].[Number], [l].[IsGreen], CASE + WHEN [l].[Id] IS NOT NULL THEN N'EntityLeaf' + WHEN [b].[Id] IS NOT NULL THEN N'EntityBranch' +END AS [Discriminator] +FROM [Roots] AS [r] +LEFT JOIN [Branches] AS [b] ON [r].[Id] = [b].[Id] +LEFT JOIN [Leaves] AS [l] ON [r].[Id] = [l].[Id] +WHERE [l].[Id] IS NULL AND [b].[Id] IS NULL"); + } + + public override async Task GetType_in_hierarchy_in_intermediate_type(bool async) + { + await base.GetType_in_hierarchy_in_intermediate_type(async); + + AssertSql( + @"SELECT [r].[Id], [r].[Name], [b].[Number], [l].[IsGreen], CASE + WHEN [l].[Id] IS NOT NULL THEN N'EntityLeaf' + WHEN [b].[Id] IS NOT NULL THEN N'EntityBranch' +END AS [Discriminator] +FROM [Roots] AS [r] +LEFT JOIN [Branches] AS [b] ON [r].[Id] = [b].[Id] +LEFT JOIN [Leaves] AS [l] ON [r].[Id] = [l].[Id] +WHERE [l].[Id] IS NULL AND [b].[Id] IS NOT NULL"); + } + + public override async Task GetType_in_hierarchy_in_leaf_type(bool async) + { + await base.GetType_in_hierarchy_in_leaf_type(async); + + AssertSql( + @"SELECT [r].[Id], [r].[Name], [b].[Number], [l].[IsGreen], CASE + WHEN [l].[Id] IS NOT NULL THEN N'EntityLeaf' + WHEN [b].[Id] IS NOT NULL THEN N'EntityBranch' +END AS [Discriminator] +FROM [Roots] AS [r] +LEFT JOIN [Branches] AS [b] ON [r].[Id] = [b].[Id] +LEFT JOIN [Leaves] AS [l] ON [r].[Id] = [l].[Id] +WHERE [l].[Id] IS NOT NULL"); + } + + public override async Task GetType_in_hierarchy_in_querying_base_type(bool async) + { + await base.GetType_in_hierarchy_in_querying_base_type(async); + + AssertSql( + @"SELECT [r].[Id], [r].[Name], [b].[Number], [l].[IsGreen], CASE + WHEN [l].[Id] IS NOT NULL THEN N'EntityLeaf' +END AS [Discriminator] +FROM [Roots] AS [r] +INNER JOIN [Branches] AS [b] ON [r].[Id] = [b].[Id] +LEFT JOIN [Leaves] AS [l] ON [r].[Id] = [l].[Id] +WHERE 0 = 1"); + } private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);