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

Perf: unset EnableContentResponseOnWrite dotnet#22999 #23322

Merged
Merged
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 @@ -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);
}

AndriySvyryd marked this conversation as resolved.
Show resolved Hide resolved
[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);
}
}
}