diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index cca2aac6c00..9f1d919bdc7 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -1019,12 +1019,6 @@ public static string TableValuedFunctionNonTPH([CanBeNull] object dbFunction, [C GetString("TableValuedFunctionNonTPH", nameof(dbFunction), nameof(entityType)), dbFunction, entityType); - /// - /// Using 'FromSqlRaw' or 'FromSqlInterpolated' on an entity type which has owned reference navigations sharing same table is not supported. - /// - public static string CustomQueryMappingOnOwner - => GetString("CustomQueryMappingOnOwner"); - /// /// Nullability information should only be specified for scalar database functions. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index dbd3530916e..5b99204c4ed 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -726,9 +726,6 @@ The element type of result of '{dbFunction}' is mapped to '{entityType}'. This is not supported since '{entityType}' is part of hierarchy and does not contain a discriminator property. - - Using 'FromSqlRaw' or 'FromSqlInterpolated' on an entity type which has owned reference navigations sharing same table is not supported. - Nullability information should only be specified for scalar database functions. diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 833e1957f55..03540e38cd4 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -1334,11 +1334,15 @@ private Expression TryExpand(Expression source, MemberIdentity member) var propertyExpressions = GetPropertyExpressionFromSameTable( targetEntityType, table, _selectExpression, identifyingColumn, principalNullable); - - innerShaper = new RelationalEntityShaperExpression( - targetEntityType, new EntityProjectionExpression(targetEntityType, propertyExpressions), true); + if (propertyExpressions != null) + { + innerShaper = new RelationalEntityShaperExpression( + targetEntityType, new EntityProjectionExpression(targetEntityType, propertyExpressions), true); + } } - else + + // InnerShaper is still null if either it is not table sharing or we failed to find table to pick data from + if (innerShaper == null) { var innerSelectExpression = _sqlExpressionFactory.Select(targetEntityType); var innerShapedQuery = CreateShapedQueryExpression(targetEntityType, innerSelectExpression); @@ -1408,6 +1412,11 @@ private static IDictionary GetPropertyExpressionFro var subqueryPropertyExpressions = GetPropertyExpressionFromSameTable( entityType, table, subquery, subqueryIdentifyingColumn, nullable); + if (subqueryPropertyExpressions == null) + { + return null; + } + var newPropertyExpressions = new Dictionary(); foreach (var item in subqueryPropertyExpressions) { @@ -1418,7 +1427,7 @@ private static IDictionary GetPropertyExpressionFro return newPropertyExpressions; } - throw new InvalidOperationException(RelationalStrings.CustomQueryMappingOnOwner); + return null; } private static IDictionary GetPropertyExpressionsFromJoinedTable( diff --git a/test/EFCore.Relational.Specification.Tests/Query/OwnedQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/OwnedQueryRelationalTestBase.cs index 03a9a60317a..06fa3932438 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/OwnedQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/OwnedQueryRelationalTestBase.cs @@ -115,15 +115,14 @@ public virtual Task Can_query_on_indexer_properties_split(bool async) [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual async Task Throws_when_using_from_sql_on_owner(bool async) + public virtual async Task Using_from_sql_on_owner_generates_join_with_table_for_owned_shared_dependents(bool async) { using var context = CreateContext(); - var query = context.Set().FromSqlRaw(NormalizeDelimitersInRawString("SELECT * FROM [OwnedPersons]")); + var query = context.Set().FromSqlRaw(NormalizeDelimitersInRawString("SELECT * FROM [OwnedPerson]")); var message = async - ? (await Assert.ThrowsAsync(() => query.ToListAsync())).Message - : Assert.Throws(() => query.ToList()).Message; - Assert.Equal(RelationalStrings.CustomQueryMappingOnOwner, message); + ? await query.ToListAsync() + : query.ToList(); } protected string NormalizeDelimitersInRawString(string sql) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs index 3bbb48d7d5a..86a93136389 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs @@ -933,6 +933,129 @@ FROM [OwnedPerson] AS [o] LEFT JOIN [Star] AS [s] ON [p].[StarId] = [s].[Id]"); } + public override async Task Using_from_sql_on_owner_generates_join_with_table_for_owned_shared_dependents(bool async) + { + await base.Using_from_sql_on_owner_generates_join_with_table_for_owned_shared_dependents(async); + + AssertSql( + @"SELECT [o].[Id], [o].[Discriminator], [o].[Name], [t].[Id], [t].[PersonAddress_AddressLine], [t].[PersonAddress_PlaceType], [t].[PersonAddress_ZipCode], [t].[Id1], [t].[PersonAddress_Country_Name], [t].[PersonAddress_Country_PlanetId], [t3].[Id], [t3].[BranchAddress_BranchName], [t3].[BranchAddress_PlaceType], [t8].[Id], [t8].[BranchAddress_Country_Name], [t8].[BranchAddress_Country_PlanetId], [t12].[Id], [t12].[LeafBAddress_LeafBType], [t12].[LeafBAddress_PlaceType], [t17].[Id], [t17].[LeafBAddress_Country_Name], [t17].[LeafBAddress_Country_PlanetId], [t19].[Id], [t19].[LeafAAddress_LeafType], [t19].[LeafAAddress_PlaceType], [t19].[Id1], [t19].[LeafAAddress_Country_Name], [t19].[LeafAAddress_Country_PlanetId], [t].[Id0], [t3].[Id0], [t8].[Id0], [t8].[Id00], [t12].[Id0], [t17].[Id0], [t17].[Id00], [t19].[Id0], [o22].[ClientId], [o22].[Id], [o22].[OrderDate] +FROM ( + SELECT * FROM ""OwnedPerson"" +) AS [o] +LEFT JOIN ( + SELECT [o0].[Id], [o0].[PersonAddress_AddressLine], [o0].[PersonAddress_PlaceType], [o0].[PersonAddress_ZipCode], [o1].[Id] AS [Id0], [o0].[Id] AS [Id1], [o0].[PersonAddress_Country_Name], [o0].[PersonAddress_Country_PlanetId] + FROM [OwnedPerson] AS [o0] + INNER JOIN [OwnedPerson] AS [o1] ON [o0].[Id] = [o1].[Id] + WHERE [o0].[PersonAddress_ZipCode] IS NOT NULL +) AS [t] ON [o].[Id] = [t].[Id] +LEFT JOIN ( + SELECT [t1].[Id], [t1].[BranchAddress_BranchName], [t1].[BranchAddress_PlaceType], [t2].[Id] AS [Id0] + FROM ( + SELECT [o2].[Id], [o2].[BranchAddress_BranchName], [o2].[BranchAddress_PlaceType] + FROM [OwnedPerson] AS [o2] + WHERE [o2].[BranchAddress_PlaceType] IS NOT NULL OR [o2].[BranchAddress_BranchName] IS NOT NULL + UNION + SELECT [o3].[Id], [o3].[BranchAddress_BranchName], [o3].[BranchAddress_PlaceType] + FROM [OwnedPerson] AS [o3] + INNER JOIN ( + SELECT [o4].[Id], [o4].[BranchAddress_Country_Name], [o4].[BranchAddress_Country_PlanetId] + FROM [OwnedPerson] AS [o4] + WHERE [o4].[BranchAddress_Country_PlanetId] IS NOT NULL + ) AS [t0] ON [o3].[Id] = [t0].[Id] + ) AS [t1] + INNER JOIN ( + SELECT [o5].[Id], [o5].[Discriminator], [o5].[Name] + FROM [OwnedPerson] AS [o5] + WHERE [o5].[Discriminator] IN (N'Branch', N'LeafA') + ) AS [t2] ON [t1].[Id] = [t2].[Id] +) AS [t3] ON [o].[Id] = [t3].[Id] +LEFT JOIN ( + SELECT [o6].[Id], [o6].[BranchAddress_Country_Name], [o6].[BranchAddress_Country_PlanetId], [t7].[Id] AS [Id0], [t7].[Id0] AS [Id00] + FROM [OwnedPerson] AS [o6] + INNER JOIN ( + SELECT [t5].[Id], [t5].[BranchAddress_BranchName], [t5].[BranchAddress_PlaceType], [t6].[Id] AS [Id0] + FROM ( + SELECT [o7].[Id], [o7].[BranchAddress_BranchName], [o7].[BranchAddress_PlaceType] + FROM [OwnedPerson] AS [o7] + WHERE [o7].[BranchAddress_PlaceType] IS NOT NULL OR [o7].[BranchAddress_BranchName] IS NOT NULL + UNION + SELECT [o8].[Id], [o8].[BranchAddress_BranchName], [o8].[BranchAddress_PlaceType] + FROM [OwnedPerson] AS [o8] + INNER JOIN ( + SELECT [o9].[Id], [o9].[BranchAddress_Country_Name], [o9].[BranchAddress_Country_PlanetId] + FROM [OwnedPerson] AS [o9] + WHERE [o9].[BranchAddress_Country_PlanetId] IS NOT NULL + ) AS [t4] ON [o8].[Id] = [t4].[Id] + ) AS [t5] + INNER JOIN ( + SELECT [o10].[Id], [o10].[Discriminator], [o10].[Name] + FROM [OwnedPerson] AS [o10] + WHERE [o10].[Discriminator] IN (N'Branch', N'LeafA') + ) AS [t6] ON [t5].[Id] = [t6].[Id] + ) AS [t7] ON [o6].[Id] = [t7].[Id] + WHERE [o6].[BranchAddress_Country_PlanetId] IS NOT NULL +) AS [t8] ON [t3].[Id] = [t8].[Id] +LEFT JOIN ( + SELECT [t10].[Id], [t10].[LeafBAddress_LeafBType], [t10].[LeafBAddress_PlaceType], [t11].[Id] AS [Id0] + FROM ( + SELECT [o11].[Id], [o11].[LeafBAddress_LeafBType], [o11].[LeafBAddress_PlaceType] + FROM [OwnedPerson] AS [o11] + WHERE [o11].[LeafBAddress_PlaceType] IS NOT NULL OR [o11].[LeafBAddress_LeafBType] IS NOT NULL + UNION + SELECT [o12].[Id], [o12].[LeafBAddress_LeafBType], [o12].[LeafBAddress_PlaceType] + FROM [OwnedPerson] AS [o12] + INNER JOIN ( + SELECT [o13].[Id], [o13].[LeafBAddress_Country_Name], [o13].[LeafBAddress_Country_PlanetId] + FROM [OwnedPerson] AS [o13] + WHERE [o13].[LeafBAddress_Country_PlanetId] IS NOT NULL + ) AS [t9] ON [o12].[Id] = [t9].[Id] + ) AS [t10] + INNER JOIN ( + SELECT [o14].[Id], [o14].[Discriminator], [o14].[Name] + FROM [OwnedPerson] AS [o14] + WHERE [o14].[Discriminator] = N'LeafB' + ) AS [t11] ON [t10].[Id] = [t11].[Id] +) AS [t12] ON [o].[Id] = [t12].[Id] +LEFT JOIN ( + SELECT [o15].[Id], [o15].[LeafBAddress_Country_Name], [o15].[LeafBAddress_Country_PlanetId], [t16].[Id] AS [Id0], [t16].[Id0] AS [Id00] + FROM [OwnedPerson] AS [o15] + INNER JOIN ( + SELECT [t14].[Id], [t14].[LeafBAddress_LeafBType], [t14].[LeafBAddress_PlaceType], [t15].[Id] AS [Id0] + FROM ( + SELECT [o16].[Id], [o16].[LeafBAddress_LeafBType], [o16].[LeafBAddress_PlaceType] + FROM [OwnedPerson] AS [o16] + WHERE [o16].[LeafBAddress_PlaceType] IS NOT NULL OR [o16].[LeafBAddress_LeafBType] IS NOT NULL + UNION + SELECT [o17].[Id], [o17].[LeafBAddress_LeafBType], [o17].[LeafBAddress_PlaceType] + FROM [OwnedPerson] AS [o17] + INNER JOIN ( + SELECT [o18].[Id], [o18].[LeafBAddress_Country_Name], [o18].[LeafBAddress_Country_PlanetId] + FROM [OwnedPerson] AS [o18] + WHERE [o18].[LeafBAddress_Country_PlanetId] IS NOT NULL + ) AS [t13] ON [o17].[Id] = [t13].[Id] + ) AS [t14] + INNER JOIN ( + SELECT [o19].[Id], [o19].[Discriminator], [o19].[Name] + FROM [OwnedPerson] AS [o19] + WHERE [o19].[Discriminator] = N'LeafB' + ) AS [t15] ON [t14].[Id] = [t15].[Id] + ) AS [t16] ON [o15].[Id] = [t16].[Id] + WHERE [o15].[LeafBAddress_Country_PlanetId] IS NOT NULL +) AS [t17] ON [t12].[Id] = [t17].[Id] +LEFT JOIN ( + SELECT [o20].[Id], [o20].[LeafAAddress_LeafType], [o20].[LeafAAddress_PlaceType], [t18].[Id] AS [Id0], [o20].[Id] AS [Id1], [o20].[LeafAAddress_Country_Name], [o20].[LeafAAddress_Country_PlanetId] + FROM [OwnedPerson] AS [o20] + INNER JOIN ( + SELECT [o21].[Id], [o21].[Discriminator], [o21].[Name] + FROM [OwnedPerson] AS [o21] + WHERE [o21].[Discriminator] = N'LeafA' + ) AS [t18] ON [o20].[Id] = [t18].[Id] + WHERE [o20].[LeafAAddress_LeafType] IS NOT NULL +) AS [t19] ON [o].[Id] = [t19].[Id] +LEFT JOIN [Order] AS [o22] ON [o].[Id] = [o22].[ClientId] +ORDER BY [o].[Id], [t].[Id], [t].[Id0], [t3].[Id], [t3].[Id0], [t8].[Id], [t8].[Id0], [t8].[Id00], [t12].[Id], [t12].[Id0], [t17].[Id], [t17].[Id0], [t17].[Id00], [t19].[Id], [t19].[Id0], [o22].[ClientId], [o22].[Id]"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);