Skip to content

Commit ce9f999

Browse files
committed
Fix to #32192 - Remove lookup of JsonTypeMapping via JsonElement
When adding support for JSON mapped types, we decided to use JsonElement as a CLR type marker for JsonTypeMapping. This decision was in hindsight incorrect, it makes it hard for providers that actually support weak-typed json (npgsql) and throws cryptic exception (No coercion operator is defined between types 'System.IO.MemoryStream' and 'System.Text.Json.JsonElement) for providers that don't support it, when customers try to do it regardless. Fix is to create a dummy type and use that instead, so JsonElement is no longer recognized by base EFCore. Fixes #32192 also fixes #34752
1 parent b0da783 commit ce9f999

File tree

13 files changed

+52
-17
lines changed

13 files changed

+52
-17
lines changed

Diff for: src/EFCore.Relational/Metadata/Internal/JsonColumn.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public JsonColumn(
3636
/// doing so can result in application failures when updating to a new Entity Framework Core release.
3737
/// </summary>
3838
protected override RelationalTypeMapping GetDefaultStoreTypeMapping()
39-
=> (RelationalTypeMapping)Table.Model.Model.GetModelDependencies().TypeMappingSource.FindMapping(typeof(JsonElement))!;
39+
=> (RelationalTypeMapping)Table.Model.Model.GetModelDependencies().TypeMappingSource.FindMapping(typeof(JsonClrType))!;
4040

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

Diff for: src/EFCore.Relational/Metadata/Internal/JsonColumnBase.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,5 @@ public JsonColumnBase(
3636
/// doing so can result in application failures when updating to a new Entity Framework Core release.
3737
/// </summary>
3838
protected override RelationalTypeMapping GetDefaultStoreTypeMapping()
39-
=> (RelationalTypeMapping)Table.Model.Model.GetModelDependencies().TypeMappingSource.FindMapping(typeof(JsonElement))!;
39+
=> (RelationalTypeMapping)Table.Model.Model.GetModelDependencies().TypeMappingSource.FindMapping(typeof(JsonClrType))!;
4040
}

Diff for: src/EFCore.Relational/Metadata/Internal/JsonViewColumn.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,5 @@ public JsonViewColumn(
3636
/// doing so can result in application failures when updating to a new Entity Framework Core release.
3737
/// </summary>
3838
protected override RelationalTypeMapping GetDefaultStoreTypeMapping()
39-
=> (RelationalTypeMapping)Table.Model.Model.GetModelDependencies().TypeMappingSource.FindMapping(typeof(JsonElement))!;
39+
=> (RelationalTypeMapping)Table.Model.Model.GetModelDependencies().TypeMappingSource.FindMapping(typeof(JsonClrType))!;
4040
}

Diff for: src/EFCore.Relational/Metadata/Internal/RelationalModel.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,7 @@ private static void CreateContainerColumn<TColumnMappingBase>(
582582
{
583583
Check.DebugAssert(tableBase.FindColumn(containerColumnName) == null, $"Table does not have column '{containerColumnName}'.");
584584

585-
var jsonColumnTypeMapping = relationalTypeMappingSource.FindMapping(typeof(JsonElement), storeTypeName: containerColumnType)!;
585+
var jsonColumnTypeMapping = relationalTypeMappingSource.FindMapping(typeof(JsonClrType), storeTypeName: containerColumnType)!;
586586
var jsonColumn = createColumn(containerColumnName, containerColumnType, tableBase, jsonColumnTypeMapping);
587587
tableBase.Columns.Add(containerColumnName, jsonColumn);
588588
jsonColumn.IsNullable = !ownership.IsRequiredDependent || !ownership.IsUnique;

Diff for: src/EFCore.Relational/Metadata/JsonClrType.cs

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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+
/// A type representing CLR type of the JsonTypeMapping.
8+
/// </summary>
9+
public class JsonClrType
10+
{
11+
}

Diff for: src/EFCore.Relational/Storage/JsonTypeMapping.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Data;
5-
using System.Text.Json;
65

76
namespace Microsoft.EntityFrameworkCore.Storage;
87

98
/// <summary>
109
/// <para>
11-
/// Represents the mapping between a <see cref="JsonElement" /> type and a database type.
10+
/// Represents the mapping between a JSON object and a database type.
1211
/// </para>
1312
/// <para>
1413
/// This type is typically used by database providers (and other extensions). It is generally

Diff for: src/EFCore.SqlServer/Storage/Internal/SqlServerOwnedJsonTypeMapping.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ private static readonly MethodInfo GetStringMethod
4545
/// doing so can result in application failures when updating to a new Entity Framework Core release.
4646
/// </summary>
4747
public SqlServerOwnedJsonTypeMapping(string storeType)
48-
: base(storeType, typeof(JsonElement), System.Data.DbType.String)
48+
: base(storeType, typeof(JsonClrType), System.Data.DbType.String)
4949
{
5050
}
5151

Diff for: src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System.Collections;
55
using System.Data;
6-
using System.Text.Json;
76

87
namespace Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
98

@@ -239,7 +238,7 @@ public SqlServerTypeMappingSource(
239238
var clrType = mappingInfo.ClrType;
240239
var storeTypeName = mappingInfo.StoreTypeName;
241240

242-
if (clrType == typeof(JsonElement))
241+
if (clrType == typeof(JsonClrType))
243242
{
244243
return storeTypeName == "json"
245244
? SqlServerOwnedJsonTypeMapping.OwnedJsonTypeDefault

Diff for: src/EFCore.Sqlite.Core/Storage/Internal/SqliteJsonTypeMapping.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ private static readonly ConstructorInfo MemoryStreamConstructor
3939
/// </summary>
4040
/// <param name="storeType">The name of the database type.</param>
4141
public SqliteJsonTypeMapping(string storeType)
42-
: base(storeType, typeof(JsonElement), System.Data.DbType.String)
42+
: base(storeType, typeof(JsonClrType), System.Data.DbType.String)
4343
{
4444
}
4545

Diff for: src/EFCore.Sqlite.Core/Storage/Internal/SqliteTypeMappingSource.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ private static readonly HashSet<string> SpatialiteTypes
8383
{ typeof(double), Real },
8484
{ typeof(float), new FloatTypeMapping(RealTypeName) },
8585
{ typeof(Guid), SqliteGuidTypeMapping.Default },
86-
{ typeof(JsonElement), SqliteJsonTypeMapping.Default }
86+
{ typeof(JsonClrType), SqliteJsonTypeMapping.Default }
8787
};
8888

8989
private readonly Dictionary<string, RelationalTypeMapping> _storeTypeMappings = new(StringComparer.OrdinalIgnoreCase)

Diff for: test/EFCore.Relational.Specification.Tests/Query/AdHocMiscellaneousQueryRelationalTestBase.cs

+28
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#nullable disable
55

66
using System.ComponentModel.DataAnnotations.Schema;
7+
using System.Text.Json;
78
using NameSpace1;
89

910
namespace Microsoft.EntityFrameworkCore.Query
@@ -239,6 +240,32 @@ public static DateTime Modify(DateTime date)
239240

240241
#endregion
241242

243+
#region 34752
244+
245+
[ConditionalFact]
246+
public virtual async Task Mapping_JsonElement_property_throws_a_meaningful_exception()
247+
{
248+
var message = (await Assert.ThrowsAsync<InvalidOperationException>(
249+
() => InitializeAsync<Context34752>())).Message;
250+
251+
Assert.Equal(
252+
CoreStrings.PropertyNotAdded(nameof(Context34752.Entity), nameof(Context34752.Entity.Json), nameof(JsonElement)),
253+
message);
254+
}
255+
256+
protected class Context34752(DbContextOptions options) : DbContext(options)
257+
{
258+
public DbSet<Entity> Entities { get; set; }
259+
260+
public class Entity
261+
{
262+
public int Id { get; set; }
263+
public JsonElement Json { get; set; }
264+
}
265+
}
266+
267+
#endregion
268+
242269
#region Inlined redacting
243270

244271
protected abstract DbContextOptionsBuilder SetTranslateParameterizedCollectionsToConstants(DbContextOptionsBuilder optionsBuilder);
@@ -289,6 +316,7 @@ public class TestEntity
289316
public static readonly IEnumerable<object[]> InlinedRedactingData = [[true, true], [true, false], [false, true], [false, false]];
290317

291318
#endregion
319+
292320
}
293321
}
294322

Diff for: test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/DbContextModelBuilder.cs

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// <auto-generated />
22
using System;
33
using System.Collections.Generic;
4-
using System.Text.Json;
54
using Microsoft.EntityFrameworkCore;
65
using Microsoft.EntityFrameworkCore.Infrastructure;
76
using Microsoft.EntityFrameworkCore.Metadata;
@@ -2258,10 +2257,10 @@ private IRelationalModel CreateRelationalModel()
22582257
IsNullable = true
22592258
};
22602259
principalBaseTable.Columns.Add("ManyOwned", manyOwnedColumn);
2261-
manyOwnedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric<JsonElement>(manyOwnedColumn);
2260+
manyOwnedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric<JsonClrType>(manyOwnedColumn);
22622261
var ownedColumn = new JsonColumn("Owned", "nvarchar(max)", principalBaseTable);
22632262
principalBaseTable.Columns.Add("Owned", ownedColumn);
2264-
ownedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric<JsonElement>(ownedColumn);
2263+
ownedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric<JsonClrType>(ownedColumn);
22652264
var refTypeArrayColumn = new Column("RefTypeArray", "nvarchar(max)", principalBaseTable)
22662265
{
22672266
IsNullable = true

Diff for: test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/DbContextModelBuilder.cs

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// <auto-generated />
22
using System;
33
using System.Collections.Generic;
4-
using System.Text.Json;
54
using Microsoft.EntityFrameworkCore;
65
using Microsoft.EntityFrameworkCore.Infrastructure;
76
using Microsoft.EntityFrameworkCore.Metadata;
@@ -2325,10 +2324,10 @@ private IRelationalModel CreateRelationalModel()
23252324
IsNullable = true
23262325
};
23272326
principalBaseTable.Columns.Add("ManyOwned", manyOwnedColumn);
2328-
manyOwnedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric<JsonElement>(manyOwnedColumn);
2327+
manyOwnedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric<JsonClrType>(manyOwnedColumn);
23292328
var ownedColumn = new JsonColumn("Owned", "TEXT", principalBaseTable);
23302329
principalBaseTable.Columns.Add("Owned", ownedColumn);
2331-
ownedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric<JsonElement>(ownedColumn);
2330+
ownedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric<JsonClrType>(ownedColumn);
23322331
var pointColumn0 = new Column("Point", "geometry", principalBaseTable)
23332332
{
23342333
IsNullable = true

0 commit comments

Comments
 (0)