Skip to content

Commit

Permalink
Perf: unset EnableContentResponseOnWrite
Browse files Browse the repository at this point in the history
Fixes #22999
  • Loading branch information
umitkavala authored Nov 16, 2020
1 parent f4628ea commit 89816b7
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,15 @@ public virtual CosmosDbContextOptionsBuilder MaxTcpConnectionsPerEndpoint(int co
public virtual CosmosDbContextOptionsBuilder MaxRequestsPerTcpConnection(int requestLimit)
=> WithOption(e => e.WithMaxRequestsPerTcpConnection(Check.NotNull(requestLimit, nameof(requestLimit))));

/// <summary>
/// Sets the boolean to only return the headers and status code in the Cosmos DB response for write item operation
/// like Create, Upsert, Patch and Replace. Setting the option to false will cause the response to have a null resource.
/// This reduces networking and CPU load by not sending the resource back over the network and serializing it on the client.
/// </summary>
/// <param name="enabled"><see langword="false" /> to have null resource</param>
public virtual CosmosDbContextOptionsBuilder ContentResponseOnWriteEnabled(bool enabled = true)
=> WithOption(e => e.ContentResponseOnWriteEnabled(Check.NotNull(enabled, nameof(enabled))));

/// <summary>
/// Sets an option by cloning the extension used to store the settings. This ensures the builder
/// does not modify options that are already in use elsewhere.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public class CosmosOptionsExtension : IDbContextOptionsExtension
private int? _gatewayModeMaxConnectionLimit;
private int? _maxTcpConnectionsPerEndpoint;
private int? _maxRequestsPerTcpConnection;
private bool? _enableContentResponseOnWrite;
private DbContextOptionsExtensionInfo _info;

/// <summary>
Expand Down Expand Up @@ -441,6 +442,30 @@ public virtual CosmosOptionsExtension WithMaxRequestsPerTcpConnection(int reques
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 bool? EnableContentResponseOnWrite
=> _enableContentResponseOnWrite;

/// <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 ContentResponseOnWriteEnabled(bool enabled)
{
var clone = Clone();

clone._enableContentResponseOnWrite = enabled;

return clone;
}

/// <summary>
/// A factory for creating the default <see cref="IExecutionStrategy" />, or <see langword="null" /> if none has been
/// configured.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ public class CosmosSingletonOptions : ICosmosSingletonOptions
/// </summary>
public virtual int? MaxRequestsPerTcpConnection { 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 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
Expand All @@ -153,6 +161,7 @@ public virtual void Initialize(IDbContextOptions options)
GatewayModeMaxConnectionLimit = cosmosOptions.GatewayModeMaxConnectionLimit;
MaxTcpConnectionsPerEndpoint = cosmosOptions.MaxTcpConnectionsPerEndpoint;
MaxRequestsPerTcpConnection = cosmosOptions.MaxRequestsPerTcpConnection;
EnableContentResponseOnWrite = cosmosOptions.EnableContentResponseOnWrite;
}
}

Expand All @@ -179,7 +188,9 @@ public virtual void Validate(IDbContextOptions options)
|| IdleTcpConnectionTimeout != cosmosOptions.IdleTcpConnectionTimeout
|| GatewayModeMaxConnectionLimit != cosmosOptions.GatewayModeMaxConnectionLimit
|| MaxTcpConnectionsPerEndpoint != cosmosOptions.MaxTcpConnectionsPerEndpoint
|| MaxRequestsPerTcpConnection != cosmosOptions.MaxRequestsPerTcpConnection))
|| MaxRequestsPerTcpConnection != cosmosOptions.MaxRequestsPerTcpConnection
|| EnableContentResponseOnWrite != cosmosOptions.EnableContentResponseOnWrite
))
{
throw new InvalidOperationException(
CoreStrings.SingletonOptionChanged(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,5 +128,13 @@ public interface ICosmosSingletonOptions : ISingletonOptions
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
int? MaxRequestsPerTcpConnection { 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>
bool? EnableContentResponseOnWrite { get; }
}
}
9 changes: 7 additions & 2 deletions src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public class CosmosClientWrapper
private readonly string _databaseId;
private readonly IExecutionStrategyFactory _executionStrategyFactory;
private readonly IDiagnosticsLogger<DbLoggerCategory.Database.Command> _commandLogger;
private readonly bool? _enableContentResponseOnWrite;

static CosmosClientWrapper()
{
Expand All @@ -89,6 +90,7 @@ public CosmosClientWrapper(
_databaseId = options.DatabaseName;
_executionStrategyFactory = executionStrategyFactory;
_commandLogger = commandLogger;
_enableContentResponseOnWrite = options.EnableContentResponseOnWrite;
}

private CosmosClient Client
Expand Down Expand Up @@ -416,6 +418,7 @@ public virtual async Task<bool> DeleteItemOnceAsync(
{
var entry = parameters.Entry;
var items = Client.GetDatabase(_databaseId).GetContainer(parameters.ContainerId);

var itemRequestOptions = CreateItemRequestOptions(entry);
var partitionKey = CreatePartitionKey(entry);

Expand All @@ -427,7 +430,7 @@ public virtual async Task<bool> DeleteItemOnceAsync(
return response.StatusCode == HttpStatusCode.NoContent;
}

private static ItemRequestOptions CreateItemRequestOptions(IUpdateEntry entry)
private ItemRequestOptions CreateItemRequestOptions(IUpdateEntry entry)
{
var etagProperty = entry.EntityType.GetETagProperty();
if (etagProperty == null)
Expand All @@ -442,7 +445,9 @@ private static ItemRequestOptions CreateItemRequestOptions(IUpdateEntry entry)
etag = converter.ConvertToProvider(etag);
}

return new ItemRequestOptions { IfMatchEtag = (string)etag };
var enabledContentResponse = _enableContentResponseOnWrite ?? entry.EntityType.FindProperty(StoreKeyConvention.JObjectPropertyName)?.ValueGenerated == ValueGenerated.OnAddOrUpdate;

return new ItemRequestOptions { IfMatchEtag = (string)etag, EnableContentResponseOnWrite = enabledContentResponse };
}

private static PartitionKey CreatePartitionKey(IUpdateEntry entry)
Expand Down
89 changes: 86 additions & 3 deletions test/EFCore.Cosmos.FunctionalTests/CosmosConcurrencyTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public virtual Task Adding_the_same_entity_twice_results_in_DbUpdateException()
ctx => ctx.Customers.Add(
new Customer
{
Id = "1", Name = "CreatedTwice",
Id = "1",
Name = "CreatedTwice",
}));
}

Expand All @@ -38,7 +39,8 @@ public virtual Task Updating_then_deleting_the_same_entity_results_in_DbUpdateCo
ctx => ctx.Customers.Add(
new Customer
{
Id = "2", Name = "Added",
Id = "2",
Name = "Added",
}),
ctx => ctx.Customers.Single(c => c.Id == "2").Name = "Updated",
ctx => ctx.Customers.Remove(ctx.Customers.Single(c => c.Id == "2")));
Expand All @@ -51,12 +53,81 @@ public virtual Task Updating_then_updating_the_same_entity_results_in_DbUpdateCo
ctx => ctx.Customers.Add(
new Customer
{
Id = "3", Name = "Added",
Id = "3",
Name = "Added",
}),
ctx => ctx.Customers.Single(c => c.Id == "3").Name = "Updated",
ctx => ctx.Customers.Single(c => c.Id == "3").Name = "Updated");
}

[ConditionalFact]
public async Task Etag_will_return_when_content_response_enabled_false()
{
await using var testDatabase = CosmosTestStore.CreateInitialized(DatabaseName);

var customer = new Customer
{
Id = "4",
Name = "Theon",
};

await using (var context = new ConcurrencyContext(CreateOptions(testDatabase, enableContentResponseOnWrite: false)))
{
await context.Database.EnsureCreatedAsync();

context.Add(customer);

await context.SaveChangesAsync();
}

await using (var context = new ConcurrencyContext(CreateOptions(testDatabase, enableContentResponseOnWrite: false)))
{
var customerFromStore = await context.Set<Customer>().SingleAsync();

Assert.Equal(customer.Id, customerFromStore.Id);
Assert.Equal("Theon", customerFromStore.Name);
Assert.Equal(customer.ETag, customerFromStore.ETag);

context.Remove(customerFromStore);

await context.SaveChangesAsync();
}
}

[ConditionalFact]
public async Task Etag_will_return_when_content_response_enabled_true()
{
await using var testDatabase = CosmosTestStore.Create(DatabaseName);

var customer = new Customer
{
Id = "3",
Name = "Theon",
};

await using (var context = new ConcurrencyContext(CreateOptions(testDatabase, enableContentResponseOnWrite: true)))
{
await context.Database.EnsureCreatedAsync();

context.Add(customer);

await context.SaveChangesAsync();
}

await using (var context = new ConcurrencyContext(CreateOptions(testDatabase, enableContentResponseOnWrite: true)))
{
var customerFromStore = await context.Set<Customer>().SingleAsync();

Assert.Equal(customer.Id, customerFromStore.Id);
Assert.Equal("Theon", customerFromStore.Name);
Assert.Equal(customer.ETag, customerFromStore.ETag);

context.Remove(customerFromStore);

await context.SaveChangesAsync();
}
}

/// <summary>
/// Runs the two actions with two different contexts and calling
/// SaveChanges such that storeChange will succeed and the store will reflect this change, and
Expand Down Expand Up @@ -137,6 +208,18 @@ protected override void OnModelCreating(ModelBuilder builder)
}
}

private DbContextOptions CreateOptions(CosmosTestStore testDatabase, bool enableContentResponseOnWrite)
{
var optionsBuilder = new DbContextOptionsBuilder();

new DbContextOptionsBuilder().UseCosmos(testDatabase.ConnectionString, testDatabase.Name,
b => b.ApplyConfiguration().ContentResponseOnWriteEnabled(enabled: enableContentResponseOnWrite));

return testDatabase.AddProviderOptions(optionsBuilder)
.EnableDetailedErrors()
.Options;
}

public class Customer
{
public string Id { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,5 +191,21 @@ public void Can_create_options_with_max_requests_per_tcp_connection()

Assert.Equal(requestLimit, extension.MaxRequestsPerTcpConnection);
}

[ConditionalFact]
public void Can_create_options_with_content_response_on_write_enabled()
{
var enabled = true;
var options = new DbContextOptionsBuilder().UseCosmos(
"serviceEndPoint",
"authKeyOrResourceToken",
"databaseName",
o => { o.ContentResponseOnWriteEnabled(enabled); });

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

Assert.Equal(enabled, extension.EnableContentResponseOnWrite);
}
}
}

0 comments on commit 89816b7

Please sign in to comment.