From b4a4d0d160f3073242f794bd003c363d5474017a Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Thu, 9 Jun 2022 15:50:33 -0700 Subject: [PATCH] Query: Improve logic to find column names in TPC Resolves #28196 --- .../Query/SqlExpressions/SelectExpression.cs | 15 ++-- .../Query/SimpleQueryRelationalTestBase.cs | 30 ++++++++ .../Query/SimpleQueryTestBase.cs | 76 +++++++++++++++++++ .../Query/SimpleQuerySqlServerTest.cs | 42 ++++++++++ 4 files changed, 155 insertions(+), 8 deletions(-) diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index 7405cbae7ba..01f28507f40 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -224,18 +224,17 @@ internal SelectExpression(IEntityType entityType, ISqlExpressionFactory sqlExpre var tables = entityTypes.Select(e => GetTableBase(e)).ToArray(); var properties = GetAllPropertiesInHierarchy(entityType).ToArray(); var propertyNamesMap = new Dictionary(); - foreach (var property in entityTypes[0].GetProperties()) - { - propertyNamesMap[property] = tables[0].FindColumn(property)!.Name; - } - for (var i = 1; i < entityTypes.Length; i++) + for (var i = 0; i < entityTypes.Length; i++) { - foreach (var property in entityTypes[i].GetDeclaredProperties()) + foreach (var property in entityTypes[i].GetProperties()) { - Check.DebugAssert(!propertyNamesMap.ContainsKey(property), "Duplicate property found."); - propertyNamesMap[property] = tables[i].FindColumn(property)!.Name; + if (!propertyNamesMap.ContainsKey(property)) + { + propertyNamesMap[property] = tables[i].FindColumn(property)!.Name; + } } } + var propertyNames = new string[properties.Length]; for (var i = 0; i < properties.Length; i++) { diff --git a/test/EFCore.Relational.Specification.Tests/Query/SimpleQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/SimpleQueryRelationalTestBase.cs index b6777516a13..5331a46ad70 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/SimpleQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/SimpleQueryRelationalTestBase.cs @@ -101,6 +101,36 @@ protected class MyEntity public DateTime SomeDate { get; set; } public static DateTime Modify(DateTime date) => throw new NotSupportedException(); } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Hierarchy_query_with_abstract_type_sibling_TPC(bool async) + { + return Hierarchy_query_with_abstract_type_sibling_helper(async, + mb => + { + mb.Entity().UseTpcMappingStrategy(); + mb.Entity().ToTable("Pets"); + mb.Entity().ToTable("Cats"); + mb.Entity().ToTable("Dogs"); + mb.Entity().ToTable("FarmAnimals"); + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Hierarchy_query_with_abstract_type_sibling_TPT(bool async) + { + return Hierarchy_query_with_abstract_type_sibling_helper(async, + mb => + { + mb.Entity().UseTptMappingStrategy(); + mb.Entity().ToTable("Pets"); + mb.Entity().ToTable("Cats"); + mb.Entity().ToTable("Dogs"); + mb.Entity().ToTable("FarmAnimals"); + }); + } } } diff --git a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs index e7402233b11..ccff8503161 100644 --- a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs @@ -1256,4 +1256,80 @@ protected class SearchResult public int RowId { get; set; } public string DistinctValue { get; set; } } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Hierarchy_query_with_abstract_type_sibling(bool async) + { + return Hierarchy_query_with_abstract_type_sibling_helper(async, null); + } + + public virtual async Task Hierarchy_query_with_abstract_type_sibling_helper(bool async, Action onModelCreating) + { + var contextFactory = await InitializeAsync(onModelCreating: onModelCreating, seed: c => c.Seed()); + using var context = contextFactory.CreateContext(); + + var query = context.Animals.OfType().Where(a => a.Species.StartsWith("F")); + + var result = async + ? await query.ToListAsync() + : query.ToList(); + } + + protected class Context28196 : DbContext + { + public Context28196(DbContextOptions options) + : base(options) + { + } + + public DbSet Animals { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever(); + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(); + } + + public void Seed() + { + AddRange( + new Cat { Id = 1, Name = "Alice", Species = "Felis catus", EdcuationLevel = "MBA" }, + new Cat { Id = 2, Name = "Mac", Species = "Felis catus", EdcuationLevel = "BA" }, + new Dog { Id = 3, Name = "Toast", Species = "Canis familiaris", FavoriteToy = "Mr. Squirrel" }, + new FarmAnimal { Id = 4, Value = 100.0, Species = "Ovis aries" }); + + SaveChanges(); + } + } + + protected abstract class Animal + { + public int Id { get; set; } + public string Species { get; set; } + } + + protected class FarmAnimal : Animal + { + public double Value { get; set; } + } + + protected abstract class Pet : Animal + { + public string Name { get; set; } + } + + protected class Cat : Pet + { + public string EdcuationLevel { get; set; } + } + + protected class Dog : Pet + { + public string FavoriteToy { get; set; } + } + } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs index 316b08e54bb..e1f7ac80e55 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs @@ -429,4 +429,46 @@ HAVING COUNT(*) = 1 WHERE [t].[TableId] = 123 ORDER BY [t].[ParcelNumber]"); } + + public override async Task Hierarchy_query_with_abstract_type_sibling(bool async) + { + await base.Hierarchy_query_with_abstract_type_sibling(async); + + AssertSql( + @"SELECT [a].[Id], [a].[Discriminator], [a].[Species], [a].[Name], [a].[EdcuationLevel], [a].[FavoriteToy] +FROM [Animals] AS [a] +WHERE [a].[Discriminator] IN (N'Cat', N'Dog') AND [a].[Species] IS NOT NULL AND ([a].[Species] LIKE N'F%')"); + } + + public override async Task Hierarchy_query_with_abstract_type_sibling_TPT(bool async) + { + await base.Hierarchy_query_with_abstract_type_sibling_TPT(async); + + AssertSql( + @"SELECT [a].[Id], [a].[Species], [p].[Name], [c].[EdcuationLevel], [d].[FavoriteToy], CASE + WHEN [d].[Id] IS NOT NULL THEN N'Dog' + WHEN [c].[Id] IS NOT NULL THEN N'Cat' +END AS [Discriminator] +FROM [Animals] AS [a] +LEFT JOIN [Pets] AS [p] ON [a].[Id] = [p].[Id] +LEFT JOIN [Cats] AS [c] ON [a].[Id] = [c].[Id] +LEFT JOIN [Dogs] AS [d] ON [a].[Id] = [d].[Id] +WHERE ([d].[Id] IS NOT NULL OR [c].[Id] IS NOT NULL) AND [a].[Species] IS NOT NULL AND ([a].[Species] LIKE N'F%')"); + } + + public override async Task Hierarchy_query_with_abstract_type_sibling_TPC(bool async) + { + await base.Hierarchy_query_with_abstract_type_sibling_TPC(async); + + AssertSql( + @"SELECT [t].[Id], [t].[Species], [t].[Name], [t].[EdcuationLevel], [t].[FavoriteToy], [t].[Discriminator] +FROM ( + SELECT [c].[Id], [c].[Species], [c].[Name], [c].[EdcuationLevel], NULL AS [FavoriteToy], N'Cat' AS [Discriminator] + FROM [Cats] AS [c] + UNION ALL + SELECT [d].[Id], [d].[Species], [d].[Name], NULL AS [EdcuationLevel], [d].[FavoriteToy], N'Dog' AS [Discriminator] + FROM [Dogs] AS [d] +) AS [t] +WHERE [t].[Species] IS NOT NULL AND ([t].[Species] LIKE N'F%')"); + } }