Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 32886fb

Browse files
authored
Add complex type mappings to the default relational mappings (#32867)
* Add complex type mappings to the default relational mappings Replaces #32816 Fixes #32699 As discussed, leaving FromSql to use the default mappings, but add complex types to the default mappings. * Add note to SelectExpression
1 parent 7003680 commit 32886fb

File tree

4 files changed

+363
-49
lines changed

4 files changed

+363
-49
lines changed

src/EFCore.Relational/Metadata/Internal/RelationalModel.cs

Lines changed: 79 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System.Data;
4+
using System.Text;
55
using System.Text.Json;
66

77
namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
@@ -323,32 +323,7 @@ private static void AddDefaultMappings(
323323
}
324324
else
325325
{
326-
foreach (var property in entityType.GetProperties())
327-
{
328-
var columnName = property.IsPrimaryKey() || isTpc || isTph || property.DeclaringType == mappedType
329-
? property.GetColumnName()
330-
: null;
331-
if (columnName == null)
332-
{
333-
continue;
334-
}
335-
336-
var column = (ColumnBase<ColumnMappingBase>?)defaultTable.FindColumn(columnName);
337-
if (column == null)
338-
{
339-
column = new ColumnBase<ColumnMappingBase>(columnName, property.GetColumnType(), defaultTable)
340-
{
341-
IsNullable = property.IsColumnNullable()
342-
};
343-
defaultTable.Columns.Add(columnName, column);
344-
}
345-
else if (!property.IsColumnNullable())
346-
{
347-
column.IsNullable = false;
348-
}
349-
350-
CreateColumnMapping(column, property, tableMapping);
351-
}
326+
CreateDefaultColumnMapping(entityType, mappedType, defaultTable, tableMapping, isTph, isTpc);
352327
}
353328

354329
if (((ITableMappingBase)tableMapping).ColumnMappings.Any()
@@ -369,6 +344,83 @@ private static void AddDefaultMappings(
369344
tableMappings.Reverse();
370345
}
371346

347+
private static void CreateDefaultColumnMapping(
348+
ITypeBase typeBase,
349+
ITypeBase mappedType,
350+
TableBase defaultTable,
351+
TableMappingBase<ColumnMappingBase> tableMapping,
352+
bool isTph,
353+
bool isTpc)
354+
{
355+
foreach (var property in typeBase.GetProperties())
356+
{
357+
var columnName = property.IsPrimaryKey() || isTpc || isTph || property.DeclaringType == mappedType
358+
? GetColumnName(property)
359+
: null;
360+
361+
if (columnName == null)
362+
{
363+
continue;
364+
}
365+
366+
var column = (ColumnBase<ColumnMappingBase>?)defaultTable.FindColumn(columnName);
367+
if (column == null)
368+
{
369+
column = new ColumnBase<ColumnMappingBase>(columnName, property.GetColumnType(), defaultTable)
370+
{
371+
IsNullable = property.IsColumnNullable()
372+
};
373+
defaultTable.Columns.Add(columnName, column);
374+
}
375+
else if (!property.IsColumnNullable())
376+
{
377+
column.IsNullable = false;
378+
}
379+
380+
CreateColumnMapping(column, property, tableMapping);
381+
}
382+
383+
foreach (var complexProperty in typeBase.GetDeclaredComplexProperties())
384+
{
385+
var complexType = complexProperty.ComplexType;
386+
tableMapping = new TableMappingBase<ColumnMappingBase>(complexType, defaultTable, includesDerivedTypes: null);
387+
388+
CreateDefaultColumnMapping(complexType, complexType, defaultTable, tableMapping, isTph, isTpc);
389+
390+
var tableMappings = (List<TableMappingBase<ColumnMappingBase>>?)complexType
391+
.FindRuntimeAnnotationValue(RelationalAnnotationNames.DefaultMappings);
392+
if (tableMappings == null)
393+
{
394+
tableMappings = new List<TableMappingBase<ColumnMappingBase>>();
395+
complexType.AddRuntimeAnnotation(RelationalAnnotationNames.DefaultMappings, tableMappings);
396+
}
397+
tableMappings.Add(tableMapping);
398+
399+
defaultTable.ComplexTypeMappings.Add(tableMapping);
400+
}
401+
402+
static string GetColumnName(IProperty property)
403+
{
404+
var complexType = property.DeclaringType as IComplexType;
405+
if (complexType != null)
406+
{
407+
var builder = new StringBuilder();
408+
builder.Append(property.Name);
409+
while (complexType != null)
410+
{
411+
builder.Insert(0, "_");
412+
builder.Insert(0, complexType.ComplexProperty.Name);
413+
414+
complexType = complexType.ComplexProperty.DeclaringType as IComplexType;
415+
}
416+
417+
return builder.ToString();
418+
}
419+
420+
return property.GetColumnName();
421+
}
422+
}
423+
372424
private static void AddTables(
373425
RelationalModel databaseModel,
374426
IEntityType entityType,

src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2563,9 +2563,13 @@ public static StructuralTypeShaperExpression GenerateComplexPropertyShaperExpres
25632563
var propertyExpressionMap = new Dictionary<IProperty, ColumnExpression>();
25642564

25652565
// We do not support complex type splitting, so we will only ever have a single table/view mapping to it.
2566+
// See Issue #32853 and Issue #31248
25662567
var complexTypeTable = complexProperty.ComplexType.GetViewOrTableMappings().Single().Table;
2567-
var tableReferenceExpression = containerProjection.TableMap[complexTypeTable];
2568-
2568+
if (!containerProjection.TableMap.TryGetValue(complexTypeTable, out var tableReferenceExpression))
2569+
{
2570+
complexTypeTable = complexProperty.ComplexType.GetDefaultMappings().Single().Table;
2571+
tableReferenceExpression = containerProjection.TableMap[complexTypeTable];
2572+
}
25692573
var isComplexTypeNullable = containerProjection.IsNullable || complexProperty.IsNullable;
25702574

25712575
// If the complex property is declared on a type that's derived relative to the type being projected, the projected column is

test/EFCore.SqlServer.FunctionalTests/Query/ComplexTypeQuerySqlServerTest.cs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using Microsoft.EntityFrameworkCore.TestModels.ComplexTypeModel;
5+
46
namespace Microsoft.EntityFrameworkCore.Query;
57

68
public class ComplexTypeQuerySqlServerTest : ComplexTypeQueryRelationalTestBase<
@@ -742,6 +744,121 @@ public override async Task Union_two_different_struct_complex_type(bool async)
742744
AssertSql();
743745
}
744746

747+
[ConditionalTheory]
748+
[MemberData(nameof(IsAsyncData))]
749+
public virtual Task Filter_on_property_inside_complex_type_with_FromSql(bool async)
750+
=> AssertQuery(
751+
async,
752+
ss => ((DbSet<Customer>)ss.Set<Customer>()).FromSqlRaw(
753+
"""
754+
SELECT [c].[Id], [c].[Name], [c].[BillingAddress_AddressLine1], [c].[BillingAddress_AddressLine2], [c].[BillingAddress_Tags], [c].[BillingAddress_ZipCode], [c].[BillingAddress_Country_Code], [c].[BillingAddress_Country_FullName], [c].[ShippingAddress_AddressLine1], [c].[ShippingAddress_AddressLine2], [c].[ShippingAddress_Tags], [c].[ShippingAddress_ZipCode], [c].[ShippingAddress_Country_Code], [c].[ShippingAddress_Country_FullName]
755+
FROM [Customer] AS [c]
756+
WHERE [c].[ShippingAddress_ZipCode] = 7728
757+
"""),
758+
ss => ss.Set<Customer>().Where(c => c.ShippingAddress.ZipCode == 07728));
759+
760+
[ConditionalTheory]
761+
[MemberData(nameof(IsAsyncData))]
762+
public virtual Task Filter_on_property_inside_complex_type_after_subquery_with_FromSql(bool async)
763+
=> AssertQuery(
764+
async,
765+
ss => ((DbSet<Customer>)ss.Set<Customer>()).FromSql(
766+
$"""
767+
SELECT DISTINCT [t].[Id], [t].[Name], [t].[BillingAddress_AddressLine1], [t].[BillingAddress_AddressLine2], [t].[BillingAddress_Tags], [t].[BillingAddress_ZipCode], [t].[BillingAddress_Country_Code], [t].[BillingAddress_Country_FullName], [t].[ShippingAddress_AddressLine1], [t].[ShippingAddress_AddressLine2], [t].[ShippingAddress_Tags], [t].[ShippingAddress_ZipCode], [t].[ShippingAddress_Country_Code], [t].[ShippingAddress_Country_FullName]
768+
FROM (
769+
SELECT [c].[Id], [c].[Name], [c].[BillingAddress_AddressLine1], [c].[BillingAddress_AddressLine2], [c].[BillingAddress_Tags], [c].[BillingAddress_ZipCode], [c].[BillingAddress_Country_Code], [c].[BillingAddress_Country_FullName], [c].[ShippingAddress_AddressLine1], [c].[ShippingAddress_AddressLine2], [c].[ShippingAddress_Tags], [c].[ShippingAddress_ZipCode], [c].[ShippingAddress_Country_Code], [c].[ShippingAddress_Country_FullName]
770+
FROM [Customer] AS [c]
771+
ORDER BY [c].[Id]
772+
OFFSET {1} ROWS
773+
) AS [t]
774+
WHERE [t].[ShippingAddress_ZipCode] = 7728
775+
"""),
776+
ss => ss.Set<Customer>()
777+
.OrderBy(c => c.Id)
778+
.Skip(1)
779+
.Distinct()
780+
.Where(c => c.ShippingAddress.ZipCode == 07728));
781+
782+
[ConditionalTheory]
783+
[MemberData(nameof(IsAsyncData))]
784+
public virtual Task Load_complex_type_after_subquery_on_entity_type_with_FromSql(bool async)
785+
=> AssertQuery(
786+
async,
787+
ss => ((DbSet<Customer>)ss.Set<Customer>()).FromSql(
788+
$"""
789+
SELECT DISTINCT [t].[Id], [t].[Name], [t].[BillingAddress_AddressLine1], [t].[BillingAddress_AddressLine2], [t].[BillingAddress_Tags], [t].[BillingAddress_ZipCode], [t].[BillingAddress_Country_Code], [t].[BillingAddress_Country_FullName], [t].[ShippingAddress_AddressLine1], [t].[ShippingAddress_AddressLine2], [t].[ShippingAddress_Tags], [t].[ShippingAddress_ZipCode], [t].[ShippingAddress_Country_Code], [t].[ShippingAddress_Country_FullName]
790+
FROM (
791+
SELECT [c].[Id], [c].[Name], [c].[BillingAddress_AddressLine1], [c].[BillingAddress_AddressLine2], [c].[BillingAddress_Tags], [c].[BillingAddress_ZipCode], [c].[BillingAddress_Country_Code], [c].[BillingAddress_Country_FullName], [c].[ShippingAddress_AddressLine1], [c].[ShippingAddress_AddressLine2], [c].[ShippingAddress_Tags], [c].[ShippingAddress_ZipCode], [c].[ShippingAddress_Country_Code], [c].[ShippingAddress_Country_FullName]
792+
FROM [Customer] AS [c]
793+
ORDER BY [c].[Id]
794+
OFFSET {1} ROWS
795+
) AS [t]
796+
"""),
797+
ss => ss.Set<Customer>()
798+
.OrderBy(c => c.Id)
799+
.Skip(1)
800+
.Distinct());
801+
802+
[ConditionalTheory]
803+
[MemberData(nameof(IsAsyncData))]
804+
public virtual Task Select_complex_type_with_FromSql(bool async)
805+
=> AssertQuery(
806+
async,
807+
ss => ((DbSet<Customer>)ss.Set<Customer>()).FromSqlRaw(
808+
"""
809+
SELECT [c].[ShippingAddress_AddressLine1], [c].[ShippingAddress_AddressLine2], [c].[ShippingAddress_Tags], [c].[ShippingAddress_ZipCode], [c].[ShippingAddress_Country_Code], [c].[ShippingAddress_Country_FullName]
810+
FROM [Customer] AS [c]
811+
""").Select(c => c.ShippingAddress),
812+
ss => ss.Set<Customer>().Select(c => c.ShippingAddress));
813+
814+
[ConditionalTheory]
815+
[MemberData(nameof(IsAsyncData))]
816+
public virtual Task Select_nested_complex_type_with_FromSql(bool async)
817+
=> AssertQuery(
818+
async,
819+
ss => ((DbSet<Customer>)ss.Set<Customer>()).FromSqlRaw(
820+
"""
821+
SELECT [c].[ShippingAddress_Country_Code], [c].[ShippingAddress_Country_FullName]
822+
FROM [Customer] AS [c]
823+
""").Select(c => c.ShippingAddress.Country),
824+
ss => ss.Set<Customer>().Select(c => c.ShippingAddress.Country));
825+
826+
[ConditionalTheory]
827+
[MemberData(nameof(IsAsyncData))]
828+
public virtual Task Select_single_property_on_nested_complex_type_with_FromSql(bool async)
829+
=> AssertQuery(
830+
async,
831+
ss => ((DbSet<Customer>)ss.Set<Customer>()).FromSqlRaw(
832+
"""
833+
SELECT [c].[ShippingAddress_Country_FullName]
834+
FROM [Customer] AS [c]
835+
""").Select(c => c.ShippingAddress.Country.FullName),
836+
ss => ss.Set<Customer>().Select(c => c.ShippingAddress.Country.FullName));
837+
838+
[ConditionalTheory]
839+
[MemberData(nameof(IsAsyncData))]
840+
public virtual Task Select_complex_type_Where_with_FromSql(bool async)
841+
=> AssertQuery(
842+
async,
843+
ss => ((DbSet<Customer>)ss.Set<Customer>()).FromSqlRaw(
844+
"""
845+
SELECT [c].[ShippingAddress_AddressLine1], [c].[ShippingAddress_AddressLine2], [c].[ShippingAddress_Tags], [c].[ShippingAddress_ZipCode], [c].[ShippingAddress_Country_Code], [c].[ShippingAddress_Country_FullName]
846+
FROM [Customer] AS [c]
847+
""").Select(c => c.ShippingAddress).Where(a => a.ZipCode == 07728),
848+
ss => ss.Set<Customer>().Select(c => c.ShippingAddress).Where(a => a.ZipCode == 07728));
849+
850+
[ConditionalTheory]
851+
[MemberData(nameof(IsAsyncData))]
852+
public virtual Task Select_complex_type_Distinct_with_FromSql(bool async)
853+
=> AssertQuery(
854+
async,
855+
ss => ((DbSet<Customer>)ss.Set<Customer>()).FromSqlRaw(
856+
"""
857+
SELECT [c].[ShippingAddress_AddressLine1], [c].[ShippingAddress_AddressLine2], [c].[ShippingAddress_Tags], [c].[ShippingAddress_ZipCode], [c].[ShippingAddress_Country_Code], [c].[ShippingAddress_Country_FullName]
858+
FROM [Customer] AS [c]
859+
""").Select(c => c.ShippingAddress).Distinct(),
860+
ss => ss.Set<Customer>().Select(c => c.ShippingAddress).Distinct());
861+
745862
[ConditionalFact]
746863
public virtual void Check_all_tests_overridden()
747864
=> TestHelpers.AssertAllMethodsOverridden(GetType());

0 commit comments

Comments
 (0)