Skip to content
Open
39 changes: 36 additions & 3 deletions src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<ViewMapping> viewMappings,
Expand Down Expand Up @@ -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<ViewMapping>?)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);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ public static class RelationalTypeBaseExtensions
public static IEnumerable<ITableMappingBase> GetViewOrTableMappings(this ITypeBase typeBase)
{
typeBase.Model.EnsureRelationalModel();
return (IEnumerable<ITableMappingBase>?)(typeBase.FindRuntimeAnnotationValue(
RelationalAnnotationNames.ViewMappings)
?? typeBase.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableMappings))
?? [];
var viewMapping = typeBase.FindRuntimeAnnotationValue(RelationalAnnotationNames.ViewMappings);
var tableMapping = typeBase.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableMappings);
return (IEnumerable<ITableMappingBase>?)(viewMapping ?? tableMapping) ?? [];
}
}
2 changes: 1 addition & 1 deletion src/EFCore.Relational/Metadata/Internal/ViewMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class ViewMapping : TableMappingBase<ViewColumnMapping>, IViewMapping
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public ViewMapping(
IEntityType entityType,
ITypeBase entityType,
View view,
bool? includesDerivedTypes)
: base(entityType, view, includesDerivedTypes)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,23 +294,23 @@ when elementAtMethod.GetGenericMethodDefinition() == QueryableMethods.ElementAt
default:
throw new InvalidOperationException(RelationalStrings.InvalidPropertyInSetProperty(propertySelector.Print()));

bool TryTranslateMemberAccess(
Expression expression,
[NotNullWhen(true)] out Expression? translation,
[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))
bool TryTranslateMemberAccess(
Expression expression,
[NotNullWhen(true)] out Expression? translation,
[NotNullWhen(true)] out IPropertyBase? property)
{
translation = target;
property = targetProperty;
return true;
}
if (IsMemberAccess(expression, QueryCompilationContext.Model, out var baseExpression, out var member)
&& _sqlTranslator.TryBindMember(_sqlTranslator.Visit(baseExpression), member, out var target, out var targetProperty))
{
translation = target;
property = targetProperty;
return true;
}

translation = null;
property = null;
return false;
}
translation = null;
property = null;
return false;
}
}

if (targetProperty.DeclaringType is IEntityType entityType && entityType.IsMappedToJson())
Expand Down Expand Up @@ -473,8 +473,7 @@ void ProcessColumn(ColumnExpression column)
targetColumnModel = complexType.ContainingEntityType.GetTableMappings()
.SelectMany(m => m.Table.Columns)
.Where(c => c.Name == containerColumnName)
.Single();

.SingleOrDefault();
break;
}

Expand Down Expand Up @@ -510,14 +509,17 @@ void ProcessColumn(ColumnExpression column)
void ProcessComplexType(StructuralTypeShaperExpression shaperExpression, Expression valueExpression)
{
if (shaperExpression.StructuralType is not IComplexType complexType
|| shaperExpression.ValueBufferExpression is not StructuralTypeProjectionExpression projection)
|| shaperExpression.ValueBufferExpression is not StructuralTypeProjectionExpression projection)
{
throw new UnreachableException();
}

foreach (var property in complexType.GetProperties())
{
targetProperty = property;
var column = projection.BindProperty(property);
ProcessColumn(column);

CheckColumnOnSameTable(column, propertySelector);

var rewrittenValueSelector = CreatePropertyAccessExpression(valueExpression, property);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,20 +108,30 @@ await AssertUpdate(
rowsAffectedCount: 1);
}

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

// #34706
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => AssertUpdate(
await AssertUpdate(
async,
contextFactory.CreateContext,
ss => ss.Foos,
s => s.SetProperty(f => f.ComplexThing, new Context34677.ComplexThing { Prop1 = 3, Prop2 = 4 }),
rowsAffectedCount: 1));
rowsAffectedCount: 1);
}

Assert.IsType<KeyNotFoundException>(exception.InnerException);
[ConditionalTheory, MemberData(nameof(IsAsyncData))] // #34677
public virtual async Task Update_complex_type_property_with_view_mapping(bool async)
{
var contextFactory = await InitializeAsync<Context34677>(seed: async context => await context.Seed());

await AssertUpdate(
async,
contextFactory.CreateContext,
ss => ss.Foos,
s => s.SetProperty(f => f.ComplexThing.Prop1, 6),
rowsAffectedCount: 1);
}

protected class Context34677(DbContextOptions options) : DbContext(options)
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<Order> Orders { get; set; }
public List<Address> Addresses { get; set; }
}
Expand Down Expand Up @@ -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; }
Expand All @@ -107,6 +144,7 @@ protected class UDFSqlContext(DbContextOptions options) : PoolableDbContext(opti
public DbSet<Order> Orders { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Address> Addresses { get; set; }
public DbSet<MapLocation> MapLocations { get; set; }

#endregion

Expand Down Expand Up @@ -355,6 +393,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity<OrderByYear>().HasNoKey();
modelBuilder.Entity<TopSellingProduct>().HasNoKey().ToFunction("GetTopTwoSellingProducts");
modelBuilder.Entity<CustomerData>().ToView("Customers");
modelBuilder.Entity<MapLocationData>().ToView("MapLocations");
}
}

Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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<MapLocationData>()
orderby t.Id
select t).ToList();

Assert.Equal(2, locations.Count);
}
}

[ConditionalFact]
public virtual void Udf_with_argument_being_comparison_to_null_parameter()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,31 @@ public override async Task Update_complex_type_with_view_mapping(bool async)
{
await base.Update_complex_type_with_view_mapping(async);

// #34706
AssertSql();
AssertSql(
"""
@complex_type_p_Prop1='3' (Nullable = true)
@complex_type_p_Prop2='4' (Nullable = true)

UPDATE [b]
SET [b].[ComplexThing_Prop1] = @complex_type_p_Prop1,
[b].[ComplexThing_Prop2] = @complex_type_p_Prop2
FROM [Blogs] AS [b]
""");

}

public override async Task Update_complex_type_property_with_view_mapping(bool async)
{
await base.Update_complex_type_property_with_view_mapping(async);

AssertSql(
"""
@p='6'

UPDATE [b]
SET [b].[ComplexThing_Prop1] = @p
FROM [Blogs] AS [b]
""");
}

private void AssertSql(params string[] expected)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading