Skip to content

Commit bbf6760

Browse files
Added metadata support for complex types mapped to views (#37072)
Part of #34627, #34706
1 parent 4cb4dcc commit bbf6760

File tree

11 files changed

+364
-38
lines changed

11 files changed

+364
-38
lines changed

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

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -713,8 +713,8 @@ private static void AddViews(
713713

714714
private static void CreateViewMapping(
715715
IRelationalTypeMappingSource relationalTypeMappingSource,
716-
IEntityType entityType,
717-
IEntityType mappedType,
716+
ITypeBase entityType,
717+
ITypeBase mappedType,
718718
StoreObjectIdentifier mappedView,
719719
RelationalModel databaseModel,
720720
List<ViewMapping> viewMappings,
@@ -770,11 +770,43 @@ private static void CreateViewMapping(
770770
}
771771
}
772772

773+
// TODO: Change this to call GetComplexProperties()
774+
// Issue #31248
775+
foreach (var complexProperty in mappedType.GetDeclaredComplexProperties())
776+
{
777+
var complexType = complexProperty.ComplexType;
778+
779+
var complexViewMappings =
780+
(List<ViewMapping>?)complexType.FindRuntimeAnnotationValue(RelationalAnnotationNames.ViewMappings);
781+
if (complexViewMappings == null)
782+
{
783+
complexViewMappings = [];
784+
complexType.AddRuntimeAnnotation(RelationalAnnotationNames.ViewMappings, complexViewMappings);
785+
}
786+
787+
CreateViewMapping(
788+
relationalTypeMappingSource,
789+
complexType,
790+
complexType,
791+
mappedView,
792+
databaseModel,
793+
complexViewMappings,
794+
includesDerivedTypes: true,
795+
isSplitEntityTypePrincipal: isSplitEntityTypePrincipal == true ? false : isSplitEntityTypePrincipal);
796+
}
797+
773798
if (((ITableMappingBase)viewMapping).ColumnMappings.Any()
774799
|| viewMappings.Count == 0)
775800
{
776801
viewMappings.Add(viewMapping);
777-
view.EntityTypeMappings.Add(viewMapping);
802+
if (entityType is IEntityType)
803+
{
804+
view.EntityTypeMappings.Add(viewMapping);
805+
}
806+
else
807+
{
808+
view.ComplexTypeMappings.Add(viewMapping);
809+
}
778810
}
779811
}
780812

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@ public static class RelationalTypeBaseExtensions
2222
public static IEnumerable<ITableMappingBase> GetViewOrTableMappings(this ITypeBase typeBase)
2323
{
2424
typeBase.Model.EnsureRelationalModel();
25-
return (IEnumerable<ITableMappingBase>?)(typeBase.FindRuntimeAnnotationValue(
26-
RelationalAnnotationNames.ViewMappings)
27-
?? typeBase.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableMappings))
28-
?? [];
25+
var viewMapping = typeBase.FindRuntimeAnnotationValue(RelationalAnnotationNames.ViewMappings);
26+
var tableMapping = typeBase.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableMappings);
27+
return (IEnumerable<ITableMappingBase>?)(viewMapping ?? tableMapping) ?? [];
2928
}
3029
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public class ViewMapping : TableMappingBase<ViewColumnMapping>, IViewMapping
1818
/// doing so can result in application failures when updating to a new Entity Framework Core release.
1919
/// </summary>
2020
public ViewMapping(
21-
IEntityType entityType,
21+
ITypeBase entityType,
2222
View view,
2323
bool? includesDerivedTypes)
2424
: base(entityType, view, includesDerivedTypes)

src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -294,23 +294,23 @@ when elementAtMethod.GetGenericMethodDefinition() == QueryableMethods.ElementAt
294294
default:
295295
throw new InvalidOperationException(RelationalStrings.InvalidPropertyInSetProperty(propertySelector.Print()));
296296

297-
bool TryTranslateMemberAccess(
298-
Expression expression,
299-
[NotNullWhen(true)] out Expression? translation,
300-
[NotNullWhen(true)] out IPropertyBase? property)
301-
{
302-
if (IsMemberAccess(expression, QueryCompilationContext.Model, out var baseExpression, out var member)
303-
&& _sqlTranslator.TryBindMember(_sqlTranslator.Visit(baseExpression), member, out var target, out var targetProperty))
297+
bool TryTranslateMemberAccess(
298+
Expression expression,
299+
[NotNullWhen(true)] out Expression? translation,
300+
[NotNullWhen(true)] out IPropertyBase? property)
304301
{
305-
translation = target;
306-
property = targetProperty;
307-
return true;
308-
}
302+
if (IsMemberAccess(expression, QueryCompilationContext.Model, out var baseExpression, out var member)
303+
&& _sqlTranslator.TryBindMember(_sqlTranslator.Visit(baseExpression), member, out var target, out var targetProperty))
304+
{
305+
translation = target;
306+
property = targetProperty;
307+
return true;
308+
}
309309

310-
translation = null;
311-
property = null;
312-
return false;
313-
}
310+
translation = null;
311+
property = null;
312+
return false;
313+
}
314314
}
315315

316316
if (targetProperty.DeclaringType is IEntityType entityType && entityType.IsMappedToJson())
@@ -473,8 +473,7 @@ void ProcessColumn(ColumnExpression column)
473473
targetColumnModel = complexType.ContainingEntityType.GetTableMappings()
474474
.SelectMany(m => m.Table.Columns)
475475
.Where(c => c.Name == containerColumnName)
476-
.Single();
477-
476+
.SingleOrDefault();
478477
break;
479478
}
480479

@@ -510,15 +509,16 @@ void ProcessColumn(ColumnExpression column)
510509
void ProcessComplexType(StructuralTypeShaperExpression shaperExpression, Expression valueExpression)
511510
{
512511
if (shaperExpression.StructuralType is not IComplexType complexType
513-
|| shaperExpression.ValueBufferExpression is not StructuralTypeProjectionExpression projection)
512+
|| shaperExpression.ValueBufferExpression is not StructuralTypeProjectionExpression projection)
514513
{
515514
throw new UnreachableException();
516515
}
517516

518517
foreach (var property in complexType.GetProperties())
519518
{
519+
targetProperty = property;
520520
var column = projection.BindProperty(property);
521-
CheckColumnOnSameTable(column, propertySelector);
521+
ProcessColumn(column);
522522

523523
var rewrittenValueSelector = CreatePropertyAccessExpression(valueExpression, property);
524524
var translatedValueSelector = TranslateScalarSetterValueSelector(

test/EFCore.Relational.Specification.Tests/BulkUpdates/NonSharedModelBulkUpdatesRelationalTestBase.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,20 +108,30 @@ await AssertUpdate(
108108
rowsAffectedCount: 1);
109109
}
110110

111-
[ConditionalTheory, MemberData(nameof(IsAsyncData))] // #34677, #34706
111+
[ConditionalTheory, MemberData(nameof(IsAsyncData))] // #34677
112112
public virtual async Task Update_complex_type_with_view_mapping(bool async)
113113
{
114114
var contextFactory = await InitializeAsync<Context34677>(seed: async context => await context.Seed());
115115

116-
// #34706
117-
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => AssertUpdate(
116+
await AssertUpdate(
118117
async,
119118
contextFactory.CreateContext,
120119
ss => ss.Foos,
121120
s => s.SetProperty(f => f.ComplexThing, new Context34677.ComplexThing { Prop1 = 3, Prop2 = 4 }),
122-
rowsAffectedCount: 1));
121+
rowsAffectedCount: 1);
122+
}
123123

124-
Assert.IsType<KeyNotFoundException>(exception.InnerException);
124+
[ConditionalTheory, MemberData(nameof(IsAsyncData))] // #34677
125+
public virtual async Task Update_complex_type_property_with_view_mapping(bool async)
126+
{
127+
var contextFactory = await InitializeAsync<Context34677>(seed: async context => await context.Seed());
128+
129+
await AssertUpdate(
130+
async,
131+
contextFactory.CreateContext,
132+
ss => ss.Foos,
133+
s => s.SetProperty(f => f.ComplexThing.Prop1, 6),
134+
rowsAffectedCount: 1);
125135
}
126136

127137
protected class Context34677(DbContextOptions options) : DbContext(options)

test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +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.ComponentModel.DataAnnotations.Schema;
45
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
56

67
namespace Microsoft.EntityFrameworkCore.Query;
@@ -17,12 +18,23 @@ protected UDFSqlContext CreateContext()
1718

1819
#region Model
1920

21+
[ComplexType]
22+
public class Phone
23+
{
24+
public Phone(int code, int number)
25+
{
26+
Code = code;
27+
Number = number;
28+
}
29+
30+
public int Code { get; set; }
31+
public int Number { get; set; }
32+
}
2033
public class Customer
2134
{
2235
public int Id { get; set; }
2336
public string FirstName { get; set; }
2437
public string LastName { get; set; }
25-
2638
public List<Order> Orders { get; set; }
2739
public List<Address> Addresses { get; set; }
2840
}
@@ -92,6 +104,31 @@ public class TopSellingProduct
92104
public int? AmountSold { get; set; }
93105
}
94106

107+
[ComplexType]
108+
public class ComplexGpsCoordinates
109+
{
110+
public ComplexGpsCoordinates(double latitude, double longitude)
111+
{
112+
Latitude = latitude;
113+
Longitude = longitude;
114+
}
115+
116+
public double Latitude { get; set; }
117+
public double Longitude { get; set; }
118+
}
119+
120+
public class MapLocation
121+
{
122+
public int Id { get; set; }
123+
public ComplexGpsCoordinates GpsCoordinates { get; set; }
124+
}
125+
126+
public class MapLocationData
127+
{
128+
public int Id { get; set; }
129+
public ComplexGpsCoordinates GpsCoordinates { get; set; }
130+
}
131+
95132
public class CustomerData
96133
{
97134
public int Id { get; set; }
@@ -107,6 +144,7 @@ protected class UDFSqlContext(DbContextOptions options) : PoolableDbContext(opti
107144
public DbSet<Order> Orders { get; set; }
108145
public DbSet<Product> Products { get; set; }
109146
public DbSet<Address> Addresses { get; set; }
147+
public DbSet<MapLocation> MapLocations { get; set; }
110148

111149
#endregion
112150

@@ -355,6 +393,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
355393
modelBuilder.Entity<OrderByYear>().HasNoKey();
356394
modelBuilder.Entity<TopSellingProduct>().HasNoKey().ToFunction("GetTopTwoSellingProducts");
357395
modelBuilder.Entity<CustomerData>().ToView("Customers");
396+
modelBuilder.Entity<MapLocationData>().ToView("MapLocations");
358397
}
359398
}
360399

@@ -520,11 +559,22 @@ protected override async Task SeedAsync(DbContext context)
520559
]
521560
};
522561

562+
var location1 = new MapLocation
563+
{
564+
GpsCoordinates = new ComplexGpsCoordinates(1.0, 2.0),
565+
};
566+
567+
var location2 = new MapLocation
568+
{
569+
GpsCoordinates = new ComplexGpsCoordinates(1.0, 2.0),
570+
};
571+
523572
((UDFSqlContext)context).Products.AddRange(product1, product2, product3, product4, product5);
524573
((UDFSqlContext)context).Addresses.AddRange(
525574
address11, address12, address21, address31, address32, address41, address42, address43);
526575
((UDFSqlContext)context).Customers.AddRange(customer1, customer2, customer3, customer4);
527576
((UDFSqlContext)context).Orders.AddRange(order11, order12, order13, order21, order22, order31);
577+
((UDFSqlContext)context).MapLocations.AddRange(location1, location2);
528578
}
529579
}
530580

@@ -2182,6 +2232,19 @@ orderby t.FirstName
21822232
}
21832233
}
21842234

2235+
[ConditionalFact]
2236+
public virtual void TVF_backing_entity_type_with_complextype_mapped_to_view()
2237+
{
2238+
using (var context = CreateContext())
2239+
{
2240+
var locations = (from t in context.Set<MapLocationData>()
2241+
orderby t.Id
2242+
select t).ToList();
2243+
2244+
Assert.Equal(2, locations.Count);
2245+
}
2246+
}
2247+
21852248
[ConditionalFact]
21862249
public virtual void Udf_with_argument_being_comparison_to_null_parameter()
21872250
{

test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +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.ComponentModel.DataAnnotations.Schema;
45
using System.Data;
56
using Microsoft.EntityFrameworkCore.Metadata.Internal;
67
using NameSpace1;
@@ -186,7 +187,7 @@ private static void AssertDefaultMappings(IRelationalModel model, Mapping mappin
186187
Assert.Equal([nameof(Order), nameof(OrderDetails)], ordersTable.EntityTypeMappings.Select(m => m.TypeBase.DisplayName()));
187188
Assert.Equal(
188189
[
189-
nameof(OrderDetails.Active), nameof(Order.AlternateId), nameof(Order.CustomerId), nameof(Order.Id),
190+
nameof(OrderDetails.Active), nameof(Order.AlternateId), nameof(Order.ComplexProperty) + "_" + nameof(ComplexData.Number), nameof(Order.ComplexProperty) + "_" + nameof(ComplexData.Value), nameof(Order.CustomerId), nameof(Order.Id),
190191
nameof(OrderDetails.OrderDate), nameof(OrderDetails.OrderId)
191192
],
192193
ordersTable.Columns.Select(m => m.Name));
@@ -323,6 +324,8 @@ private static void AssertViews(IRelationalModel model, Mapping mapping)
323324
Assert.Equal(
324325
[
325326
nameof(Order.AlternateId),
327+
nameof(Order.ComplexProperty) + "_" + nameof(ComplexData.Number),
328+
nameof(Order.ComplexProperty) + "_" + nameof(ComplexData.Value),
326329
nameof(Order.CustomerId),
327330
"Details_Active",
328331
"Details_BillingAddress_City",
@@ -565,6 +568,8 @@ private static void AssertTables(IRelationalModel model, Mapping mapping)
565568
[
566569
nameof(Order.Id),
567570
nameof(Order.AlternateId),
571+
nameof(Order.ComplexProperty) + "_" + nameof(ComplexData.Number),
572+
nameof(Order.ComplexProperty) + "_" + nameof(ComplexData.Value),
568573
nameof(Order.CustomerId),
569574
"Details_Active",
570575
"Details_BillingAddress_City",
@@ -2250,6 +2255,12 @@ private IRelationalModel CreateTestModel(
22502255
.HasForeignKey<Order>(o => o.OrderDate).HasPrincipalKey<DateDetails>(o => o.Date)
22512256
.HasConstraintName("FK_DateDetails");
22522257

2258+
ob.ComplexProperty(c => c.ComplexProperty, b =>
2259+
{
2260+
b.Property(c => c.Number).HasColumnName("ComplexProperty_Number");
2261+
b.Property(c => c.Value).HasColumnName("ComplexProperty_Value");
2262+
});
2263+
22532264
ob.HasIndex(o => o.OrderDate).HasDatabaseName("IX_OrderDate");
22542265

22552266
if (mapToSprocs)
@@ -3279,6 +3290,8 @@ private class Order
32793290
public Customer Customer { get; set; }
32803291

32813292
public OrderDetails Details { get; set; }
3293+
3294+
public ComplexData ComplexProperty { get; set; }
32823295
}
32833296

32843297
private class OrderDetails
@@ -3318,6 +3331,7 @@ private class EntityWithComplexCollection
33183331
public List<ComplexData> ComplexCollection { get; set; }
33193332
}
33203333

3334+
[ComplexType]
33213335
private class ComplexData
33223336
{
33233337
public string Value { get; set; }

0 commit comments

Comments
 (0)