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
Original file line number Diff line number Diff line change
Expand Up @@ -470,10 +470,20 @@ public static void SetSqlQuery(this IMutableEntityType entityType, string? query
public static string? GetFunctionName(this IReadOnlyEntityType entityType)
{
var nameAnnotation = entityType.FindAnnotation(RelationalAnnotationNames.FunctionName);
return nameAnnotation != null
? (string?)nameAnnotation.Value
: entityType.BaseType != null
? entityType.GetRootType().GetFunctionName()
if (nameAnnotation != null)
{
return (string?)nameAnnotation.Value;
}

if (entityType.BaseType != null)
{
return entityType.GetRootType().GetFunctionName();
}

var ownership = entityType.FindOwnership();
return ownership != null
&& (ownership.IsUnique || entityType.IsMappedToJson())
? ownership.PrincipalEntityType.GetFunctionName()
: null;
}

Expand Down
92 changes: 62 additions & 30 deletions src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,12 @@ public static IRelationalModel Create(

AddSqlQueries(databaseModel, entityType);

AddMappedFunctions(databaseModel, entityType);
AddMappedFunctions(databaseModel, entityType, relationalTypeMappingSource);

AddStoredProcedures(databaseModel, entityType, relationalTypeMappingSource);
}

AddTvfs(databaseModel);
AddTvfs(databaseModel, relationalTypeMappingSource);

var tables = ((IRelationalModel)databaseModel).Tables;
foreach (Table table in tables)
Expand Down Expand Up @@ -918,7 +918,7 @@ private static void AddSqlQueries(RelationalModel databaseModel, IEntityType ent
queryMappings?.Reverse();
}

private static void AddMappedFunctions(RelationalModel databaseModel, IEntityType entityType)
private static void AddMappedFunctions(RelationalModel databaseModel, IEntityType entityType, IRelationalTypeMappingSource relationalTypeMappingSource)
{
var model = databaseModel.Model;
var functionName = entityType.GetFunctionName();
Expand All @@ -940,7 +940,7 @@ private static void AddMappedFunctions(RelationalModel databaseModel, IEntityTyp
}

var dbFunction = (IRuntimeDbFunction)model.FindDbFunction(mappedFunctionName)!;
var functionMapping = CreateFunctionMapping(entityType, mappedType, dbFunction, databaseModel, @default: true);
var functionMapping = CreateFunctionMapping(entityType, mappedType, dbFunction, databaseModel, relationalTypeMappingSource, @default: true);

mappedType = mappedType.BaseType;

Expand All @@ -963,7 +963,7 @@ private static void AddMappedFunctions(RelationalModel databaseModel, IEntityTyp
functionMappings?.Reverse();
}

private static void AddTvfs(RelationalModel relationalModel)
private static void AddTvfs(RelationalModel relationalModel, IRelationalTypeMappingSource relationalTypeMappingSource)
{
var model = relationalModel.Model;
foreach (IRuntimeDbFunction function in model.GetDbFunctions())
Expand All @@ -982,25 +982,45 @@ private static void AddTvfs(RelationalModel relationalModel)
continue;
}

var functionMapping = CreateFunctionMapping(entityType, entityType, function, relationalModel, @default: false);
AddTvfMapping(entityType, function, relationalModel, relationalTypeMappingSource);

if (entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.FunctionMappings)
is not List<FunctionMapping> functionMappings)
foreach (var ownedJsonNavigation in entityType.GetNavigationsInHierarchy()
.Where(
n => n.ForeignKey.IsOwnership
&& n.TargetEntityType.IsMappedToJson()
&& n.ForeignKey.PrincipalToDependent == n))
{
functionMappings = [];
entityType.AddRuntimeAnnotation(RelationalAnnotationNames.FunctionMappings, functionMappings);
AddTvfMapping(ownedJsonNavigation.TargetEntityType, function, relationalModel, relationalTypeMappingSource);
}
}
}

functionMappings.Add(functionMapping);
((StoreFunction)functionMapping.StoreFunction).EntityTypeMappings.Add(functionMapping);
private static void AddTvfMapping(
IEntityType entityType,
IRuntimeDbFunction function,
RelationalModel relationalModel,
IRelationalTypeMappingSource relationalTypeMappingSource)
{
var functionMapping = CreateFunctionMapping(
entityType, entityType, function, relationalModel, relationalTypeMappingSource, @default: false);

if (entityType.FindRuntimeAnnotationValue(RelationalAnnotationNames.FunctionMappings)
is not List<FunctionMapping> functionMappings)
{
functionMappings = [];
entityType.AddRuntimeAnnotation(RelationalAnnotationNames.FunctionMappings, functionMappings);
}

functionMappings.Add(functionMapping);
((StoreFunction)functionMapping.StoreFunction).EntityTypeMappings.Add(functionMapping);
}

private static FunctionMapping CreateFunctionMapping(
IEntityType entityType,
IEntityType mappedType,
IRuntimeDbFunction dbFunction,
RelationalModel model,
IRelationalTypeMappingSource relationalTypeMappingSource,
bool @default)
{
var storeFunction = GetOrCreateStoreFunction(dbFunction, model);
Expand All @@ -1010,29 +1030,41 @@ private static FunctionMapping CreateFunctionMapping(
entityType, storeFunction, dbFunction,
includesDerivedTypes: entityType.GetDirectlyDerivedTypes().Any() ? true : null) { IsDefaultFunctionMapping = @default };

foreach (var property in mappedType.GetProperties())
var containerColumnName = mappedType.GetContainerColumnName();
var containerColumnType = mappedType.GetContainerColumnType();
if (!string.IsNullOrEmpty(containerColumnName))
{
var columnName = property.GetColumnName(mappedFunction);
if (columnName == null)
CreateContainerColumn(
storeFunction, containerColumnName, containerColumnType, mappedType, relationalTypeMappingSource,
static (colName, colType, table, mapping)
=> new FunctionColumn(colName, colType ?? mapping.StoreType, (StoreFunction)table, mapping));
}
else
{
foreach (var property in mappedType.GetProperties())
{
continue;
}
var columnName = property.GetColumnName(mappedFunction);
if (columnName == null)
{
continue;
}

var column = storeFunction.FindColumn(columnName);
if (column == null)
{
column = new FunctionColumn(columnName, property.GetColumnType(mappedFunction), storeFunction)
var column = storeFunction.FindColumn(columnName);
if (column == null)
{
IsNullable = property.IsColumnNullable(mappedFunction)
};
storeFunction.Columns.Add(columnName, column);
}
else if (!property.IsColumnNullable(mappedFunction))
{
column.IsNullable = false;
}
column = new FunctionColumn(columnName, property.GetColumnType(mappedFunction), storeFunction)
{
IsNullable = property.IsColumnNullable(mappedFunction)
};
storeFunction.Columns.Add(columnName, column);
}
else if (!property.IsColumnNullable(mappedFunction))
{
column.IsNullable = false;
}

CreateFunctionColumnMapping(column, property, functionMapping);
CreateFunctionColumnMapping(column, property, functionMapping);
}
}

return functionMapping;
Expand Down
42 changes: 42 additions & 0 deletions test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public void Both_design_and_runtime_RelationalModels_are_built_for_external_mode
modelBuilder.Ignore<OrderDetails>();
modelBuilder.Ignore<DateDetails>();
modelBuilder.Ignore<Customer>();
modelBuilder.Ignore<Address>();
modelBuilder.Entity<Order>().ToTable(tb => tb.HasCheckConstraint("OrderCK", "[Id] > 0"));

var options = FakeRelationalTestHelpers.Instance.CreateOptions((IModel)modelBuilder.Model);
Expand Down Expand Up @@ -2275,6 +2276,7 @@ private IRelationalModel CreateTestModel(

modelBuilder.Entity<Order>(ob =>
{
ob.Ignore(o => o.Addresses);
ob.Property(o => o.OrderDate).HasColumnName("OrderDate");
ob.Property(o => o.AlternateId).HasColumnName("AlternateId");

Expand Down Expand Up @@ -2934,6 +2936,7 @@ public void Can_use_relational_model_with_SQL_queries()
cb.Ignore(c => c.Customer);
cb.Ignore(c => c.Details);
cb.Ignore(c => c.DateDetails);
cb.Ignore(c => c.Addresses);

cb.Property(c => c.AlternateId).HasColumnName("SomeName");
cb.HasNoKey();
Expand Down Expand Up @@ -3063,6 +3066,7 @@ public void Can_use_relational_model_with_functions()
cb.Ignore(c => c.Customer);
cb.Ignore(c => c.Details);
cb.Ignore(c => c.DateDetails);
cb.Ignore(c => c.Addresses);

cb.Property(c => c.AlternateId).HasColumnName("SomeName");
cb.HasNoKey();
Expand Down Expand Up @@ -3264,6 +3268,42 @@ public void Complex_property_json_column_is_nullable_in_TPH_hierarchy()
Assert.IsType<JsonColumn>(jsonColumn);
}

[ConditionalFact]
public void Can_use_relational_model_with_functions_and_json_owned_types()
Comment thread
AndriySvyryd marked this conversation as resolved.
{
var modelBuilder = CreateConventionModelBuilder();

modelBuilder.Entity<Order>(cb =>
{
cb.Ignore(c => c.Customer);
cb.Ignore(c => c.Details);
cb.Ignore(c => c.ComplexProperty);

#pragma warning disable EF8001 // Owned JSON entities are obsolete
cb.OwnsOne(c => c.DateDetails, o => o.ToJson("date_details"));
cb.OwnsMany(c => c.Addresses, o => o.ToJson("addresses"));
#pragma warning restore EF8001
});

modelBuilder.HasDbFunction(
typeof(RelationalModelTest).GetMethod(
nameof(GetOrdersForCustomer), BindingFlags.NonPublic | BindingFlags.Static, [typeof(int)]));
Comment thread
AndriySvyryd marked this conversation as resolved.

var model = Finalize(modelBuilder);

var orderType = model.Model.FindEntityType(typeof(Order));

var functionMappings = orderType.GetFunctionMappings().ToList();
Assert.Single(functionMappings);

var storeFunction = functionMappings[0].StoreFunction;
Assert.Equal(
[nameof(Order.AlternateId), nameof(Order.CustomerId), nameof(Order.Id), nameof(Order.OrderDate), "addresses", "date_details"],
storeFunction.Columns.Select(m => m.Name));
Assert.NotNull(storeFunction.FindColumn("date_details"));
Assert.NotNull(storeFunction.FindColumn("addresses"));
}

private static IRelationalModel Finalize(TestHelpers.TestModelBuilder modelBuilder)
=> modelBuilder.FinalizeModel(designTime: true).GetRelationalModel();

Expand Down Expand Up @@ -3350,6 +3390,8 @@ private class Order
public OrderDetails Details { get; set; }

public ComplexData ComplexProperty { get; set; }

public List<Address> Addresses { get; set; }
}

private class OrderDetails
Expand Down
Loading