Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 35 additions & 3 deletions src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -713,8 +713,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 +770,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,15 +509,16 @@ 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);
CheckColumnOnSameTable(column, propertySelector);
ProcessColumn(column);

var rewrittenValueSelector = CreatePropertyAccessExpression(valueExpression, property);
var translatedValueSelector = TranslateScalarSetterValueSelector(
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
16 changes: 15 additions & 1 deletion test/EFCore.Relational.Tests/Metadata/RelationalModelTest.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.ComponentModel.DataAnnotations.Schema;
using System.Data;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using NameSpace1;
Expand Down Expand Up @@ -186,7 +187,7 @@ private static void AssertDefaultMappings(IRelationalModel model, Mapping mappin
Assert.Equal([nameof(Order), nameof(OrderDetails)], ordersTable.EntityTypeMappings.Select(m => m.TypeBase.DisplayName()));
Assert.Equal(
[
nameof(OrderDetails.Active), nameof(Order.AlternateId), nameof(Order.CustomerId), nameof(Order.Id),
nameof(OrderDetails.Active), nameof(Order.AlternateId), nameof(Order.ComplexProperty) + "_" + nameof(ComplexData.Number), nameof(Order.ComplexProperty) + "_" + nameof(ComplexData.Value), nameof(Order.CustomerId), nameof(Order.Id),
nameof(OrderDetails.OrderDate), nameof(OrderDetails.OrderId)
],
ordersTable.Columns.Select(m => m.Name));
Expand Down Expand Up @@ -323,6 +324,8 @@ private static void AssertViews(IRelationalModel model, Mapping mapping)
Assert.Equal(
[
nameof(Order.AlternateId),
nameof(Order.ComplexProperty) + "_" + nameof(ComplexData.Number),
nameof(Order.ComplexProperty) + "_" + nameof(ComplexData.Value),
nameof(Order.CustomerId),
"Details_Active",
"Details_BillingAddress_City",
Expand Down Expand Up @@ -565,6 +568,8 @@ private static void AssertTables(IRelationalModel model, Mapping mapping)
[
nameof(Order.Id),
nameof(Order.AlternateId),
nameof(Order.ComplexProperty) + "_" + nameof(ComplexData.Number),
nameof(Order.ComplexProperty) + "_" + nameof(ComplexData.Value),
nameof(Order.CustomerId),
"Details_Active",
"Details_BillingAddress_City",
Expand Down Expand Up @@ -2250,6 +2255,12 @@ private IRelationalModel CreateTestModel(
.HasForeignKey<Order>(o => o.OrderDate).HasPrincipalKey<DateDetails>(o => o.Date)
.HasConstraintName("FK_DateDetails");

ob.ComplexProperty(c => c.ComplexProperty, b =>
{
b.Property(c => c.Number).HasColumnName("ComplexProperty_Number");
b.Property(c => c.Value).HasColumnName("ComplexProperty_Value");
});

ob.HasIndex(o => o.OrderDate).HasDatabaseName("IX_OrderDate");

if (mapToSprocs)
Expand Down Expand Up @@ -3279,6 +3290,8 @@ private class Order
public Customer Customer { get; set; }

public OrderDetails Details { get; set; }

public ComplexData ComplexProperty { get; set; }
}

private class OrderDetails
Expand Down Expand Up @@ -3318,6 +3331,7 @@ private class EntityWithComplexCollection
public List<ComplexData> ComplexCollection { get; set; }
}

[ComplexType]
private class ComplexData
{
public string Value { get; set; }
Expand Down
Loading