Skip to content

Commit d490ecc

Browse files
committed
Fully integrate JSON mapping into the relational model
Change partial update JSON path to be structured instead of a string JSONPATH Fixes #36646 Fixes #32185
1 parent e289bfa commit d490ecc

49 files changed

Lines changed: 3268 additions & 76 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/EFCore.Relational/Design/AnnotationCodeGenerator.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public class AnnotationCodeGenerator : IAnnotationCodeGenerator
3535
RelationalAnnotationNames.UpdateStoredProcedure,
3636
RelationalAnnotationNames.MappingFragments,
3737
RelationalAnnotationNames.RelationalOverrides,
38+
RelationalAnnotationNames.JsonElementMappings,
3839
#pragma warning disable CS0618
3940
RelationalAnnotationNames.ContainerColumnTypeMapping
4041
#pragma warning restore CS0618

src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,8 @@ private string GetOrCreate(
369369
Create(column, tableParameters);
370370
}
371371

372+
CreateJsonElements(table, tableParameters);
373+
372374
CreateAnnotations(
373375
table,
374376
Generate,
@@ -406,6 +408,8 @@ private string Create(
406408
Create(column, tableParameters);
407409
}
408410

411+
CreateJsonElements(table, tableParameters);
412+
409413
CreateAnnotations(
410414
table,
411415
Generate,
@@ -449,6 +453,8 @@ private string GetOrCreate(
449453
Create(column, viewParameters);
450454
}
451455

456+
CreateJsonElements(view, viewParameters);
457+
452458
CreateAnnotations(
453459
view,
454460
Generate,
@@ -469,6 +475,135 @@ private string GetOrCreate(
469475
public virtual void Generate(IView view, CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
470476
=> GenerateSimpleAnnotations(parameters);
471477

478+
private void CreateJsonElements(
479+
ITableBase table,
480+
CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
481+
{
482+
foreach (var column in table.Columns)
483+
{
484+
if (column.JsonElement == null)
485+
{
486+
continue;
487+
}
488+
489+
var code = Dependencies.CSharpHelper;
490+
var mainBuilder = parameters.MainBuilder;
491+
AddNamespace(typeof(RelationalJsonObject), parameters.Namespaces);
492+
AddNamespace(typeof(JsonValueType), parameters.Namespaces);
493+
494+
var columnVariable = parameters.ScopeVariables.TryGetValue(column, out var cv)
495+
? cv
496+
: $"{parameters.TargetName}.FindColumn({code.Literal(column.Name)})!";
497+
var elementVariable = CreateJsonElement(column.JsonElement, columnVariable, parameters);
498+
499+
mainBuilder.AppendLine($"{columnVariable}.JsonElement = {elementVariable};");
500+
}
501+
}
502+
503+
private string CreateJsonElement(
504+
IRelationalJsonElement element,
505+
string columnVariable,
506+
CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
507+
{
508+
var parentLiteral = element.ParentElement != null && parameters.ScopeVariables.TryGetValue(element.ParentElement, out var pv)
509+
? pv
510+
: "null";
511+
512+
return element switch
513+
{
514+
IRelationalJsonObject jsonObject => CreateJsonObject(jsonObject, columnVariable, parentLiteral, parameters),
515+
IRelationalJsonArray jsonArray => CreateJsonArray(jsonArray, columnVariable, parentLiteral, parameters),
516+
IRelationalJsonScalar jsonProperty => CreateJsonProperty(jsonProperty, columnVariable, parentLiteral, parameters),
517+
_ => throw new UnreachableException()
518+
};
519+
}
520+
521+
private string CreateJsonObject(
522+
IRelationalJsonObject jsonObject,
523+
string columnVariable,
524+
string parentLiteral,
525+
CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
526+
{
527+
var code = Dependencies.CSharpHelper;
528+
var mainBuilder = parameters.MainBuilder;
529+
var variable = code.Identifier((jsonObject.PropertyName ?? "element") + "JsonObject", jsonObject, parameters.ScopeObjects, capitalize: false);
530+
531+
mainBuilder.Append($"var {variable} = new RelationalJsonObject(");
532+
AppendJsonConstructorArgs(jsonObject, columnVariable, parentLiteral, mainBuilder, code);
533+
mainBuilder.AppendLine(");");
534+
535+
foreach (var child in jsonObject.Properties)
536+
{
537+
var childVariable = CreateJsonElement(child, columnVariable, parameters with { ScopeVariables = new Dictionary<object, string>(parameters.ScopeVariables) { [jsonObject] = variable } });
538+
mainBuilder.AppendLine($"{variable}.AddProperty({childVariable});");
539+
}
540+
541+
return variable;
542+
}
543+
544+
private string CreateJsonArray(
545+
IRelationalJsonArray jsonArray,
546+
string columnVariable,
547+
string parentLiteral,
548+
CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
549+
{
550+
var code = Dependencies.CSharpHelper;
551+
var mainBuilder = parameters.MainBuilder;
552+
553+
var variable = code.Identifier((jsonArray.PropertyName ?? "array") + "JsonArray", jsonArray, parameters.ScopeObjects, capitalize: false);
554+
555+
mainBuilder.Append($"var {variable} = new RelationalJsonArray(");
556+
AppendJsonConstructorArgs(jsonArray, columnVariable, parentLiteral, mainBuilder, code);
557+
mainBuilder.AppendLine(");");
558+
559+
var elementTypeVariable = CreateJsonElement(jsonArray.ElementType, columnVariable,
560+
parameters with { ScopeVariables = new Dictionary<object, string>(parameters.ScopeVariables) { [jsonArray] = variable } });
561+
mainBuilder.AppendLine($"{variable}.ElementType = {elementTypeVariable};");
562+
563+
return variable;
564+
}
565+
566+
private string CreateJsonProperty(
567+
IRelationalJsonScalar jsonProperty,
568+
string columnVariable,
569+
string parentLiteral,
570+
CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
571+
{
572+
var code = Dependencies.CSharpHelper;
573+
var mainBuilder = parameters.MainBuilder;
574+
var variable = code.Identifier((jsonProperty.PropertyName ?? "scalar") + "JsonScalar", jsonProperty, parameters.ScopeObjects, capitalize: false);
575+
576+
mainBuilder.Append($"var {variable} = new RelationalJsonScalar(");
577+
AppendJsonConstructorArgs(jsonProperty, columnVariable, parentLiteral, mainBuilder, code);
578+
mainBuilder.Append($", JsonValueType.{jsonProperty.ValueType})").AppendLine(";");
579+
580+
return variable;
581+
}
582+
583+
private static void AppendJsonConstructorArgs(
584+
IRelationalJsonElement element,
585+
string columnVariable,
586+
string parentLiteral,
587+
IndentedStringBuilder builder,
588+
ICSharpHelper code)
589+
{
590+
if (element.ParentElement is IRelationalJsonArray)
591+
{
592+
// (parent, isNullable) — array type
593+
builder.Append($"{parentLiteral}, {code.Literal(element.IsNullable)}");
594+
}
595+
else if (element.ParentElement is IRelationalJsonObject)
596+
{
597+
// (name, parent, isNullable) — object property
598+
builder.Append($"{code.Literal(element.PropertyName!)}, {parentLiteral}, {code.Literal(element.IsNullable)}");
599+
}
600+
else
601+
{
602+
// (column, isNullable) — root element
603+
builder.Append($"{columnVariable}, {code.Literal(element.IsNullable)}");
604+
}
605+
}
606+
472607
private string GetOrCreate(
473608
ISqlQuery sqlQuery,
474609
CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
@@ -2078,6 +2213,7 @@ public override void Generate(IProperty property, CSharpRuntimeAnnotationCodeGen
20782213
annotations.Remove(RelationalAnnotationNames.UpdateStoredProcedureParameterMappings);
20792214
annotations.Remove(RelationalAnnotationNames.UpdateStoredProcedureResultColumnMappings);
20802215
annotations.Remove(RelationalAnnotationNames.DefaultColumnMappings);
2216+
annotations.Remove(RelationalAnnotationNames.JsonElementMappings);
20812217
}
20822218
else
20832219
{
@@ -2110,6 +2246,40 @@ public override void Generate(IProperty property, CSharpRuntimeAnnotationCodeGen
21102246
base.Generate(property, parameters);
21112247
}
21122248

2249+
/// <inheritdoc />
2250+
public override void Generate(INavigation navigation, CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
2251+
{
2252+
if (parameters.IsRuntime)
2253+
{
2254+
var annotations = parameters.Annotations;
2255+
annotations.Remove(RelationalAnnotationNames.TableColumnMappings);
2256+
annotations.Remove(RelationalAnnotationNames.ViewColumnMappings);
2257+
annotations.Remove(RelationalAnnotationNames.SqlQueryColumnMappings);
2258+
annotations.Remove(RelationalAnnotationNames.FunctionColumnMappings);
2259+
annotations.Remove(RelationalAnnotationNames.DefaultColumnMappings);
2260+
annotations.Remove(RelationalAnnotationNames.JsonElementMappings);
2261+
}
2262+
2263+
base.Generate(navigation, parameters);
2264+
}
2265+
2266+
/// <inheritdoc />
2267+
public override void Generate(IComplexProperty complexProperty, CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
2268+
{
2269+
if (parameters.IsRuntime)
2270+
{
2271+
var annotations = parameters.Annotations;
2272+
annotations.Remove(RelationalAnnotationNames.TableColumnMappings);
2273+
annotations.Remove(RelationalAnnotationNames.ViewColumnMappings);
2274+
annotations.Remove(RelationalAnnotationNames.SqlQueryColumnMappings);
2275+
annotations.Remove(RelationalAnnotationNames.FunctionColumnMappings);
2276+
annotations.Remove(RelationalAnnotationNames.DefaultColumnMappings);
2277+
annotations.Remove(RelationalAnnotationNames.JsonElementMappings);
2278+
}
2279+
2280+
base.Generate(complexProperty, parameters);
2281+
}
2282+
21132283
private void Create(
21142284
IRelationalPropertyOverrides overrides,
21152285
string overridesVariable,

src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -551,42 +551,55 @@ public static void SetColumnType(this IMutableProperty property, string? value)
551551
/// <summary>
552552
/// Returns the default columns to which the property would be mapped.
553553
/// </summary>
554-
/// <param name="property">The property.</param>
554+
/// <param name="propertyBase">The property.</param>
555555
/// <returns>The default columns to which the property would be mapped.</returns>
556-
public static IEnumerable<IColumnMappingBase> GetDefaultColumnMappings(this IProperty property)
556+
public static IEnumerable<IColumnMappingBase> GetDefaultColumnMappings(this IPropertyBase propertyBase)
557557
{
558-
property.DeclaringType.Model.EnsureRelationalModel();
559-
return (IEnumerable<IColumnMappingBase>?)property.FindRuntimeAnnotationValue(
558+
propertyBase.DeclaringType.Model.EnsureRelationalModel();
559+
return (IEnumerable<IColumnMappingBase>?)propertyBase.FindRuntimeAnnotationValue(
560560
RelationalAnnotationNames.DefaultColumnMappings)
561561
?? [];
562562
}
563563

564564
/// <summary>
565565
/// Returns the table columns to which the property is mapped.
566566
/// </summary>
567-
/// <param name="property">The property.</param>
567+
/// <param name="propertyBase">The property.</param>
568568
/// <returns>The table columns to which the property is mapped.</returns>
569-
public static IEnumerable<IColumnMapping> GetTableColumnMappings(this IProperty property)
569+
public static IEnumerable<IColumnMapping> GetTableColumnMappings(this IPropertyBase propertyBase)
570570
{
571-
property.DeclaringType.Model.EnsureRelationalModel();
572-
return (IEnumerable<IColumnMapping>?)property.FindRuntimeAnnotationValue(
571+
propertyBase.DeclaringType.Model.EnsureRelationalModel();
572+
return (IEnumerable<IColumnMapping>?)propertyBase.FindRuntimeAnnotationValue(
573573
RelationalAnnotationNames.TableColumnMappings)
574574
?? [];
575575
}
576576

577577
/// <summary>
578578
/// Returns the view columns to which the property is mapped.
579579
/// </summary>
580-
/// <param name="property">The property.</param>
580+
/// <param name="propertyBase">The property.</param>
581581
/// <returns>The view columns to which the property is mapped.</returns>
582-
public static IEnumerable<IViewColumnMapping> GetViewColumnMappings(this IProperty property)
582+
public static IEnumerable<IViewColumnMapping> GetViewColumnMappings(this IPropertyBase propertyBase)
583583
{
584-
property.DeclaringType.Model.EnsureRelationalModel();
585-
return (IEnumerable<IViewColumnMapping>?)property.FindRuntimeAnnotationValue(
584+
propertyBase.DeclaringType.Model.EnsureRelationalModel();
585+
return (IEnumerable<IViewColumnMapping>?)propertyBase.FindRuntimeAnnotationValue(
586586
RelationalAnnotationNames.ViewColumnMappings)
587587
?? [];
588588
}
589589

590+
/// <summary>
591+
/// Returns the JSON element mappings for this property.
592+
/// </summary>
593+
/// <param name="propertyBase">The property.</param>
594+
/// <returns>The JSON element mappings for this property.</returns>
595+
public static IEnumerable<IJsonElementMapping> GetJsonElementMappings(this IPropertyBase propertyBase)
596+
{
597+
propertyBase.DeclaringType.Model.EnsureRelationalModel();
598+
return (IEnumerable<IJsonElementMapping>?)propertyBase.FindRuntimeAnnotationValue(
599+
RelationalAnnotationNames.JsonElementMappings)
600+
?? [];
601+
}
602+
590603
/// <summary>
591604
/// Returns the SQL query columns to which the property is mapped.
592605
/// </summary>

src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,7 @@ protected override void ProcessPropertyAnnotations(
393393
annotations.Remove(RelationalAnnotationNames.UpdateStoredProcedureParameterMappings);
394394
annotations.Remove(RelationalAnnotationNames.UpdateStoredProcedureResultColumnMappings);
395395
annotations.Remove(RelationalAnnotationNames.DefaultColumnMappings);
396+
annotations.Remove(RelationalAnnotationNames.JsonElementMappings);
396397
}
397398
else
398399
{
@@ -430,6 +431,46 @@ private static RuntimeRelationalPropertyOverrides Create(
430431
propertyOverrides.IsColumnNameOverridden,
431432
propertyOverrides.ColumnName);
432433

434+
/// <inheritdoc />
435+
protected override void ProcessNavigationAnnotations(
436+
Dictionary<string, object?> annotations,
437+
INavigation navigation,
438+
RuntimeNavigation runtimeNavigation,
439+
bool runtime)
440+
{
441+
base.ProcessNavigationAnnotations(annotations, navigation, runtimeNavigation, runtime);
442+
443+
if (runtime)
444+
{
445+
annotations.Remove(RelationalAnnotationNames.TableColumnMappings);
446+
annotations.Remove(RelationalAnnotationNames.ViewColumnMappings);
447+
annotations.Remove(RelationalAnnotationNames.SqlQueryColumnMappings);
448+
annotations.Remove(RelationalAnnotationNames.FunctionColumnMappings);
449+
annotations.Remove(RelationalAnnotationNames.DefaultColumnMappings);
450+
annotations.Remove(RelationalAnnotationNames.JsonElementMappings);
451+
}
452+
}
453+
454+
/// <inheritdoc />
455+
protected override void ProcessComplexPropertyAnnotations(
456+
Dictionary<string, object?> annotations,
457+
IComplexProperty complexProperty,
458+
RuntimeComplexProperty runtimeComplexProperty,
459+
bool runtime)
460+
{
461+
base.ProcessComplexPropertyAnnotations(annotations, complexProperty, runtimeComplexProperty, runtime);
462+
463+
if (runtime)
464+
{
465+
annotations.Remove(RelationalAnnotationNames.TableColumnMappings);
466+
annotations.Remove(RelationalAnnotationNames.ViewColumnMappings);
467+
annotations.Remove(RelationalAnnotationNames.SqlQueryColumnMappings);
468+
annotations.Remove(RelationalAnnotationNames.FunctionColumnMappings);
469+
annotations.Remove(RelationalAnnotationNames.DefaultColumnMappings);
470+
annotations.Remove(RelationalAnnotationNames.JsonElementMappings);
471+
}
472+
}
473+
433474
/// <summary>
434475
/// Updates the relational property overrides annotations that will be set on the read-only object.
435476
/// </summary>

src/EFCore.Relational/Metadata/IColumnBase.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ public interface IColumnBase : IAnnotatable
4848
/// </summary>
4949
IReadOnlyList<IColumnMappingBase> PropertyMappings { get; }
5050

51+
/// <summary>
52+
/// Gets the root JSON element for this column, or <see langword="null" /> if the column is not a JSON column.
53+
/// </summary>
54+
IRelationalJsonElement? JsonElement => null;
55+
5156
/// <summary>
5257
/// Gets the <see cref="ValueComparer" /> for this column.
5358
/// </summary>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.EntityFrameworkCore.Metadata;
5+
6+
/// <summary>
7+
/// Represents a mapping between a model property and a JSON element in the relational model.
8+
/// </summary>
9+
/// <remarks>
10+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see> for more information and examples.
11+
/// </remarks>
12+
public interface IJsonElementMapping
13+
{
14+
/// <summary>
15+
/// Gets the model property that is mapped to the JSON element.
16+
/// </summary>
17+
IPropertyBase Property { get; }
18+
19+
/// <summary>
20+
/// Gets the JSON element in the relational model.
21+
/// </summary>
22+
IRelationalJsonElement Element { get; }
23+
24+
/// <summary>
25+
/// Gets the table mapping that contains this JSON element mapping.
26+
/// </summary>
27+
ITableMappingBase TableMapping { get; }
28+
}

0 commit comments

Comments
 (0)