Skip to content

Commit f94e421

Browse files
authored
Fix to #32192 - Remove lookup of JsonTypeMapping via JsonElement (#35788)
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 64512be commit f94e421

File tree

13 files changed

+56
-17
lines changed

13 files changed

+56
-17
lines changed

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(JsonTypePlaceholder))!;
4040

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

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(JsonTypePlaceholder))!;
4040
}

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(JsonTypePlaceholder))!;
4040
}

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(JsonTypePlaceholder), storeTypeName: containerColumnType)!;
586586
var jsonColumn = createColumn(containerColumnName, containerColumnType, tableBase, jsonColumnTypeMapping);
587587
tableBase.Columns.Add(containerColumnName, jsonColumn);
588588
jsonColumn.IsNullable = !ownership.IsRequiredDependent || !ownership.IsUnique;

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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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.Storage;
5+
6+
/// <summary>
7+
/// A type representing CLR type of the JsonTypeMapping.
8+
/// </summary>
9+
public sealed class JsonTypePlaceholder
10+
{
11+
private JsonTypePlaceholder()
12+
{
13+
}
14+
}

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(JsonTypePlaceholder), System.Data.DbType.String)
4949
{
5050
}
5151

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(JsonTypePlaceholder))
243242
{
244243
return storeTypeName == "json"
245244
? SqlServerOwnedJsonTypeMapping.OwnedJsonTypeDefault

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(JsonTypePlaceholder), System.Data.DbType.String)
4343
{
4444
}
4545

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(JsonTypePlaceholder), SqliteJsonTypeMapping.Default }
8787
};
8888

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

test/EFCore.Relational.Specification.Tests/Query/AdHocMiscellaneousQueryRelationalTestBase.cs

+27
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);

test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/DbContextModelBuilder.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
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;
87
using Microsoft.EntityFrameworkCore.Metadata.Internal;
98
using Microsoft.EntityFrameworkCore.Migrations;
9+
using Microsoft.EntityFrameworkCore.Storage;
1010
using Microsoft.EntityFrameworkCore.Update.Internal;
1111

1212
#pragma warning disable 219, 612, 618
@@ -2258,10 +2258,10 @@ private IRelationalModel CreateRelationalModel()
22582258
IsNullable = true
22592259
};
22602260
principalBaseTable.Columns.Add("ManyOwned", manyOwnedColumn);
2261-
manyOwnedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric<JsonElement>(manyOwnedColumn);
2261+
manyOwnedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric<JsonTypePlaceholder>(manyOwnedColumn);
22622262
var ownedColumn = new JsonColumn("Owned", "nvarchar(max)", principalBaseTable);
22632263
principalBaseTable.Columns.Add("Owned", ownedColumn);
2264-
ownedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric<JsonElement>(ownedColumn);
2264+
ownedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric<JsonTypePlaceholder>(ownedColumn);
22652265
var refTypeArrayColumn = new Column("RefTypeArray", "nvarchar(max)", principalBaseTable)
22662266
{
22672267
IsNullable = true

test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/DbContextModelBuilder.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
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;
87
using Microsoft.EntityFrameworkCore.Metadata.Internal;
98
using Microsoft.EntityFrameworkCore.Migrations;
9+
using Microsoft.EntityFrameworkCore.Storage;
1010
using Microsoft.EntityFrameworkCore.Update.Internal;
1111
using NetTopologySuite.Geometries;
1212

@@ -2325,10 +2325,10 @@ private IRelationalModel CreateRelationalModel()
23252325
IsNullable = true
23262326
};
23272327
principalBaseTable.Columns.Add("ManyOwned", manyOwnedColumn);
2328-
manyOwnedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric<JsonElement>(manyOwnedColumn);
2328+
manyOwnedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric<JsonTypePlaceholder>(manyOwnedColumn);
23292329
var ownedColumn = new JsonColumn("Owned", "TEXT", principalBaseTable);
23302330
principalBaseTable.Columns.Add("Owned", ownedColumn);
2331-
ownedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric<JsonElement>(ownedColumn);
2331+
ownedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric<JsonTypePlaceholder>(ownedColumn);
23322332
var pointColumn0 = new Column("Point", "geometry", principalBaseTable)
23332333
{
23342334
IsNullable = true

0 commit comments

Comments
 (0)