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 class MySqlJsonMicrosoftServiceCollectionExtensions .TryAdd() .TryAdd() .TryAddProviderSpecificServices( - x => x.TryAddScopedEnumerable()); + x => x + .TryAddSingleton() + .TryAddScopedEnumerable()); return serviceCollection; } 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 b763d99c7..a2bb9fcc7 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<>); @@ -64,7 +69,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) 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 ec5af53fe..12e294b3b 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)) { } - private static string ConvertToProviderCore(T v) - => JsonSerializer.Serialize(v); + private static string ConvertToProviderCore(T v, JsonSerializerOptions jsonSerializerOptions) + => JsonSerializer.Serialize(v, jsonSerializerOptions); - private static T ConvertFromProviderCore(string v) - => JsonSerializer.Deserialize(v); + private static T ConvertFromProviderCore(string v, JsonSerializerOptions jsonSerializerOptions) + => JsonSerializer.Deserialize(v, jsonSerializerOptions); } } 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);