Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for CosmosSerializer and CosmosSerializationOptions in the Cosmos Provider #24334

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,22 @@ public virtual CosmosDbContextOptionsBuilder IdleTcpConnectionTimeout(TimeSpan t
public virtual CosmosDbContextOptionsBuilder GatewayModeMaxConnectionLimit(int connectionLimit)
=> WithOption(e => e.WithGatewayModeMaxConnectionLimit(Check.NotNull(connectionLimit, nameof(connectionLimit))));

/// <summary>
/// Configures an optional JSON serializer. The client will use it to serialize or
/// de-serialize user's cosmos request/responses. SDK owned types such as DatabaseProperties
/// and ContainerProperties will always use the SDK default serializer.
/// </summary>
/// <param name="serializer"> The JSON serializer. </param>
public virtual CosmosDbContextOptionsBuilder Serializer(CosmosSerializer serializer)
=> WithOption(e => e.WithSerializer(serializer));

/// <summary>
/// Configures the optional serializer options.
/// </summary>
/// <param name="serializationOptions"> Provides a way to configure basic serializer settings. </param>
public virtual CosmosDbContextOptionsBuilder SerializationOptions(CosmosSerializationOptions serializationOptions)
=> WithOption(e => e.WithSerializationOptions(serializationOptions));

/// <summary>
/// Configures the maximum number of TCP connections that may be opened to each Cosmos DB back-end.
/// Together with MaxRequestsPerTcpConnection, this setting limits the number of requests that are
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using JetBrains.Annotations;
using Microsoft.Azure.Cosmos;
using Microsoft.EntityFrameworkCore.Cosmos.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Utilities;
Expand Down Expand Up @@ -43,6 +44,8 @@ public class CosmosOptionsExtension : IDbContextOptionsExtension
private int? _maxRequestsPerTcpConnection;
private bool? _enableContentResponseOnWrite;
private DbContextOptionsExtensionInfo? _info;
private CosmosSerializer? _serializer;
private CosmosSerializationOptions? _serializationOptions;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down Expand Up @@ -77,6 +80,8 @@ protected CosmosOptionsExtension([NotNull] CosmosOptionsExtension copyFrom)
_gatewayModeMaxConnectionLimit = copyFrom._gatewayModeMaxConnectionLimit;
_maxTcpConnectionsPerEndpoint = copyFrom._maxTcpConnectionsPerEndpoint;
_maxRequestsPerTcpConnection = copyFrom._maxRequestsPerTcpConnection;
_serializer = copyFrom._serializer;
_serializationOptions = copyFrom._serializationOptions;
}

/// <summary>
Expand Down Expand Up @@ -146,6 +151,62 @@ public virtual CosmosOptionsExtension WithAccountKey([CanBeNull] string? account
return clone;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual CosmosSerializer? Serializer => _serializer;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual CosmosOptionsExtension WithSerializer([CanBeNull] CosmosSerializer? serializer)
{
if (serializer is not null && (_serializationOptions != null))
{
throw new InvalidOperationException(CosmosStrings.SerializerOptionsConflictingSerializer);
}

var clone = Clone();

clone._serializer = serializer ?? new JsonCosmosSerializer();

return clone;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual CosmosSerializationOptions? SerializationOptions => _serializationOptions;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual CosmosOptionsExtension WithSerializationOptions([CanBeNull] CosmosSerializationOptions? serializationOptions)
{
if (serializationOptions is not null && (_serializer != null))
{
throw new InvalidOperationException(CosmosStrings.SerializerOptionsConflictingSerializer);
}

var clone = Clone();

clone._serializationOptions = serializationOptions;

return clone;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down Expand Up @@ -560,6 +621,8 @@ public override long GetServiceProviderHashCode()
hashCode = (hashCode * 131) ^ (Extension._gatewayModeMaxConnectionLimit?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (Extension._maxTcpConnectionsPerEndpoint?.GetHashCode() ?? 0);
hashCode = (hashCode * 131) ^ (Extension._maxRequestsPerTcpConnection?.GetHashCode() ?? 0);
hashCode = (hashCode * 131) ^ (Extension._serializer?.GetHashCode() ?? 0);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure about this code. can anyone explain how to implement it in a proper way?

hashCode = (hashCode * 397) ^ (Extension._serializationOptions?.GetHashCode() ?? 0);
_serviceProviderHash = hashCode;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,22 @@ public class CosmosSingletonOptions : ICosmosSingletonOptions
/// </summary>
public virtual bool? EnableContentResponseOnWrite { get; private set; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual CosmosSerializer? Serializer { get; private set; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual CosmosSerializationOptions? SerializationOptions { get; private set; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand All @@ -164,6 +180,8 @@ public virtual void Initialize(IDbContextOptions options)
MaxTcpConnectionsPerEndpoint = cosmosOptions.MaxTcpConnectionsPerEndpoint;
MaxRequestsPerTcpConnection = cosmosOptions.MaxRequestsPerTcpConnection;
EnableContentResponseOnWrite = cosmosOptions.EnableContentResponseOnWrite;
Serializer = cosmosOptions.Serializer;
SerializationOptions = cosmosOptions.SerializationOptions;
}
}

Expand Down Expand Up @@ -192,6 +210,8 @@ public virtual void Validate(IDbContextOptions options)
|| MaxTcpConnectionsPerEndpoint != cosmosOptions.MaxTcpConnectionsPerEndpoint
|| MaxRequestsPerTcpConnection != cosmosOptions.MaxRequestsPerTcpConnection
|| EnableContentResponseOnWrite != cosmosOptions.EnableContentResponseOnWrite
|| Serializer != cosmosOptions.Serializer
|| SerializationOptions != cosmosOptions.SerializationOptions
))
{
throw new InvalidOperationException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,5 +138,21 @@ public interface ICosmosSingletonOptions : ISingletonOptions
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
bool? EnableContentResponseOnWrite { get; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
CosmosSerializer? Serializer { get; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
CosmosSerializationOptions? SerializationOptions { get; }
}
}
6 changes: 6 additions & 0 deletions src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/EFCore.Cosmos/Properties/CosmosStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,9 @@
<data name="ReverseAfterSkipTakeNotSupported" xml:space="preserve">
<value>Reversing the ordering is not supported when limit or offset are already applied.</value>
</data>
<data name="SerializerOptionsConflictingSerializer" xml:space="preserve">
<value>SerializerOptions is not compatible with Serializer. Only one can be set.</value>
</data>
<data name="TransactionsNotSupported" xml:space="preserve">
<value>The Cosmos database provider does not support transactions.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public SingletonCosmosClientWrapper([NotNull] ICosmosSingletonOptions options)
_endpoint = options.AccountEndpoint;
_key = options.AccountKey;
_connectionString = options.ConnectionString;
var configuration = new CosmosClientOptions { ApplicationName = _userAgent, Serializer = new JsonCosmosSerializer() };
var configuration = new CosmosClientOptions { ApplicationName = _userAgent };

if (options.Region != null)
{
Expand Down Expand Up @@ -97,6 +97,16 @@ public SingletonCosmosClientWrapper([NotNull] ICosmosSingletonOptions options)
configuration.MaxRequestsPerTcpConnection = options.MaxRequestsPerTcpConnection.Value;
}

if (options.Serializer != null)
{
configuration.Serializer = options.Serializer;
}

if (options.SerializationOptions != null)
{
configuration.SerializerOptions = options.SerializationOptions;
}

_options = configuration;
}

Expand Down
28 changes: 28 additions & 0 deletions test/EFCore.Cosmos.FunctionalTests/ConfigPatternsCosmosTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,34 @@ public async Task Should_throw_if_specified_connection_mode_is_wrong()
});
}

[ConditionalFact]
public async Task Should_use_serialization_options()
Copy link
Member Author

@Marusyk Marusyk Mar 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is just for example. I actually don't know where I should put tests for this.
Also, after SaveChanges, the Name will be stored with null. It seems I missed something. Can it be some Tests configuration? Could you please help?

{
var serializationOptions = new CosmosSerializationOptions
{
IgnoreNullValues = true
};

await using var testDatabase = CosmosTestStore.CreateInitialized(DatabaseName, o =>
{
o.SerializationOptions(serializationOptions);
});
var options = CreateOptions(testDatabase);

var customer = new Customer { Id = 43 };

using var context = new CustomerContext(options);
context.Database.EnsureCreated();

context.Add(customer);

context.SaveChanges();

var customerEntry = context.Entry(context.Find<Customer>(customer.Id));
var jsonProperty = customerEntry.Property<string>("Name");
Assert.Null(jsonProperty);
}

private DbContextOptions CreateOptions(CosmosTestStore testDatabase)
=> Fixture.AddOptions(testDatabase.AddProviderOptions(new DbContextOptionsBuilder()))
.EnableDetailedErrors()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Net;
using Microsoft.Azure.Cosmos;
using Microsoft.EntityFrameworkCore.Cosmos.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Xunit;

Expand Down Expand Up @@ -44,6 +45,66 @@ public void Can_create_options_with_specified_region()
Assert.Equal(regionName, extension.Region);
}

[ConditionalFact]
public void Can_create_options_with_specified_serializer()
{
var serializer = new JsonCosmosSerializer();
var options = new DbContextOptionsBuilder().UseCosmos(
"serviceEndPoint",
"authKeyOrResourceToken",
"databaseName",
o => { o.Serializer(serializer); });

var extension = options
.Options.FindExtension<CosmosOptionsExtension>();

Assert.Same(serializer, extension.Serializer);
}

[ConditionalFact]
public void Can_create_options_with_specified_serialization_options()
{
var serializationOptions = new CosmosSerializationOptions{ IgnoreNullValues = true };
var options = new DbContextOptionsBuilder().UseCosmos(
"serviceEndPoint",
"authKeyOrResourceToken",
"databaseName",
o => { o.SerializationOptions(serializationOptions); });

var extension = options
.Options.FindExtension<CosmosOptionsExtension>();

Assert.Same(serializationOptions, extension.SerializationOptions);
}

[ConditionalFact]
public void Throws_if_specified_serializer_and_serialization_options()
{
var serializer = new JsonCosmosSerializer();
var serializationOptions = new CosmosSerializationOptions { IgnoreNullValues = true };
var options = Assert.Throws<InvalidOperationException>(
() =>
new DbContextOptionsBuilder().UseCosmos(
"serviceEndPoint",
"authKeyOrResourceToken",
"databaseName",
o => { o.Serializer(serializer).SerializationOptions(serializationOptions); }));
}

[ConditionalFact]
public void Throws_if_specified_serialization_options_and_serializer()
{
var serializationOptions = new CosmosSerializationOptions { IgnoreNullValues = true };
var serializer = new JsonCosmosSerializer();
var options = Assert.Throws<InvalidOperationException>(
() =>
new DbContextOptionsBuilder().UseCosmos(
"serviceEndPoint",
"authKeyOrResourceToken",
"databaseName",
o => { o.SerializationOptions(serializationOptions).Serializer(serializer); }));
}

[ConditionalFact]
public void Can_create_options_with_wrong_region()
{
Expand Down