From ad72a529e52c148511e7e89cb4e19b9db2ef757d Mon Sep 17 00:00:00 2001 From: Jan Trejbal Date: Mon, 8 Jan 2024 16:30:49 +0100 Subject: [PATCH 1/4] Add GenerateSqlLiteral_returns_json_object_literal_customJsonOptions --- .../MySqlJsonMicrosoftTypeMappingTest.cs | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/test/EFCore.MySql.FunctionalTests/Storage/MySqlJsonMicrosoftTypeMappingTest.cs b/test/EFCore.MySql.FunctionalTests/Storage/MySqlJsonMicrosoftTypeMappingTest.cs index bb665cd89..faca3d674 100644 --- a/test/EFCore.MySql.FunctionalTests/Storage/MySqlJsonMicrosoftTypeMappingTest.cs +++ b/test/EFCore.MySql.FunctionalTests/Storage/MySqlJsonMicrosoftTypeMappingTest.cs @@ -1,10 +1,13 @@ using System; using System.Text.Json; +using System.Text.Json.Serialization; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.Json; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Pomelo.EntityFrameworkCore.MySql.Internal; +using Pomelo.EntityFrameworkCore.MySql.Json.Microsoft.Infrastructure; +using Pomelo.EntityFrameworkCore.MySql.Json.Microsoft.Infrastructure.Internal; using Pomelo.EntityFrameworkCore.MySql.Json.Microsoft.Storage.Internal; using Pomelo.EntityFrameworkCore.MySql.Storage.Internal; using Xunit; @@ -29,6 +32,36 @@ public void GenerateSqlLiteral_returns_json_object_literal() @"]}'", literal); } + [Fact] + public void GenerateSqlLiteral_returns_json_object_literal_customJsonOptions() + { + var jsonSerializerOptions = new JsonSerializerOptions(); + jsonSerializerOptions.Converters.Add(new BoolJsonConverter()); + + var literal = CreateMySqlTypeMappingSource(new DefaultMysqlJsonOptions { JsonSerializerOptions = jsonSerializerOptions }) + .FindMapping(typeof(Customer), "json").GenerateSqlLiteral(SampleCustomer); + Assert.Equal( + """ + '{"Name":"Joe","Age":25,"IsVip":0,"Orders":[{"Price":99.5,"ShippingAddress":"Some address 1","ShippingDate":"2019-10-01T00:00:00"},{"Price":23.1,"ShippingAddress":"Some address 2","ShippingDate":"2019-10-10T00:00:00"}]}' + """, + literal); + + } + + /// + /// POC converter, verify that custom JsonSerializerOptions is being used + /// + private sealed class BoolJsonConverter : JsonConverter + { + public override bool Read( + ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options + ) => reader.TokenType is JsonTokenType.Number && reader.GetInt32() == 1; + + public override void Write( + Utf8JsonWriter writer, bool value, JsonSerializerOptions options + ) => writer.WriteNumberValue(value ? 1 : 0); + } + [Fact] public void GenerateSqlLiteral_returns_json_document_literal() { @@ -98,16 +131,22 @@ public class Order #region Support - private static readonly MySqlTypeMappingSource Mapper = new MySqlTypeMappingSource( + private static MySqlTypeMappingSource CreateMySqlTypeMappingSource( + IMysqlJsonOptions mysqlJsonOptions + ) => new MySqlTypeMappingSource( new TypeMappingSourceDependencies( new ValueConverterSelector(new ValueConverterSelectorDependencies()), new JsonValueReaderWriterSource(new JsonValueReaderWriterSourceDependencies()), Array.Empty()), new RelationalTypeMappingSourceDependencies( - new [] {new MySqlJsonMicrosoftTypeMappingSourcePlugin(new MySqlOptions())}), + new[] { new MySqlJsonMicrosoftTypeMappingSourcePlugin(new MySqlOptions(), mysqlJsonOptions) }), new MySqlOptions() ); + private static readonly MySqlTypeMappingSource Mapper = CreateMySqlTypeMappingSource( + new DefaultMysqlJsonOptions() + ); + private static RelationalTypeMapping GetMapping(string storeType) => Mapper.FindMapping(storeType); private static RelationalTypeMapping GetMapping(Type clrType) => (RelationalTypeMapping)Mapper.FindMapping(clrType); From b6507cbaf10f7362a675d5d568ab1abaec29ef0f Mon Sep 17 00:00:00 2001 From: Jan Trejbal Date: Mon, 8 Jan 2024 16:31:18 +0100 Subject: [PATCH 2/4] Add IMysqlJsonOptions --- .../Infrastructure/IMysqlJsonOptions.cs | 11 +++++++++++ .../Internal/DefaultMysqlJsonOptions.cs | 8 ++++++++ .../MySqlJsonMicrosoftTypeMappingSourcePlugin.cs | 10 ++++++++-- 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 src/EFCore.MySql.Json.Microsoft/Infrastructure/IMysqlJsonOptions.cs create mode 100644 src/EFCore.MySql.Json.Microsoft/Infrastructure/Internal/DefaultMysqlJsonOptions.cs diff --git a/src/EFCore.MySql.Json.Microsoft/Infrastructure/IMysqlJsonOptions.cs b/src/EFCore.MySql.Json.Microsoft/Infrastructure/IMysqlJsonOptions.cs new file mode 100644 index 000000000..13d133c1a --- /dev/null +++ b/src/EFCore.MySql.Json.Microsoft/Infrastructure/IMysqlJsonOptions.cs @@ -0,0 +1,11 @@ +// Copyright (c) Pomelo Foundation. All rights reserved. +// Licensed under the MIT. See LICENSE in the project root for license information. + +using System.Text.Json; + +namespace Pomelo.EntityFrameworkCore.MySql.Json.Microsoft.Infrastructure; + +public interface IMysqlJsonOptions +{ + JsonSerializerOptions JsonSerializerOptions { get; } +} diff --git a/src/EFCore.MySql.Json.Microsoft/Infrastructure/Internal/DefaultMysqlJsonOptions.cs b/src/EFCore.MySql.Json.Microsoft/Infrastructure/Internal/DefaultMysqlJsonOptions.cs new file mode 100644 index 000000000..172f73c61 --- /dev/null +++ b/src/EFCore.MySql.Json.Microsoft/Infrastructure/Internal/DefaultMysqlJsonOptions.cs @@ -0,0 +1,8 @@ +using System.Text.Json; + +namespace Pomelo.EntityFrameworkCore.MySql.Json.Microsoft.Infrastructure.Internal; + +public sealed class DefaultMysqlJsonOptions : IMysqlJsonOptions +{ + public JsonSerializerOptions JsonSerializerOptions { get; init; } = new(); +} diff --git a/src/EFCore.MySql.Json.Microsoft/Storage/Internal/MySqlJsonMicrosoftTypeMappingSourcePlugin.cs b/src/EFCore.MySql.Json.Microsoft/Storage/Internal/MySqlJsonMicrosoftTypeMappingSourcePlugin.cs index 28fa18f16..0d7615c4f 100644 --- a/src/EFCore.MySql.Json.Microsoft/Storage/Internal/MySqlJsonMicrosoftTypeMappingSourcePlugin.cs +++ b/src/EFCore.MySql.Json.Microsoft/Storage/Internal/MySqlJsonMicrosoftTypeMappingSourcePlugin.cs @@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Pomelo.EntityFrameworkCore.MySql.Infrastructure.Internal; +using Pomelo.EntityFrameworkCore.MySql.Json.Microsoft.Infrastructure; using Pomelo.EntityFrameworkCore.MySql.Json.Microsoft.Storage.ValueComparison.Internal; using Pomelo.EntityFrameworkCore.MySql.Json.Microsoft.Storage.ValueConversion.Internal; using Pomelo.EntityFrameworkCore.MySql.Storage.Internal; @@ -20,10 +21,14 @@ public class MySqlJsonMicrosoftTypeMappingSourcePlugin : MySqlJsonTypeMappingSou private static readonly Lazy _jsonElementValueConverter = new Lazy(); private static readonly Lazy _jsonStringValueConverter = new Lazy(); + private readonly IMysqlJsonOptions _mysqlJsonOptions; + public MySqlJsonMicrosoftTypeMappingSourcePlugin( - [NotNull] IMySqlOptions options) + [NotNull] IMySqlOptions options, + [NotNull] IMysqlJsonOptions mysqlJsonOptions) : base(options) { + _mysqlJsonOptions = mysqlJsonOptions; } protected override Type MySqlJsonTypeMappingType => typeof(MySqlJsonMicrosoftTypeMapping<>); @@ -65,7 +70,8 @@ protected override ValueConverter GetValueConverter(Type clrType) } return (ValueConverter)Activator.CreateInstance( - typeof(MySqlJsonMicrosoftPocoValueConverter<>).MakeGenericType(clrType)); + typeof(MySqlJsonMicrosoftPocoValueConverter<>).MakeGenericType(clrType), + _mysqlJsonOptions.JsonSerializerOptions); } protected override ValueComparer GetValueComparer(Type clrType) From e511295898c79c3394b7058fb8690d942e3c6e43 Mon Sep 17 00:00:00 2001 From: Jan Trejbal Date: Mon, 8 Jan 2024 16:31:53 +0100 Subject: [PATCH 3/4] Use JsonSerializerOptions in MySqlJsonMicrosoftPocoValueConverter --- .../MySqlJsonMicrosoftPocoValueConverter.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/EFCore.MySql.Json.Microsoft/Storage/ValueConversion/Internal/MySqlJsonMicrosoftPocoValueConverter.cs b/src/EFCore.MySql.Json.Microsoft/Storage/ValueConversion/Internal/MySqlJsonMicrosoftPocoValueConverter.cs index a431a4d1b..b82b7b29a 100644 --- a/src/EFCore.MySql.Json.Microsoft/Storage/ValueConversion/Internal/MySqlJsonMicrosoftPocoValueConverter.cs +++ b/src/EFCore.MySql.Json.Microsoft/Storage/ValueConversion/Internal/MySqlJsonMicrosoftPocoValueConverter.cs @@ -9,17 +9,17 @@ namespace Pomelo.EntityFrameworkCore.MySql.Json.Microsoft.Storage.ValueConversio { public class MySqlJsonMicrosoftPocoValueConverter : ValueConverter { - public MySqlJsonMicrosoftPocoValueConverter() + public MySqlJsonMicrosoftPocoValueConverter(JsonSerializerOptions jsonSerializerOptions) : base( - v => ConvertToProviderCore(v), - v => ConvertFromProviderCore(v)) + v => ConvertToProviderCore(v, jsonSerializerOptions), + v => ConvertFromProviderCore(v, jsonSerializerOptions)) { } - public static string ConvertToProviderCore(T v) - => JsonSerializer.Serialize(v); + public static string ConvertToProviderCore(T v, JsonSerializerOptions jsonSerializerOptions) + => JsonSerializer.Serialize(v, jsonSerializerOptions); - public static T ConvertFromProviderCore(string v) - => JsonSerializer.Deserialize(v); + public static T ConvertFromProviderCore(string v, JsonSerializerOptions jsonSerializerOptions) + => JsonSerializer.Deserialize(v, jsonSerializerOptions); } } From 982c46a657f86b6d561b08aeeaf78bd16ee42bd9 Mon Sep 17 00:00:00 2001 From: Jan Trejbal Date: Mon, 8 Jan 2024 16:38:06 +0100 Subject: [PATCH 4/4] Add IMysqlJsonOptions into the EF serviceCollection --- .../Design/Internal/MySqlJsonMicrosoftDesignTimeServices.cs | 3 +++ .../MySqlJsonMicrosoftServiceCollectionExtensions.cs | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/EFCore.MySql.Json.Microsoft/Design/Internal/MySqlJsonMicrosoftDesignTimeServices.cs b/src/EFCore.MySql.Json.Microsoft/Design/Internal/MySqlJsonMicrosoftDesignTimeServices.cs index 72593fa78..e5193d9ea 100644 --- a/src/EFCore.MySql.Json.Microsoft/Design/Internal/MySqlJsonMicrosoftDesignTimeServices.cs +++ b/src/EFCore.MySql.Json.Microsoft/Design/Internal/MySqlJsonMicrosoftDesignTimeServices.cs @@ -7,6 +7,8 @@ using Pomelo.EntityFrameworkCore.MySql.Json.Microsoft.Storage.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.DependencyInjection; +using Pomelo.EntityFrameworkCore.MySql.Json.Microsoft.Infrastructure; +using Pomelo.EntityFrameworkCore.MySql.Json.Microsoft.Infrastructure.Internal; namespace Pomelo.EntityFrameworkCore.MySql.Json.Microsoft.Design.Internal { @@ -26,6 +28,7 @@ public class MySqlJsonMicrosoftDesignTimeServices : IDesignTimeServices /// public virtual void ConfigureDesignTimeServices(IServiceCollection serviceCollection) => serviceCollection + .AddSingleton() .AddSingleton() .AddSingleton(); } diff --git a/src/EFCore.MySql.Json.Microsoft/Extensions/MySqlJsonMicrosoftServiceCollectionExtensions.cs b/src/EFCore.MySql.Json.Microsoft/Extensions/MySqlJsonMicrosoftServiceCollectionExtensions.cs index 18f35e7d8..d94a4d283 100644 --- a/src/EFCore.MySql.Json.Microsoft/Extensions/MySqlJsonMicrosoftServiceCollectionExtensions.cs +++ b/src/EFCore.MySql.Json.Microsoft/Extensions/MySqlJsonMicrosoftServiceCollectionExtensions.cs @@ -8,6 +8,8 @@ using Pomelo.EntityFrameworkCore.MySql.Json.Microsoft.Storage.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; +using Pomelo.EntityFrameworkCore.MySql.Json.Microsoft.Infrastructure; +using Pomelo.EntityFrameworkCore.MySql.Json.Microsoft.Infrastructure.Internal; using Pomelo.EntityFrameworkCore.MySql.Query.ExpressionTranslators.Internal; // ReSharper disable once CheckNamespace @@ -33,7 +35,9 @@ public static IServiceCollection AddEntityFrameworkMySqlJsonMicrosoft( .TryAdd() .TryAdd() .TryAddProviderSpecificServices( - x => x.TryAddScopedEnumerable()); + x => x + .TryAddSingleton() + .TryAddScopedEnumerable()); return serviceCollection; }