From 0d1f0c147f5e02e71086fac798905c2536798ee2 Mon Sep 17 00:00:00 2001 From: "kirolous.nashaat" Date: Sun, 28 Sep 2025 20:56:41 +0300 Subject: [PATCH 1/2] Added test for select complex property from tvf Added metadata for complextypes for AddViews in RelationalModel --- .../Metadata/Internal/RelationalModel.cs | 39 ++++++++++- .../Internal/RelationalTypeBaseExtensions.cs | 7 +- .../Metadata/Internal/ViewMapping.cs | 2 +- .../Query/UdfDbFunctionTestBase.cs | 65 ++++++++++++++++++- .../Query/UdfDbFunctionSqlServerTests.cs | 12 ++++ 5 files changed, 116 insertions(+), 9 deletions(-) diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index 9f6c26fb8fb..5fe2631911f 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Data; using System.Text; namespace Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -713,8 +714,8 @@ private static void AddViews( private static void CreateViewMapping( IRelationalTypeMappingSource relationalTypeMappingSource, - IEntityType entityType, - IEntityType mappedType, + ITypeBase entityType, + ITypeBase mappedType, StoreObjectIdentifier mappedView, RelationalModel databaseModel, List viewMappings, @@ -770,11 +771,43 @@ private static void CreateViewMapping( } } + // TODO: Change this to call GetComplexProperties() + // Issue #31248 + foreach (var complexProperty in mappedType.GetDeclaredComplexProperties()) + { + var complexType = complexProperty.ComplexType; + + var complexViewMappings = + (List?)complexType.FindRuntimeAnnotationValue(RelationalAnnotationNames.ViewMappings); + if (complexViewMappings == null) + { + complexViewMappings = []; + complexType.AddRuntimeAnnotation(RelationalAnnotationNames.ViewMappings, complexViewMappings); + } + + CreateViewMapping( + relationalTypeMappingSource, + complexType, + complexType, + mappedView, + databaseModel, + complexViewMappings, + includesDerivedTypes: true, + isSplitEntityTypePrincipal: isSplitEntityTypePrincipal == true ? false : isSplitEntityTypePrincipal); + } + if (((ITableMappingBase)viewMapping).ColumnMappings.Any() || viewMappings.Count == 0) { viewMappings.Add(viewMapping); - view.EntityTypeMappings.Add(viewMapping); + if(entityType is IEntityType) + { + view.EntityTypeMappings.Add(viewMapping); + } + else + { + view.ComplexTypeMappings.Add(viewMapping); + } } } diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalTypeBaseExtensions.cs b/src/EFCore.Relational/Metadata/Internal/RelationalTypeBaseExtensions.cs index 0c30e6610b6..36fac663d64 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalTypeBaseExtensions.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalTypeBaseExtensions.cs @@ -22,9 +22,8 @@ public static class RelationalTypeBaseExtensions public static IEnumerable GetViewOrTableMappings(this ITypeBase typeBase) { typeBase.Model.EnsureRelationalModel(); - return (IEnumerable?)(typeBase.FindRuntimeAnnotationValue( - RelationalAnnotationNames.ViewMappings) - ?? typeBase.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableMappings)) - ?? []; + var viewMapping = typeBase.FindRuntimeAnnotationValue(RelationalAnnotationNames.ViewMappings); + var tableMapping = typeBase.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableMappings); + return (IEnumerable?)(viewMapping ?? tableMapping) ?? []; } } diff --git a/src/EFCore.Relational/Metadata/Internal/ViewMapping.cs b/src/EFCore.Relational/Metadata/Internal/ViewMapping.cs index 7faf3af154c..d6d7ae000d1 100644 --- a/src/EFCore.Relational/Metadata/Internal/ViewMapping.cs +++ b/src/EFCore.Relational/Metadata/Internal/ViewMapping.cs @@ -18,7 +18,7 @@ public class ViewMapping : TableMappingBase, IViewMapping /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public ViewMapping( - IEntityType entityType, + ITypeBase entityType, View view, bool? includesDerivedTypes) : base(entityType, view, includesDerivedTypes) diff --git a/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs index f47dd2a4414..dd13c7db404 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; namespace Microsoft.EntityFrameworkCore.Query; @@ -17,12 +18,23 @@ protected UDFSqlContext CreateContext() #region Model + [ComplexType] + public class Phone + { + public Phone(int code, int number) + { + Code = code; + Number = number; + } + + public int Code { get; set; } + public int Number { get; set; } + } public class Customer { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } - public List Orders { get; set; } public List
Addresses { get; set; } } @@ -92,6 +104,31 @@ public class TopSellingProduct public int? AmountSold { get; set; } } + [ComplexType] + public class ComplexGpsCoordinates + { + public ComplexGpsCoordinates(double latitude, double longitude) + { + Latitude = latitude; + Longitude = longitude; + } + + public double Latitude { get; set; } + public double Longitude { get; set; } + } + + public class MapLocation + { + public int Id { get; set; } + public ComplexGpsCoordinates GpsCoordinates { get; set; } + } + + public class MapLocationData + { + public int Id { get; set; } + public ComplexGpsCoordinates GpsCoordinates { get; set; } + } + public class CustomerData { public int Id { get; set; } @@ -107,6 +144,7 @@ protected class UDFSqlContext(DbContextOptions options) : PoolableDbContext(opti public DbSet Orders { get; set; } public DbSet Products { get; set; } public DbSet
Addresses { get; set; } + public DbSet MapLocations { get; set; } #endregion @@ -355,6 +393,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().HasNoKey(); modelBuilder.Entity().HasNoKey().ToFunction("GetTopTwoSellingProducts"); modelBuilder.Entity().ToView("Customers"); + modelBuilder.Entity().ToView("MapLocations"); } } @@ -520,11 +559,22 @@ protected override async Task SeedAsync(DbContext context) ] }; + var location1 = new MapLocation + { + GpsCoordinates = new ComplexGpsCoordinates(1.0, 2.0), + }; + + var location2 = new MapLocation + { + GpsCoordinates = new ComplexGpsCoordinates(1.0, 2.0), + }; + ((UDFSqlContext)context).Products.AddRange(product1, product2, product3, product4, product5); ((UDFSqlContext)context).Addresses.AddRange( address11, address12, address21, address31, address32, address41, address42, address43); ((UDFSqlContext)context).Customers.AddRange(customer1, customer2, customer3, customer4); ((UDFSqlContext)context).Orders.AddRange(order11, order12, order13, order21, order22, order31); + ((UDFSqlContext)context).MapLocations.AddRange(location1, location2); } } @@ -2182,6 +2232,19 @@ orderby t.FirstName } } + [ConditionalFact] + public virtual void TVF_backing_entity_type_with_complextype_mapped_to_view() + { + using (var context = CreateContext()) + { + var locations = (from t in context.Set() + orderby t.Id + select t).ToList(); + + Assert.Equal(2, locations.Count); + } + } + [ConditionalFact] public virtual void Udf_with_argument_being_comparison_to_null_parameter() { diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs b/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs index 661e354256e..591fdc855db 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs @@ -969,6 +969,18 @@ ORDER BY [c].[FirstName] """); } + public override void TVF_backing_entity_type_with_complextype_mapped_to_view() + { + base.TVF_backing_entity_type_with_complextype_mapped_to_view(); + + AssertSql( + """ +SELECT [m].[Id], [m].[GpsCoordinates_Latitude], [m].[GpsCoordinates_Longitude] +FROM [MapLocations] AS [m] +ORDER BY [m].[Id] +"""); + } + public override void Udf_with_argument_being_comparison_to_null_parameter() { base.Udf_with_argument_being_comparison_to_null_parameter(); From a4f32d1decb78889d1599e55a41cb7309a3a93d2 Mon Sep 17 00:00:00 2001 From: Kirolous Nashaat Date: Sun, 12 Oct 2025 23:38:45 +0300 Subject: [PATCH 2/2] Fixed Update_complex_type_with_view_mapping tests --- ...leMethodTranslatingExpressionVisitor.ExecuteUpdate.cs | 6 +++--- .../Query/RelationalSqlTranslatingExpressionVisitor.cs | 9 +++++---- .../Query/SqlExpressions/SelectExpression.cs | 5 +++-- .../Query/StructuralTypeProjectionExpression.cs | 7 ++++++- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs index c4d4cac1cef..c1559ed0cdb 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs @@ -300,7 +300,7 @@ bool TryTranslateMemberAccess( [NotNullWhen(true)] out IPropertyBase? property) { if (IsMemberAccess(expression, QueryCompilationContext.Model, out var baseExpression, out var member) - && _sqlTranslator.TryBindMember(_sqlTranslator.Visit(baseExpression), member, out var target, out var targetProperty)) + && _sqlTranslator.TryBindMember(_sqlTranslator.Visit(baseExpression), member, out var target, out var targetProperty, forUpdate: true)) { translation = target; property = targetProperty; @@ -540,7 +540,7 @@ void ProcessComplexType(StructuralTypeShaperExpression shaperExpression, Express RelationalStrings.ExecuteUpdateOverJsonIsNotSupported(complexProperty.ComplexType.DisplayName())); } - var nestedShaperExpression = (StructuralTypeShaperExpression)projection.BindComplexProperty(complexProperty); + var nestedShaperExpression = (StructuralTypeShaperExpression)projection.BindComplexProperty(complexProperty, forUpdate: true); var nestedValueExpression = CreateComplexPropertyAccessExpression(valueExpression, complexProperty); ProcessComplexType(nestedShaperExpression, nestedValueExpression); } @@ -635,7 +635,7 @@ SqlParameterExpression parameter StructuralType: IComplexType, ValueBufferExpression: StructuralTypeProjectionExpression projection } - => projection.BindComplexProperty(complexProperty), + => projection.BindComplexProperty(complexProperty, forUpdate: true), _ => throw new UnreachableException() }; diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index c68f69f514c..bb710be2272 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -1219,7 +1219,8 @@ public virtual bool TryBindMember( Expression? source, MemberIdentity member, [NotNullWhen(true)] out Expression? expression, - [NotNullWhen(true)] out IPropertyBase? property) + [NotNullWhen(true)] out IPropertyBase? property, + bool forUpdate = false) { if (source is not StructuralTypeReferenceExpression typeReference) { @@ -1247,7 +1248,7 @@ public virtual bool TryBindMember( if (complexProperty is not null) { - expression = BindComplexProperty(typeReference, complexProperty); + expression = BindComplexProperty(typeReference, complexProperty, forUpdate: forUpdate); property = complexProperty; return true; } @@ -1359,7 +1360,7 @@ private SqlExpression BindProperty(StructuralTypeReferenceExpression typeReferen } } - private Expression BindComplexProperty(StructuralTypeReferenceExpression typeReference, IComplexProperty complexProperty) + private Expression BindComplexProperty(StructuralTypeReferenceExpression typeReference, IComplexProperty complexProperty, bool forUpdate = false) { switch (typeReference) { @@ -1370,7 +1371,7 @@ private Expression BindComplexProperty(StructuralTypeReferenceExpression typeRef // TODO: Move all this logic into StructuralTypeProjectionExpression, #31376 Check.DebugAssert(structuralTypeProjection.IsNullable == shaper.IsNullable, "Nullability mismatch"); - return structuralTypeProjection.BindComplexProperty(complexProperty) switch + return structuralTypeProjection.BindComplexProperty(complexProperty, forUpdate: forUpdate) switch { StructuralTypeShaperExpression s => new StructuralTypeReferenceExpression(s), CollectionResultExpression c => c, diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index 2d57cfb6298..df4cca48246 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -2855,14 +2855,15 @@ static TableExpressionBase FindRootTableExpressionForColumn(SelectExpression sel [EntityFrameworkInternal] public static Expression GenerateComplexPropertyShaperExpression( StructuralTypeProjectionExpression containerProjection, - IComplexProperty complexProperty) + IComplexProperty complexProperty, + bool forUpdate = false) { var complexType = complexProperty.ComplexType; var propertyExpressionMap = new Dictionary(); // We do not support complex type splitting, so we will only ever have a single table/view mapping to it. // See Issue #32853 and Issue #31248 - var complexTypeTable = complexType.GetViewOrTableMappings().Single().Table; + ITableBase complexTypeTable = forUpdate ? complexType.GetTableMappings().Single().Table : complexType.GetViewOrTableMappings().Single().Table; if (!containerProjection.TableMap.TryGetValue(complexTypeTable, out var tableAlias)) { diff --git a/src/EFCore.Relational/Query/StructuralTypeProjectionExpression.cs b/src/EFCore.Relational/Query/StructuralTypeProjectionExpression.cs index 8ca55f5227e..e6792228d2b 100644 --- a/src/EFCore.Relational/Query/StructuralTypeProjectionExpression.cs +++ b/src/EFCore.Relational/Query/StructuralTypeProjectionExpression.cs @@ -369,9 +369,14 @@ public virtual ColumnExpression BindProperty(IProperty property) /// Binds a complex property with this structural type projection to get a shaper expression for the target complex type. /// /// A complex property to bind. + /// Is the mapping for read (false) or update (true). /// A shaper expression for the target complex type. - public virtual Expression BindComplexProperty(IComplexProperty complexProperty) + public virtual Expression BindComplexProperty(IComplexProperty complexProperty, bool forUpdate = false) { + if (forUpdate) + { + return SelectExpression.GenerateComplexPropertyShaperExpression(this, complexProperty, forUpdate: true); + } if (_complexPropertyCache is null || !_complexPropertyCache.TryGetValue(complexProperty, out var resultShaper)) { _complexPropertyCache ??= new Dictionary();