Skip to content

Commit 9166ddc

Browse files
committed
Support JSON columns in compiled models
Fixes #29602 The main change here is to store the JSON type mapping in the relational model, and obsolete the previous storage in the underlying EF model.
1 parent 8664081 commit 9166ddc

File tree

13 files changed

+1358
-57
lines changed

13 files changed

+1358
-57
lines changed

src/EFCore.Relational/Design/AnnotationCodeGenerator.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ public class AnnotationCodeGenerator : IAnnotationCodeGenerator
3535
RelationalAnnotationNames.UpdateStoredProcedure,
3636
RelationalAnnotationNames.MappingFragments,
3737
RelationalAnnotationNames.RelationalOverrides,
38+
#pragma warning disable CS0618
3839
RelationalAnnotationNames.ContainerColumnTypeMapping
40+
#pragma warning restore CS0618
3941
};
4042

4143
/// <summary>
@@ -225,7 +227,9 @@ public virtual IReadOnlyList<MethodCallCodeFragment> GenerateFluentApiCalls(
225227
containerColumnName));
226228

227229
annotations.Remove(RelationalAnnotationNames.ContainerColumnName);
230+
#pragma warning disable CS0618
228231
annotations.Remove(RelationalAnnotationNames.ContainerColumnTypeMapping);
232+
#pragma warning restore CS0618
229233
}
230234

231235
methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(entityType, annotations, GenerateFluentApi));

src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1682,6 +1682,7 @@ public static void SetContainerColumnName(this IMutableEntityType entityType, st
16821682
/// </summary>
16831683
/// <param name="entityType">The entity type to set the container column type mapping for.</param>
16841684
/// <param name="typeMapping">The type mapping to set.</param>
1685+
[Obsolete("Container column mappings are now obtained from IColumnBase.StoreTypeMapping")]
16851686
public static void SetContainerColumnTypeMapping(this IMutableEntityType entityType, RelationalTypeMapping typeMapping)
16861687
=> entityType.SetOrRemoveAnnotation(RelationalAnnotationNames.ContainerColumnTypeMapping, typeMapping);
16871688

@@ -1692,6 +1693,7 @@ public static void SetContainerColumnTypeMapping(this IMutableEntityType entityT
16921693
/// <param name="typeMapping">The type mapping to set.</param>
16931694
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
16941695
/// <returns>The configured value.</returns>
1696+
[Obsolete("Container column mappings are now obtained from IColumnBase.StoreTypeMapping")]
16951697
public static RelationalTypeMapping? SetContainerColumnTypeMapping(
16961698
this IConventionEntityType entityType,
16971699
RelationalTypeMapping? typeMapping,
@@ -1704,6 +1706,7 @@ public static void SetContainerColumnTypeMapping(this IMutableEntityType entityT
17041706
/// </summary>
17051707
/// <param name="entityType">The entity type to set the container column type mapping for.</param>
17061708
/// <returns>The <see cref="ConfigurationSource" /> for the container column type mapping.</returns>
1709+
[Obsolete("Container column mappings are now obtained from IColumnBase.StoreTypeMapping")]
17071710
public static ConfigurationSource? GetContainerColumnTypeMappingConfigurationSource(this IConventionEntityType entityType)
17081711
=> entityType.FindAnnotation(RelationalAnnotationNames.ContainerColumnTypeMapping)
17091712
?.GetConfigurationSource();
@@ -1713,6 +1716,7 @@ public static void SetContainerColumnTypeMapping(this IMutableEntityType entityT
17131716
/// </summary>
17141717
/// <param name="entityType">The entity type to get the container column type mapping for.</param>
17151718
/// <returns>The container column type mapping to which the entity type is mapped.</returns>
1719+
[Obsolete("Container column mappings are now obtained from IColumnBase.StoreTypeMapping")]
17161720
public static RelationalTypeMapping? GetContainerColumnTypeMapping(this IReadOnlyEntityType entityType)
17171721
=> entityType.FindAnnotation(RelationalAnnotationNames.ContainerColumnTypeMapping)?.Value is RelationalTypeMapping typeMapping
17181722
? typeMapping

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

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,30 +37,14 @@ public RelationalMapToJsonConvention(
3737
protected virtual RelationalConventionSetBuilderDependencies RelationalDependencies { get; }
3838

3939
/// <inheritdoc />
40+
[Obsolete("Container column mappings are now obtained from IColumnBase.StoreTypeMapping")]
4041
public virtual void ProcessEntityTypeAnnotationChanged(
4142
IConventionEntityTypeBuilder entityTypeBuilder,
4243
string name,
4344
IConventionAnnotation? annotation,
4445
IConventionAnnotation? oldAnnotation,
4546
IConventionContext<IConventionAnnotation> context)
4647
{
47-
if (name != RelationalAnnotationNames.ContainerColumnName)
48-
{
49-
return;
50-
}
51-
52-
var jsonColumnName = annotation?.Value as string;
53-
if (!string.IsNullOrEmpty(jsonColumnName))
54-
{
55-
var jsonColumnTypeMapping = ((IRelationalTypeMappingSource)Dependencies.TypeMappingSource).FindMapping(
56-
typeof(JsonElement))!;
57-
58-
entityTypeBuilder.Metadata.SetContainerColumnTypeMapping(jsonColumnTypeMapping);
59-
}
60-
else
61-
{
62-
entityTypeBuilder.Metadata.SetContainerColumnTypeMapping(null);
63-
}
6448
}
6549

6650
/// <inheritdoc />

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,26 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
1111
/// </summary>
1212
public class JsonColumn : Column, IColumn
1313
{
14-
private readonly ValueComparer _providerValueComparer;
15-
1614
/// <summary>
1715
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
1816
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
1917
/// any release. You should only use it directly in your code with extreme caution and knowing that
2018
/// doing so can result in application failures when updating to a new Entity Framework Core release.
2119
/// </summary>
22-
public JsonColumn(string name, string type, Table table, ValueComparer provierValueComparer)
23-
: base(name, type, table)
20+
public JsonColumn(string name, Table table, RelationalTypeMapping typeMapping)
21+
: base(name, typeMapping.StoreType, table)
2422
{
25-
_providerValueComparer = provierValueComparer;
23+
StoreTypeMapping = typeMapping;
2624
}
2725

26+
/// <summary>
27+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
28+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
29+
/// any release. You should only use it directly in your code with extreme caution and knowing that
30+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
31+
/// </summary>
32+
public override RelationalTypeMapping StoreTypeMapping { get; }
33+
2834
/// <summary>
2935
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
3036
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -149,7 +155,7 @@ bool IColumn.IsRowVersion
149155
/// doing so can result in application failures when updating to a new Entity Framework Core release.
150156
/// </summary>
151157
ValueComparer IColumnBase.ProviderValueComparer
152-
=> _providerValueComparer;
158+
=> StoreTypeMapping.ProviderValueComparer;
153159

154160
/// <summary>
155161
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,17 @@ public class JsonViewColumn : ViewColumn, IViewColumn
1717
/// any release. You should only use it directly in your code with extreme caution and knowing that
1818
/// doing so can result in application failures when updating to a new Entity Framework Core release.
1919
/// </summary>
20-
public JsonViewColumn(string name, string type, View view)
21-
: base(name, type, view)
20+
public JsonViewColumn(string name, View view, RelationalTypeMapping typeMapping)
21+
: base(name, typeMapping.StoreType, view)
2222
{
23+
StoreTypeMapping = typeMapping;
2324
}
25+
26+
/// <summary>
27+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
28+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
29+
/// any release. You should only use it directly in your code with extreme caution and knowing that
30+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
31+
/// </summary>
32+
public override RelationalTypeMapping StoreTypeMapping { get; }
2433
}

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -466,8 +466,7 @@ private static void CreateTableMapping(
466466
Debug.Assert(table.FindColumn(containerColumnName) == null);
467467

468468
var jsonColumnTypeMapping = relationalTypeMappingSource.FindMapping(typeof(JsonElement))!;
469-
var jsonColumn = new JsonColumn(
470-
containerColumnName, jsonColumnTypeMapping.StoreType, table, jsonColumnTypeMapping.ProviderValueComparer);
469+
var jsonColumn = new JsonColumn(containerColumnName, table, jsonColumnTypeMapping);
471470
table.Columns.Add(containerColumnName, jsonColumn);
472471
jsonColumn.IsNullable = !ownership.IsRequiredDependent || !ownership.IsUnique;
473472

@@ -624,7 +623,7 @@ private static void CreateViewMapping(
624623
Debug.Assert(view.FindColumn(containerColumnName) == null);
625624

626625
var jsonColumnTypeMapping = relationalTypeMappingSource.FindMapping(typeof(JsonElement))!;
627-
var jsonColumn = new JsonViewColumn(containerColumnName, jsonColumnTypeMapping.StoreType, view);
626+
var jsonColumn = new JsonViewColumn(containerColumnName, view, jsonColumnTypeMapping);
628627
view.Columns.Add(containerColumnName, jsonColumn);
629628
jsonColumn.IsNullable = !ownership.IsRequired || !ownership.IsUnique;
630629

src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ public static class RelationalAnnotationNames
322322
/// <summary>
323323
/// The name for the annotation specifying container column type mapping.
324324
/// </summary>
325+
[Obsolete("Container column mappings are now obtained from IColumnBase.StoreTypeMapping")]
325326
public const string ContainerColumnTypeMapping = Prefix + "ContainerColumnTypeMapping";
326327

327328
/// <summary>

src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Text.Json;
77
using System.Text.Json.Nodes;
88
using Microsoft.EntityFrameworkCore.Internal;
9+
using Microsoft.EntityFrameworkCore.Metadata.Internal;
910
using Microsoft.EntityFrameworkCore.Query.Internal;
1011
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
1112

@@ -1321,12 +1322,17 @@ private Expression CreateJsonShapers(
13211322

13221323
if (index == 0)
13231324
{
1325+
var jsonColumnName = entityType.GetContainerColumnName()!;
1326+
var jsonColumnTypeMapping = (entityType.GetViewOrTableMappings().SingleOrDefault()?.Table
1327+
?? entityType.GetDefaultMappings().Single().Table)
1328+
.FindColumn(jsonColumnName)!.StoreTypeMapping;
1329+
13241330
// create the JsonElement for the initial entity
13251331
var jsonElementValueExpression = CreateGetValueExpression(
13261332
_dataReaderParameter,
13271333
jsonProjectionInfo.JsonColumnIndex,
13281334
nullable: true,
1329-
entityType.GetContainerColumnTypeMapping()!,
1335+
jsonColumnTypeMapping,
13301336
typeof(JsonElement?),
13311337
property: null);
13321338

src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,9 @@ private void AddJsonNavigationBindings(
504504
{
505505
var targetEntityType = ownedJsonNavigation.TargetEntityType;
506506
var jsonColumnName = targetEntityType.GetContainerColumnName()!;
507-
var jsonColumnTypeMapping = targetEntityType.GetContainerColumnTypeMapping()!;
507+
var jsonColumnTypeMapping = (entityType.GetViewOrTableMappings().SingleOrDefault()?.Table
508+
?? entityType.GetDefaultMappings().Single().Table)
509+
.FindColumn(jsonColumnName)!.StoreTypeMapping;
508510

509511
var jsonColumn = new ConcreteColumnExpression(
510512
jsonColumnName,

src/EFCore.Relational/Update/ModificationCommand.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -294,11 +294,11 @@ private List<IColumnModification> GenerateColumnModifications()
294294

295295
if (jsonEntry)
296296
{
297-
var jsonColumnsUpdateMap = new Dictionary<string, JsonPartialUpdateInfo>();
297+
var jsonColumnsUpdateMap = new Dictionary<IColumn, JsonPartialUpdateInfo>();
298298
var processedEntries = new List<IUpdateEntry>();
299299
foreach (var entry in _entries.Where(e => e.EntityType.IsMappedToJson()))
300300
{
301-
var jsonColumn = entry.EntityType.GetContainerColumnName()!;
301+
var jsonColumn = GetTableMapping(entry.EntityType)!.Table.FindColumn(entry.EntityType.GetContainerColumnName()!)!;
302302
var jsonPartialUpdateInfo = FindJsonPartialUpdateInfo(entry, processedEntries);
303303

304304
if (jsonPartialUpdateInfo == null)
@@ -316,12 +316,11 @@ private List<IColumnModification> GenerateColumnModifications()
316316
jsonColumnsUpdateMap[jsonColumn] = jsonPartialUpdateInfo;
317317
}
318318

319-
foreach (var (jsonColumnName, updateInfo) in jsonColumnsUpdateMap)
319+
foreach (var (jsonColumn, updateInfo) in jsonColumnsUpdateMap)
320320
{
321321
var finalUpdatePathElement = updateInfo.Path.Last();
322322
var navigation = finalUpdatePathElement.Navigation;
323-
324-
var jsonColumnTypeMapping = navigation.TargetEntityType.GetContainerColumnTypeMapping()!;
323+
var jsonColumnTypeMapping = jsonColumn.StoreTypeMapping;
325324
var navigationValue = finalUpdatePathElement.ParentEntry.GetCurrentValue(navigation);
326325

327326
var json = default(JsonNode?);
@@ -367,7 +366,7 @@ private List<IColumnModification> GenerateColumnModifications()
367366
}
368367

369368
var columnModificationParameters = new ColumnModificationParameters(
370-
jsonColumnName,
369+
jsonColumn.Name,
371370
value: json?.ToJsonString(),
372371
property: updateInfo.Property,
373372
columnType: jsonColumnTypeMapping.StoreType,

0 commit comments

Comments
 (0)