Skip to content

Commit

Permalink
Add Support for CosmosSerializer and CosmosSerializationOptions in th…
Browse files Browse the repository at this point in the history
…e Cosmos Provider
  • Loading branch information
Marusyk committed Mar 7, 2021
1 parent 673e6ca commit 119e1dd
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 9 deletions.
8 changes: 0 additions & 8 deletions src/EFCore.Cosmos/EFCore.Cosmos.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,6 @@
<AdditionalFiles Include="..\stylecop.json" />
</ItemGroup>

<ItemGroup>
<Compile Update="Properties\CosmosStrings.Designer.cs">
<DependentUpon>CosmosStrings.Designer.tt</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
</ItemGroup>

<ItemGroup>
<None Update="Properties\CosmosStrings.Designer.tt">
<CustomToolNamespace></CustomToolNamespace>
Expand Down
16 changes: 16 additions & 0 deletions src/EFCore.Cosmos/Infrastructure/CosmosDbContextOptionsBuilder.cs
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);
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()
{
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

0 comments on commit 119e1dd

Please sign in to comment.