diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 18c1e7019cc..dc81a206c35 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,7 +8,7 @@ updates: - dependency-name: Castle.Core - dependency-name: Humanizer.Core - dependency-name: IdentityServer4.EntityFramework - - dependency-name: Microsoft.Azure.Cosmos + - dependency-name: Azure.Cosmos - dependency-name: Microsoft.CSharp - dependency-name: Microsoft.Data.SqlClient - dependency-name: Microsoft.DotNet.PlatformAbstractions diff --git a/src/EFCore.Cosmos/EFCore.Cosmos.csproj b/src/EFCore.Cosmos/EFCore.Cosmos.csproj index 7b4d07c3489..7c65b686b16 100644 --- a/src/EFCore.Cosmos/EFCore.Cosmos.csproj +++ b/src/EFCore.Cosmos/EFCore.Cosmos.csproj @@ -21,7 +21,7 @@ - + diff --git a/src/EFCore.Cosmos/Extensions/CosmosDatabaseFacadeExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosDatabaseFacadeExtensions.cs index dc9e8f08d73..f7f015d84a5 100644 --- a/src/EFCore.Cosmos/Extensions/CosmosDatabaseFacadeExtensions.cs +++ b/src/EFCore.Cosmos/Extensions/CosmosDatabaseFacadeExtensions.cs @@ -2,8 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Azure.Cosmos; using JetBrains.Annotations; -using Microsoft.Azure.Cosmos; using Microsoft.EntityFrameworkCore.Cosmos.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.Cosmos.Internal; using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal; diff --git a/src/EFCore.Cosmos/Infrastructure/CosmosDbContextOptionsBuilder.cs b/src/EFCore.Cosmos/Infrastructure/CosmosDbContextOptionsBuilder.cs index a11e0ab07ec..d84e03d2f59 100644 --- a/src/EFCore.Cosmos/Infrastructure/CosmosDbContextOptionsBuilder.cs +++ b/src/EFCore.Cosmos/Infrastructure/CosmosDbContextOptionsBuilder.cs @@ -4,8 +4,8 @@ using System; using System.ComponentModel; using System.Net; +using Azure.Cosmos; using JetBrains.Annotations; -using Microsoft.Azure.Cosmos; using Microsoft.EntityFrameworkCore.Cosmos.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; @@ -59,14 +59,6 @@ public virtual CosmosDbContextOptionsBuilder Region([NotNull] string region) public virtual CosmosDbContextOptionsBuilder LimitToEndpoint(bool enable = true) => WithOption(e => e.WithLimitToEndpoint(Check.NotNull(enable, nameof(enable)))); - /// - /// Allows optimistic batching of requests to service. Setting this option might impact the latency of the operations. - /// Hence this option is recommended for non-latency sensitive scenarios only. - /// - /// to allow optimistic batching of requests to service. - public virtual CosmosDbContextOptionsBuilder AllowBulkExecution(bool enable = true) - => WithOption(e => e.WithAllowBulkExecution(Check.NotNull(enable, nameof(enable)))); - /// /// Configures the context to use the provided connection mode. /// @@ -103,20 +95,6 @@ public virtual CosmosDbContextOptionsBuilder OpenTcpConnectionTimeout(TimeSpan t public virtual CosmosDbContextOptionsBuilder IdleTcpConnectionTimeout(TimeSpan timeout) => WithOption(e => e.WithIdleTcpConnectionTimeout(Check.NotNull(timeout, nameof(timeout)))); - /// - /// Configures the client port reuse policy used by the transport stack. - /// - /// The client port reuse policy used by the transport stack. - public virtual CosmosDbContextOptionsBuilder PortReuseMode(PortReuseMode portReuseMode) - => WithOption(e => e.WithPortReuseMode(Check.NotNull(portReuseMode, nameof(portReuseMode)))); - - /// - /// Configures the context to enable address cache refresh on TCP connection reset notification. - /// - /// to enable address cache refresh on TCP connection reset notification. - public virtual CosmosDbContextOptionsBuilder EnableTcpConnectionEndpointRediscovery(bool enabled = true) - => WithOption(e => e.WithTcpConnectionEndpointRediscovery(Check.NotNull(enabled, nameof(enabled)))); - /// /// Configures the maximum number of concurrent connections allowed for the target service endpoint /// in the Azure Cosmos DB service. diff --git a/src/EFCore.Cosmos/Infrastructure/Internal/CosmosDbOptionExtension.cs b/src/EFCore.Cosmos/Infrastructure/Internal/CosmosDbOptionExtension.cs index 63f9aea3bf2..e21ab8c5d2b 100644 --- a/src/EFCore.Cosmos/Infrastructure/Internal/CosmosDbOptionExtension.cs +++ b/src/EFCore.Cosmos/Infrastructure/Internal/CosmosDbOptionExtension.cs @@ -6,8 +6,8 @@ using System.Globalization; using System.Net; using System.Text; +using Azure.Cosmos; using JetBrains.Annotations; -using Microsoft.Azure.Cosmos; using Microsoft.EntityFrameworkCore.Cosmos.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage; @@ -31,14 +31,11 @@ public class CosmosOptionsExtension : IDbContextOptionsExtension private string _region; private ConnectionMode? _connectionMode; private bool? _limitToEndpoint; - private bool? _allowBulkExecution; private Func _executionStrategyFactory; private IWebProxy _webProxy; private TimeSpan? _requestTimeout; private TimeSpan? _openTcpConnectionTimeout; private TimeSpan? _idleTcpConnectionTimeout; - private PortReuseMode? _portReuseMode; - private bool? _enableTcpConnectionEndpointRediscovery; private int? _gatewayModeMaxConnectionLimit; private int? _maxTcpConnectionsPerEndpoint; private int? _maxRequestsPerTcpConnection; @@ -69,14 +66,11 @@ protected CosmosOptionsExtension([NotNull] CosmosOptionsExtension copyFrom) _region = copyFrom._region; _connectionMode = copyFrom._connectionMode; _limitToEndpoint = copyFrom._limitToEndpoint; - _allowBulkExecution = copyFrom._allowBulkExecution; _executionStrategyFactory = copyFrom._executionStrategyFactory; _webProxy = copyFrom._webProxy; _requestTimeout = copyFrom._requestTimeout; _openTcpConnectionTimeout = copyFrom._openTcpConnectionTimeout; _idleTcpConnectionTimeout = copyFrom._idleTcpConnectionTimeout; - _portReuseMode = copyFrom._portReuseMode; - _enableTcpConnectionEndpointRediscovery = copyFrom._enableTcpConnectionEndpointRediscovery; _gatewayModeMaxConnectionLimit = copyFrom._gatewayModeMaxConnectionLimit; _maxTcpConnectionsPerEndpoint = copyFrom._maxTcpConnectionsPerEndpoint; _maxRequestsPerTcpConnection = copyFrom._maxRequestsPerTcpConnection; @@ -244,29 +238,6 @@ public virtual CosmosOptionsExtension WithLimitToEndpoint(bool enable) return clone; } - /// - /// 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. - /// - public virtual bool? AllowBulkExecution => _allowBulkExecution; - - /// - /// 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. - /// - public virtual CosmosOptionsExtension WithAllowBulkExecution(bool enable) - { - var clone = Clone(); - - clone._allowBulkExecution = enable; - - return clone; - } - /// /// 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 @@ -387,52 +358,6 @@ public virtual CosmosOptionsExtension WithIdleTcpConnectionTimeout(TimeSpan time return clone; } - /// - /// 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. - /// - public virtual PortReuseMode? PortReuseMode => _portReuseMode; - - /// - /// 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. - /// - public virtual CosmosOptionsExtension WithPortReuseMode(PortReuseMode portReuseMode) - { - var clone = Clone(); - - clone._portReuseMode = portReuseMode; - - return clone; - } - - /// - /// 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. - /// - public virtual bool? TcpConnectionEndpointRediscoveryEnabled => _enableTcpConnectionEndpointRediscovery; - - /// - /// 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. - /// - public virtual CosmosOptionsExtension WithTcpConnectionEndpointRediscovery(bool enable) - { - var clone = Clone(); - - clone._enableTcpConnectionEndpointRediscovery = enable; - - return clone; - } - /// /// 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 @@ -584,13 +509,10 @@ public override long GetServiceProviderHashCode() hashCode = (hashCode * 397) ^ (Extension._region?.GetHashCode() ?? 0); hashCode = (hashCode * 3) ^ (Extension._connectionMode?.GetHashCode() ?? 0); hashCode = (hashCode * 3) ^ (Extension._limitToEndpoint?.GetHashCode() ?? 0); - hashCode = (hashCode * 3) ^ (Extension._allowBulkExecution?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ (Extension._webProxy?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ (Extension._requestTimeout?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ (Extension._openTcpConnectionTimeout?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ (Extension._idleTcpConnectionTimeout?.GetHashCode() ?? 0); - hashCode = (hashCode * 3) ^ (Extension._portReuseMode?.GetHashCode() ?? 0); - hashCode = (hashCode * 3) ^ (Extension._enableTcpConnectionEndpointRediscovery?.GetHashCode() ?? 0); hashCode = (hashCode * 131) ^ (Extension._gatewayModeMaxConnectionLimit?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ (Extension._maxTcpConnectionsPerEndpoint?.GetHashCode() ?? 0); hashCode = (hashCode * 131) ^ (Extension._maxRequestsPerTcpConnection?.GetHashCode() ?? 0); diff --git a/src/EFCore.Cosmos/Infrastructure/Internal/CosmosSingletonOptions.cs b/src/EFCore.Cosmos/Infrastructure/Internal/CosmosSingletonOptions.cs index 58222c95e34..39282b5b194 100644 --- a/src/EFCore.Cosmos/Infrastructure/Internal/CosmosSingletonOptions.cs +++ b/src/EFCore.Cosmos/Infrastructure/Internal/CosmosSingletonOptions.cs @@ -3,6 +3,7 @@ using System; using System.Net; +using Azure.Cosmos; using Microsoft.Azure.Cosmos; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -65,14 +66,6 @@ public class CosmosSingletonOptions : ICosmosSingletonOptions /// public virtual bool? LimitToEndpoint { get; private set; } - /// - /// 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. - /// - public virtual bool? AllowBulkExecution { get; private set; } - /// /// 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 @@ -113,22 +106,6 @@ public class CosmosSingletonOptions : ICosmosSingletonOptions /// public virtual TimeSpan? IdleTcpConnectionTimeout { get; private set; } - /// - /// 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. - /// - public virtual PortReuseMode? PortReuseMode { get; private set; } - - /// - /// 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. - /// - public virtual bool? TcpConnectionEndpointRediscoveryEnabled { get; private set; } - /// /// 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 @@ -169,14 +146,11 @@ public virtual void Initialize(IDbContextOptions options) ConnectionString = cosmosOptions.ConnectionString; Region = cosmosOptions.Region; LimitToEndpoint = cosmosOptions.LimitToEndpoint; - AllowBulkExecution = cosmosOptions.AllowBulkExecution; ConnectionMode = cosmosOptions.ConnectionMode; WebProxy = cosmosOptions.WebProxy; RequestTimeout = cosmosOptions.RequestTimeout; OpenTcpConnectionTimeout = cosmosOptions.OpenTcpConnectionTimeout; IdleTcpConnectionTimeout = cosmosOptions.IdleTcpConnectionTimeout; - PortReuseMode = cosmosOptions.PortReuseMode; - TcpConnectionEndpointRediscoveryEnabled = cosmosOptions.TcpConnectionEndpointRediscoveryEnabled; GatewayModeMaxConnectionLimit = cosmosOptions.GatewayModeMaxConnectionLimit; MaxTcpConnectionsPerEndpoint = cosmosOptions.MaxTcpConnectionsPerEndpoint; MaxRequestsPerTcpConnection = cosmosOptions.MaxRequestsPerTcpConnection; @@ -199,14 +173,11 @@ public virtual void Validate(IDbContextOptions options) || ConnectionString != cosmosOptions.ConnectionString || Region != cosmosOptions.Region || LimitToEndpoint != cosmosOptions.LimitToEndpoint - || AllowBulkExecution != cosmosOptions.AllowBulkExecution || ConnectionMode != cosmosOptions.ConnectionMode || WebProxy != cosmosOptions.WebProxy || RequestTimeout != cosmosOptions.RequestTimeout || OpenTcpConnectionTimeout != cosmosOptions.OpenTcpConnectionTimeout || IdleTcpConnectionTimeout != cosmosOptions.IdleTcpConnectionTimeout - || PortReuseMode != cosmosOptions.PortReuseMode - || TcpConnectionEndpointRediscoveryEnabled != cosmosOptions.TcpConnectionEndpointRediscoveryEnabled || GatewayModeMaxConnectionLimit != cosmosOptions.GatewayModeMaxConnectionLimit || MaxTcpConnectionsPerEndpoint != cosmosOptions.MaxTcpConnectionsPerEndpoint || MaxRequestsPerTcpConnection != cosmosOptions.MaxRequestsPerTcpConnection)) diff --git a/src/EFCore.Cosmos/Infrastructure/Internal/ICosmosSingletonOptions.cs b/src/EFCore.Cosmos/Infrastructure/Internal/ICosmosSingletonOptions.cs index 50cb57c5db2..547c52aea77 100644 --- a/src/EFCore.Cosmos/Infrastructure/Internal/ICosmosSingletonOptions.cs +++ b/src/EFCore.Cosmos/Infrastructure/Internal/ICosmosSingletonOptions.cs @@ -3,7 +3,7 @@ using System; using System.Net; -using Microsoft.Azure.Cosmos; +using Azure.Cosmos; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.Extensions.DependencyInjection; @@ -65,14 +65,6 @@ public interface ICosmosSingletonOptions : ISingletonOptions /// bool? LimitToEndpoint { get; } - /// - /// 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. - /// - bool? AllowBulkExecution { get; } - /// /// 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 @@ -113,22 +105,6 @@ public interface ICosmosSingletonOptions : ISingletonOptions /// TimeSpan? IdleTcpConnectionTimeout { get; } - /// - /// 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. - /// - PortReuseMode? PortReuseMode { get; } - - /// - /// 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. - /// - bool? TcpConnectionEndpointRediscoveryEnabled { get; } - /// /// 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 diff --git a/src/EFCore.Cosmos/Query/Internal/QuerySqlGenerator.cs b/src/EFCore.Cosmos/Query/Internal/QuerySqlGenerator.cs index dd566eac06f..6ffc3ab7a77 100644 --- a/src/EFCore.Cosmos/Query/Internal/QuerySqlGenerator.cs +++ b/src/EFCore.Cosmos/Query/Internal/QuerySqlGenerator.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Linq.Expressions; using System.Text; +using System.Text.Json; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Cosmos.Internal; using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal; @@ -363,6 +364,11 @@ protected override Expression VisitSqlConstant(SqlConstantExpression sqlConstant private JToken GenerateJToken(object value, CoreTypeMapping typeMapping) { + if (value == null) + { + return null; + } + value = ConvertUnderlyingEnumValueToEnum(value, typeMapping.ClrType); var converter = typeMapping.Converter; @@ -371,18 +377,9 @@ private JToken GenerateJToken(object value, CoreTypeMapping typeMapping) value = converter.ConvertToProvider(value); } - if (value == null) - { - return null; - } - return (value as JToken) ?? JToken.FromObject(value, CosmosClientWrapper.Serializer); } - // Enum when compared to constant will always have value of integral type - // when enum would contain convert node. We remove the convert node but we also - // need to convert the integral value to enum value. - // This allows us to use converter on enum value or print enum value directly if supported by provider private object ConvertUnderlyingEnumValueToEnum(object value, Type clrType) => value?.GetType().IsInteger() == true && clrType.UnwrapNullableType().IsEnum ? Enum.ToObject(clrType.UnwrapNullableType(), value) diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs index a92f3500aed..72de1c6bf3e 100644 --- a/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs +++ b/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs @@ -11,8 +11,9 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Azure; +using Azure.Cosmos; using JetBrains.Annotations; -using Microsoft.Azure.Cosmos; using Microsoft.EntityFrameworkCore.Cosmos.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Cosmos.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; @@ -50,7 +51,7 @@ public class CosmosClientWrapper /// 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. /// - public static readonly JsonSerializer Serializer = new JsonSerializer(); + public static readonly JsonSerializer Serializer = JsonSerializer.Create(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -139,7 +140,7 @@ public virtual async Task CreateDatabaseIfNotExistsOnceAsync( var response = await Client.CreateDatabaseIfNotExistsAsync(_databaseId, cancellationToken: cancellationToken) .ConfigureAwait(false); - return response.StatusCode == HttpStatusCode.Created; + return response.GetRawResponse().Status == (int)HttpStatusCode.Created; } /// @@ -186,13 +187,13 @@ public virtual async Task DeleteDatabaseOnceAsync( { using var response = await Client.GetDatabase(_databaseId).DeleteStreamAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); - if (response.StatusCode == HttpStatusCode.NotFound) + if (response.Status == (int)HttpStatusCode.NotFound) { return false; } - response.EnsureSuccessStatusCode(); - return response.StatusCode == HttpStatusCode.NoContent; + EnsureSuccessStatusCode(response); + return response.Status == (int)HttpStatusCode.NoContent; } /// @@ -237,13 +238,13 @@ private async Task CreateContainerIfNotExistsOnceAsync( }, cancellationToken: cancellationToken) .ConfigureAwait(false); - if (response.StatusCode == HttpStatusCode.Conflict) + if (response.Status == (int)HttpStatusCode.Conflict) { return false; } - response.EnsureSuccessStatusCode(); - return response.StatusCode == HttpStatusCode.Created; + EnsureSuccessStatusCode(response); + return response.Status == (int)HttpStatusCode.Created; } /// @@ -286,7 +287,7 @@ private async Task CreateItemOnceAsync( await using var stream = new MemoryStream(); await using var writer = new StreamWriter(stream, new UTF8Encoding(), bufferSize: 1024, leaveOpen: false); using var jsonWriter = new JsonTextWriter(writer); - JsonSerializer.Create().Serialize(jsonWriter, parameters.Document); + Serializer.Serialize(jsonWriter, parameters.Document); await jsonWriter.FlushAsync(cancellationToken).ConfigureAwait(false); var entry = parameters.Entry; @@ -298,7 +299,7 @@ private async Task CreateItemOnceAsync( .ConfigureAwait(false); ProcessResponse(response, entry); - return response.StatusCode == HttpStatusCode.Created; + return response.Status == (int)HttpStatusCode.Created; } /// @@ -348,7 +349,7 @@ private async Task ReplaceItemOnceAsync( using var stream = new MemoryStream(); using var writer = new StreamWriter(stream, new UTF8Encoding(), bufferSize: 1024, leaveOpen: false); using var jsonWriter = new JsonTextWriter(writer); - JsonSerializer.Create().Serialize(jsonWriter, parameters.Document); + Serializer.Serialize(jsonWriter, parameters.Document); await jsonWriter.FlushAsync(cancellationToken).ConfigureAwait(false); var entry = parameters.Entry; @@ -361,7 +362,7 @@ private async Task ReplaceItemOnceAsync( .ConfigureAwait(false); ProcessResponse(response, entry); - return response.StatusCode == HttpStatusCode.OK; + return response.Status == (int)HttpStatusCode.OK; } /// @@ -423,7 +424,7 @@ public virtual async Task DeleteItemOnceAsync( .ConfigureAwait(false); ProcessResponse(response, entry); - return response.StatusCode == HttpStatusCode.NoContent; + return response.Status == (int)HttpStatusCode.NoContent; } private static ItemRequestOptions CreateItemRequestOptions(IUpdateEntry entry) @@ -441,7 +442,7 @@ private static ItemRequestOptions CreateItemRequestOptions(IUpdateEntry entry) etag = converter.ConvertToProvider(etag); } - return new ItemRequestOptions { IfMatchEtag = (string)etag }; + return new ItemRequestOptions { IfMatch = new ETag((string)etag) }; } private static PartitionKey CreatePartitionKey(IUpdateEntry entry) @@ -463,25 +464,25 @@ private static PartitionKey CreatePartitionKey(IUpdateEntry entry) return partitionKey == null ? PartitionKey.None : new PartitionKey((string)partitionKey); } - private static void ProcessResponse(ResponseMessage response, IUpdateEntry entry) + private static void ProcessResponse(Response response, IUpdateEntry entry) { - response.EnsureSuccessStatusCode(); + EnsureSuccessStatusCode(response); var etagProperty = entry.EntityType.GetETagProperty(); if (etagProperty != null && entry.EntityState != EntityState.Deleted) { - entry.SetStoreGeneratedValue(etagProperty, response.Headers.ETag); + entry.SetStoreGeneratedValue(etagProperty, response.Headers.ETag?.ToString()); } var jObjectProperty = entry.EntityType.FindProperty(StoreKeyConvention.JObjectPropertyName); if (jObjectProperty != null && jObjectProperty.ValueGenerated == ValueGenerated.OnAddOrUpdate - && response.Content != null) + && response.ContentStream != null) { - using var responseStream = response.Content; + using var responseStream = response.ContentStream; using var reader = new StreamReader(responseStream); using var jsonReader = new JsonTextReader(reader); - var createdDocument = new JsonSerializer().Deserialize(jsonReader); + var createdDocument = Serializer.Deserialize(jsonReader); entry.SetStoreGeneratedValue(jObjectProperty, createdDocument); } @@ -559,20 +560,20 @@ internal virtual async Task ExecuteReadItemAsync( return JObjectFromReadItemResponseMessage(responseMessage); } - private static JObject JObjectFromReadItemResponseMessage(ResponseMessage responseMessage) + private static JObject JObjectFromReadItemResponseMessage(Response response) { - responseMessage.EnsureSuccessStatusCode(); + EnsureSuccessStatusCode(response); - var responseStream = responseMessage.Content; + var responseStream = response.ContentStream; var reader = new StreamReader(responseStream); var jsonReader = new JsonTextReader(reader); - var jObject = new JsonSerializer().Deserialize(jsonReader); + var jObject = Serializer.Deserialize(jsonReader); return new JObject(new JProperty("c", jObject)); } - private FeedIterator CreateQuery( + private IAsyncEnumerable CreateQuery( string containerId, string partitionKey, CosmosSqlQuery query) @@ -594,7 +595,7 @@ private FeedIterator CreateQuery( return container.GetItemQueryStreamIterator(queryDefinition, requestOptions: queryRequestOptions); } - private async Task CreateSingleItemQuery( + private async Task CreateSingleItemQuery( string containerId, string partitionKey, string resourceId, @@ -641,13 +642,23 @@ private static bool TryReadJObject(JsonTextReader jsonReader, out JObject jObjec { if (jsonReader.TokenType == JsonToken.StartObject) { - jObject = new JsonSerializer().Deserialize(jsonReader); + jObject = Serializer.Deserialize(jsonReader); return true; } } + return false; } + private static void EnsureSuccessStatusCode(Response response) + { + var httpStatusCode = response.Status; + if (httpStatusCode < 200 || httpStatusCode > 299) + { + throw new HttpException(response); + } + } + private sealed class DocumentEnumerable : IEnumerable { private readonly CosmosClientWrapper _cosmosClient; @@ -678,12 +689,12 @@ private sealed class Enumerator : IEnumerator private readonly string _partitionKey; private readonly CosmosSqlQuery _cosmosSqlQuery; - private ResponseMessage _responseMessage; + private Response _response; private Stream _responseStream; private StreamReader _reader; private JsonTextReader _jsonReader; - private FeedIterator _query; + private IAsyncEnumerator _query; public Enumerator(DocumentEnumerable documentEnumerable) { @@ -701,18 +712,18 @@ public bool MoveNext() { if (_jsonReader == null) { - _query ??= _cosmosClientWrapper.CreateQuery(_containerId, _partitionKey, _cosmosSqlQuery); - - if (!_query.HasMoreResults) + _query ??= _cosmosClientWrapper.CreateQuery(_containerId, _partitionKey, _cosmosSqlQuery).GetAsyncEnumerator(); + + if (!_query.MoveNextAsync().AsTask().GetAwaiter().GetResult()) { Current = default; return false; } - _responseMessage = _query.ReadNextAsync().GetAwaiter().GetResult(); - _responseMessage.EnsureSuccessStatusCode(); + _response = _query.Current; + EnsureSuccessStatusCode(_response); - _responseStream = _responseMessage.Content; + _responseStream = _response.ContentStream; _reader = new StreamReader(_responseStream); _jsonReader = CreateJsonReader(_reader); } @@ -742,8 +753,8 @@ public void Dispose() { ResetRead(); - _responseMessage?.Dispose(); - _responseMessage = null; + _response?.Dispose(); + _response = null; } public void Reset() => throw new NotImplementedException(); @@ -780,12 +791,12 @@ private sealed class AsyncEnumerator : IAsyncEnumerator private readonly CosmosSqlQuery _cosmosSqlQuery; private readonly CancellationToken _cancellationToken; - private ResponseMessage _responseMessage; + private Response _response; private Stream _responseStream; private StreamReader _reader; private JsonTextReader _jsonReader; - private FeedIterator _query; + private IAsyncEnumerator _query; public JObject Current { get; private set; } @@ -805,18 +816,18 @@ public async ValueTask MoveNextAsync() if (_jsonReader == null) { - _query ??= _cosmosClientWrapper.CreateQuery(_containerId, _partitionKey, _cosmosSqlQuery); + _query ??= _cosmosClientWrapper.CreateQuery(_containerId, _partitionKey, _cosmosSqlQuery).GetAsyncEnumerator(_cancellationToken); - if (!_query.HasMoreResults) + if (!await _query.MoveNextAsync().ConfigureAwait(false)) { Current = default; return false; } - _responseMessage = await _query.ReadNextAsync(_cancellationToken).ConfigureAwait(false); - _responseMessage.EnsureSuccessStatusCode(); + _response = _query.Current; + EnsureSuccessStatusCode(_response); - _responseStream = _responseMessage.Content; + _responseStream = _response.ContentStream; _reader = new StreamReader(_responseStream); _jsonReader = CreateJsonReader(_reader); } @@ -846,8 +857,8 @@ public async ValueTask DisposeAsync() { await ResetReadAsync().ConfigureAwait(false); - await _responseMessage.DisposeAsyncIfAvailable().ConfigureAwait(false); - _responseMessage = null; + await _response.DisposeAsyncIfAvailable().ConfigureAwait(false); + _response = null; } } } diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs index fb1a06f6ace..e9e85f4dc5f 100644 --- a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs +++ b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs @@ -6,8 +6,8 @@ using System.Net; using System.Threading; using System.Threading.Tasks; +using Azure.Cosmos; using JetBrains.Annotations; -using Microsoft.Azure.Cosmos; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Cosmos.Internal; using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; @@ -114,9 +114,13 @@ public override int SaveChanges(IList entries) rowsAffected++; } } - catch (CosmosException ex) + catch (CosmosException cosmosException) { - throw ThrowUpdateException(ex, entry); + throw ThrowUpdateException(cosmosException, entry); + } + catch (HttpException httpException) + { + throw ThrowUpdateException(httpException, entry); } } @@ -177,9 +181,13 @@ public override async Task SaveChangesAsync( rowsAffected++; } } - catch (CosmosException ex) + catch (CosmosException cosmosException) + { + throw ThrowUpdateException(cosmosException, entry); + } + catch (HttpException httpException) { - throw ThrowUpdateException(ex, entry); + throw ThrowUpdateException(httpException, entry); } } @@ -384,10 +392,26 @@ private Exception ThrowUpdateException(CosmosException exception, IUpdateEntry e { var documentSource = GetDocumentSource(entry.EntityType); var id = documentSource.GetId(entry.SharedIdentityEntry ?? entry); - throw exception.StatusCode switch + throw exception.Status switch + { + (int)HttpStatusCode.PreconditionFailed => + new DbUpdateConcurrencyException(CosmosStrings.UpdateConflict(id), exception, new[] { entry }), + (int)HttpStatusCode.Conflict => + new DbUpdateException(CosmosStrings.UpdateConflict(id), exception, new[] { entry }), + _ => Rethrow(exception), + }; + } + + private Exception ThrowUpdateException(HttpException exception, IUpdateEntry entry) + { + var documentSource = GetDocumentSource(entry.EntityType); + var id = documentSource.GetId(entry.SharedIdentityEntry ?? entry); + throw exception.Response.Status switch { - HttpStatusCode.PreconditionFailed => new DbUpdateConcurrencyException(CosmosStrings.UpdateConflict(id), exception, new[] { entry }), - HttpStatusCode.Conflict => new DbUpdateException(CosmosStrings.UpdateConflict(id), exception, new[] { entry }), + (int)HttpStatusCode.PreconditionFailed => + new DbUpdateConcurrencyException(CosmosStrings.UpdateConflict(id), exception, new[] { entry }), + (int)HttpStatusCode.Conflict => + new DbUpdateException(CosmosStrings.UpdateConflict(id), exception, new[] { entry }), _ => Rethrow(exception), }; } diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosExecutionStrategy.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosExecutionStrategy.cs index 325d7ec9903..ac58c510e2d 100644 --- a/src/EFCore.Cosmos/Storage/Internal/CosmosExecutionStrategy.cs +++ b/src/EFCore.Cosmos/Storage/Internal/CosmosExecutionStrategy.cs @@ -5,8 +5,8 @@ using System.Globalization; using System.Linq; using System.Net; +using Azure.Cosmos; using JetBrains.Annotations; -using Microsoft.Azure.Cosmos; using Microsoft.EntityFrameworkCore.Storage; namespace Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal @@ -101,24 +101,24 @@ protected override bool ShouldRetryOn(Exception exception) { if (exception is CosmosException cosmosException) { - return IsTransient(cosmosException.StatusCode); + return IsTransient(cosmosException.Status); } if (exception is HttpException httpException) { - return IsTransient(httpException.Response.StatusCode); + return IsTransient(httpException.Response.Status); } if (exception is WebException webException) { - return IsTransient(((HttpWebResponse)webException.Response).StatusCode); + return IsTransient((int)((HttpWebResponse)webException.Response).StatusCode); } return false; - static bool IsTransient(HttpStatusCode statusCode) - => statusCode == HttpStatusCode.ServiceUnavailable - || statusCode == HttpStatusCode.TooManyRequests; + static bool IsTransient(int status) + => status == (int)HttpStatusCode.ServiceUnavailable + || status == (int)HttpStatusCode.TooManyRequests; } /// @@ -130,12 +130,9 @@ static bool IsTransient(HttpStatusCode statusCode) protected override TimeSpan? GetNextDelay(Exception lastException) { var baseDelay = base.GetNextDelay(lastException); - if (baseDelay == null) - { - return null; - } - - return CallOnWrappedException(lastException, GetDelayFromException) + return baseDelay == null + ? null + : CallOnWrappedException(lastException, GetDelayFromException) ?? baseDelay; } @@ -143,54 +140,93 @@ static bool IsTransient(HttpStatusCode statusCode) { if (exception is CosmosException cosmosException) { - return cosmosException.RetryAfter; + if (cosmosException.TryGetHeader("x-ms-retry-after-ms", out var delayString) + && TryParseMsRetryAfter(delayString, out var delay)) + { + return delay; + } + + if (cosmosException.TryGetHeader("Retry-After", out delayString) + && TryParseRetryAfter(delayString, out delay)) + { + return delay; + } } if (exception is HttpException httpException) { - if (httpException.Response.Headers.TryGetValues("x-ms-retry-after-ms", out var values)) + if (httpException.Response.Headers.TryGetValues("x-ms-retry-after-ms", out var values) + && TryParseMsRetryAfter(values.FirstOrDefault(), out var delay)) { - var delayString = values.Single(); - return TimeSpan.FromMilliseconds(int.Parse(delayString)); + return delay; } - var retryDate = httpException.Response.Headers.RetryAfter.Date; - if (retryDate != null) + if (httpException.Response.Headers.TryGetValues("Retry-After", out values) + && TryParseRetryAfter(values.FirstOrDefault(), out delay)) { - var delay = retryDate.Value.Subtract(DateTime.Now); - return delay <= TimeSpan.Zero ? TimeSpan.FromMilliseconds(1) : delay; + return delay; } - - return httpException.Response.Headers.RetryAfter.Delta; } if (exception is WebException webException) { var response = (HttpWebResponse)webException.Response; - var delayString = response.Headers.GetValues("x-ms-retry-after-ms")?.Single(); - if (delayString != null) + var delayString = response.Headers.GetValues("x-ms-retry-after-ms")?.FirstOrDefault(); + if (TryParseMsRetryAfter(delayString, out var delay)) { - return TimeSpan.FromMilliseconds(int.Parse(delayString)); + return delay; } - delayString = response.Headers.GetValues("Retry-After")?.Single(); - if (delayString != null) + delayString = response.Headers.GetValues("Retry-After")?.FirstOrDefault(); + if (TryParseRetryAfter(delayString, out delay)) { - if (int.TryParse(delayString, out var intDelay)) - { - return TimeSpan.FromSeconds(intDelay); - } - - if (DateTime.TryParse(delayString, CultureInfo.InvariantCulture, DateTimeStyles.None, out var retryDate)) - { - var delay = retryDate.Subtract(DateTime.Now); - return delay <= TimeSpan.Zero ? TimeSpan.FromMilliseconds(1) : delay; - } + return delay; } } return null; + + static bool TryParseMsRetryAfter(string delayString, out TimeSpan delay) + { + delay = default; + if (delayString == null) + { + return false; + } + + if (int.TryParse(delayString, out var intDelay)) + { + delay = TimeSpan.FromMilliseconds(intDelay); + return true; + } + + return false; + } + + static bool TryParseRetryAfter(string delayString, out TimeSpan delay) + { + delay = default; + if (delayString == null) + { + return false; + } + + if (int.TryParse(delayString, out var intDelay)) + { + delay = TimeSpan.FromSeconds(intDelay); + return true; + } + + if (DateTimeOffset.TryParse(delayString, CultureInfo.InvariantCulture, DateTimeStyles.None, out var retryDate)) + { + delay = retryDate.Subtract(DateTimeOffset.Now); + delay = delay <= TimeSpan.Zero ? TimeSpan.FromMilliseconds(1) : delay; + return true; + } + + return false; + } } } } diff --git a/src/EFCore.Cosmos/Storage/Internal/HttpException.cs b/src/EFCore.Cosmos/Storage/Internal/HttpException.cs index 3e4371a3525..282b8f6ccf0 100644 --- a/src/EFCore.Cosmos/Storage/Internal/HttpException.cs +++ b/src/EFCore.Cosmos/Storage/Internal/HttpException.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Net.Http; +using Azure; using JetBrains.Annotations; namespace Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal @@ -21,8 +21,8 @@ public class HttpException : Exception /// 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. /// - public HttpException([NotNull] HttpResponseMessage response) - : base(response.StatusCode.ToString()) + public HttpException([NotNull] Response response) + : base(response.ReasonPhrase) { // An error occurred while sending the request. Response = response; @@ -34,6 +34,6 @@ public HttpException([NotNull] HttpResponseMessage response) /// 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. /// - public virtual HttpResponseMessage Response { get; } + public virtual Response Response { get; } } } diff --git a/src/EFCore.Cosmos/Storage/Internal/JsonCosmosSerializer.cs b/src/EFCore.Cosmos/Storage/Internal/JsonCosmosSerializer.cs new file mode 100644 index 00000000000..984e594da29 --- /dev/null +++ b/src/EFCore.Cosmos/Storage/Internal/JsonCosmosSerializer.cs @@ -0,0 +1,56 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO; +using System.Text; +using Azure.Cosmos.Serialization; +using Newtonsoft.Json; + +namespace Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal +{ + /// + /// 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. + /// + public class JsonCosmosSerializer : CosmosSerializer + { + private static readonly Encoding DefaultEncoding = new UTF8Encoding(false, true); + + /// + public override T FromStream(Stream stream) + { + using (stream) + { + if (typeof(Stream).IsAssignableFrom(typeof(T))) + { + return (T)(object)stream; + } + + using var streamReader = new StreamReader(stream); + using var jsonTextReader = new JsonTextReader(streamReader); + return GetSerializer().Deserialize(jsonTextReader); + } + } + + /// + public override Stream ToStream(T input) + { + var streamPayload = new MemoryStream(); + using (var streamWriter = new StreamWriter(streamPayload, encoding: DefaultEncoding, bufferSize: 1024, leaveOpen: true)) + { + using var jsonTextWriter = new JsonTextWriter(streamWriter); + jsonTextWriter.Formatting = Formatting.None; + GetSerializer().Serialize(jsonTextWriter, input); + jsonTextWriter.Flush(); + streamWriter.Flush(); + } + + streamPayload.Position = 0; + return streamPayload; + } + + private JsonSerializer GetSerializer() => CosmosClientWrapper.Serializer; + } +} diff --git a/src/EFCore.Cosmos/Storage/Internal/SingletonCosmosClientWrapper.cs b/src/EFCore.Cosmos/Storage/Internal/SingletonCosmosClientWrapper.cs index b766938fd5e..9a7a7689cb0 100644 --- a/src/EFCore.Cosmos/Storage/Internal/SingletonCosmosClientWrapper.cs +++ b/src/EFCore.Cosmos/Storage/Internal/SingletonCosmosClientWrapper.cs @@ -2,8 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Azure.Cosmos; using JetBrains.Annotations; -using Microsoft.Azure.Cosmos; using Microsoft.EntityFrameworkCore.Cosmos.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.Extensions.DependencyInjection; @@ -46,7 +46,8 @@ public SingletonCosmosClientWrapper([NotNull] ICosmosSingletonOptions options) _connectionString = options.ConnectionString; var configuration = new CosmosClientOptions { - ApplicationName = _userAgent + ApplicationName = _userAgent, + Serializer = new JsonCosmosSerializer() }; if (options.Region != null) @@ -59,11 +60,6 @@ public SingletonCosmosClientWrapper([NotNull] ICosmosSingletonOptions options) configuration.LimitToEndpoint = options.LimitToEndpoint.Value; } - if (options.AllowBulkExecution != null) - { - configuration.AllowBulkExecution = options.AllowBulkExecution.Value; - } - if (options.ConnectionMode != null) { configuration.ConnectionMode = options.ConnectionMode.Value; @@ -89,16 +85,6 @@ public SingletonCosmosClientWrapper([NotNull] ICosmosSingletonOptions options) configuration.IdleTcpConnectionTimeout = options.IdleTcpConnectionTimeout.Value; } - if (options.PortReuseMode != null) - { - configuration.PortReuseMode = options.PortReuseMode.Value; - } - - if (options.TcpConnectionEndpointRediscoveryEnabled != null) - { - configuration.EnableTcpConnectionEndpointRediscovery = options.TcpConnectionEndpointRediscoveryEnabled.Value; - } - if (options.GatewayModeMaxConnectionLimit != null) { configuration.GatewayModeMaxConnectionLimit = options.GatewayModeMaxConnectionLimit.Value; diff --git a/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs b/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs index 4d2ad67c164..92d747be4b1 100644 --- a/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs +++ b/src/EFCore.Cosmos/Update/Internal/DocumentSource.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections; using System.Linq; using JetBrains.Annotations; @@ -325,7 +326,10 @@ private static JToken ConvertPropertyValue(IProperty property, object value) return null; } - var converter = property.GetTypeMapping().Converter; + var typeMapping = property.GetTypeMapping(); + value = ConvertUnderlyingEnumValueToEnum(value, typeMapping.ClrType); + + var converter = typeMapping.Converter; if (converter != null) { value = converter.ConvertToProvider(value); @@ -333,5 +337,10 @@ private static JToken ConvertPropertyValue(IProperty property, object value) return (value as JToken) ?? JToken.FromObject(value, CosmosClientWrapper.Serializer); } + + private static object ConvertUnderlyingEnumValueToEnum(object value, Type clrType) + => value?.GetType().IsInteger() == true && clrType.UnwrapNullableType().IsEnum + ? Enum.ToObject(clrType.UnwrapNullableType(), value) + : value; } } diff --git a/test/EFCore.Cosmos.FunctionalTests/ConfigPatternsCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/ConfigPatternsCosmosTest.cs index f0a8d16bcb9..9677cdeb951 100644 --- a/test/EFCore.Cosmos.FunctionalTests/ConfigPatternsCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/ConfigPatternsCosmosTest.cs @@ -3,7 +3,7 @@ using System; using System.Threading.Tasks; -using Microsoft.Azure.Cosmos; +using Azure.Cosmos; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; diff --git a/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs index 80a7eb1285a..4802d2d8304 100644 --- a/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.Azure.Cosmos; using Microsoft.EntityFrameworkCore.Cosmos.Internal; +using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Conventions; @@ -1052,8 +1053,8 @@ public async Task Add_update_delete_query_throws_if_no_container() context.Add(customer); Assert.StartsWith( - "Response status code does not indicate success: NotFound (404); Substatus: 0", - (await Assert.ThrowsAsync(() => context.SaveChangesAsync())).Message); + @"Message: {""Errors"":[""Resource Not Found""]}", + (await Assert.ThrowsAsync(() => context.SaveChangesAsync())).Message); } using (var context = new CustomerContext(options)) @@ -1061,8 +1062,8 @@ public async Task Add_update_delete_query_throws_if_no_container() context.Add(customer).State = EntityState.Modified; Assert.StartsWith( - "Response status code does not indicate success: NotFound (404); Substatus: 0", - (await Assert.ThrowsAsync(() => context.SaveChangesAsync())).Message); + @"Message: {""Errors"":[""Resource Not Found""]}", + (await Assert.ThrowsAsync(() => context.SaveChangesAsync())).Message); } using (var context = new CustomerContext(options)) @@ -1070,15 +1071,15 @@ public async Task Add_update_delete_query_throws_if_no_container() context.Add(customer).State = EntityState.Deleted; Assert.StartsWith( - "Response status code does not indicate success: NotFound (404); Substatus: 0", - (await Assert.ThrowsAsync(() => context.SaveChangesAsync())).Message); + @"Message: {""Errors"":[""Resource Not Found""]}", + (await Assert.ThrowsAsync(() => context.SaveChangesAsync())).Message); } using (var context = new CustomerContext(options)) { Assert.StartsWith( - "Response status code does not indicate success: NotFound (404); Substatus: 0", - (await Assert.ThrowsAsync(() => context.Set().SingleAsync())).Message); + @"Message: {""Errors"":[""Resource Not Found""]}", + (await Assert.ThrowsAsync(() => context.Set().SingleAsync())).Message); } } diff --git a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs index 27039fcc1a4..54312c587b0 100644 --- a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs +++ b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs @@ -5,7 +5,8 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using Microsoft.Azure.Cosmos; +using System.Text.Json; +using Azure.Cosmos; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -99,7 +100,7 @@ private async Task CreateFromFile(DbContext context) if (await context.Database.EnsureCreatedAsync()) { var cosmosClient = context.GetService(); - var serializer = new JsonSerializer(); + var serializer = CosmosClientWrapper.Serializer; using var fs = new FileStream(_dataFilePath, FileMode.Open, FileAccess.Read); using var sr = new StreamReader(fs); using var reader = new JsonTextReader(sr); @@ -167,30 +168,24 @@ public override async Task CleanAsync(DbContext context) var cosmosClient = context.Database.GetCosmosClient(); var database = cosmosClient.GetDatabase(Name); var containerIterator = database.GetContainerQueryIterator(); - while (containerIterator.HasMoreResults) + await foreach (var containerProperties in containerIterator) { - foreach (var containerProperties in await containerIterator.ReadNextAsync()) - { - var container = database.GetContainer(containerProperties.Id); - var partitionKey = containerProperties.PartitionKeyPath[1..]; - var itemIterator = container.GetItemQueryIterator( - new QueryDefinition("SELECT * FROM c")); + var container = database.GetContainer(containerProperties.Id); + var partitionKey = containerProperties.PartitionKeyPath[1..]; + var itemIterator = container.GetItemQueryIterator( + new QueryDefinition("SELECT * FROM c")); - var items = new List<(string Id, string PartitionKey)>(); - while (itemIterator.HasMoreResults) - { - foreach (var item in await itemIterator.ReadNextAsync()) - { - items.Add((item["id"].ToString(), item[partitionKey]?.ToString())); - } - } + var items = new List<(string Id, string PartitionKey)>(); + await foreach (var item in itemIterator) + { + items.Add((item["id"].ToString(), item[partitionKey]?.ToString())); + } - foreach (var item in items) - { - await container.DeleteItemAsync( - item.Id, - item.PartitionKey == null ? PartitionKey.None : new PartitionKey(item.PartitionKey)); - } + foreach (var item in items) + { + await container.DeleteItemAsync( + item.Id, + item.PartitionKey == null ? PartitionKey.None : new PartitionKey(item.PartitionKey)); } } diff --git a/test/EFCore.Cosmos.Tests/Extensions/CosmosDbContextOptionsExtensionsTests.cs b/test/EFCore.Cosmos.Tests/Extensions/CosmosDbContextOptionsExtensionsTests.cs index fdd3446f24d..436866dfd3a 100644 --- a/test/EFCore.Cosmos.Tests/Extensions/CosmosDbContextOptionsExtensionsTests.cs +++ b/test/EFCore.Cosmos.Tests/Extensions/CosmosDbContextOptionsExtensionsTests.cs @@ -3,7 +3,7 @@ using System; using System.Net; -using Microsoft.Azure.Cosmos; +using Azure.Cosmos; using Microsoft.EntityFrameworkCore.Cosmos.Infrastructure.Internal; using Xunit; @@ -87,20 +87,6 @@ public void Can_create_options_and_limit_to_endpoint() Assert.True(extension.LimitToEndpoint); } - [ConditionalFact] - public void Can_create_options_and_allow_bulk_execution() - { - var options = new DbContextOptionsBuilder().UseCosmos( - "serviceEndPoint", - "authKeyOrResourceToken", - "databaseName", - o => { o.AllowBulkExecution(); }); - - var extension = options.Options.FindExtension(); - - Assert.True(extension.AllowBulkExecution); - } - [ConditionalFact] public void Can_create_options_with_web_proxy() { @@ -161,35 +147,6 @@ public void Can_create_options_with_idle_tcp_connection_timeout() Assert.Equal(timeout, extension.IdleTcpConnectionTimeout); } - [ConditionalFact] - public void Can_create_options_with_port_reuse_mode() - { - var portReuseMode = PortReuseMode.ReuseUnicastPort; - var options = new DbContextOptionsBuilder().UseCosmos( - "serviceEndPoint", - "authKeyOrResourceToken", - "databaseName", - o => { o.PortReuseMode(portReuseMode); }); - - var extension = options.Options.FindExtension(); - - Assert.Equal(portReuseMode, extension.PortReuseMode); - } - - [ConditionalFact] - public void Can_create_options_with_tcp_connection_endpoint_rediscovery_enabled() - { - var options = new DbContextOptionsBuilder().UseCosmos( - "serviceEndPoint", - "authKeyOrResourceToken", - "databaseName", - o => { o.EnableTcpConnectionEndpointRediscovery(); }); - - var extension = options.Options.FindExtension(); - - Assert.True(extension.TcpConnectionEndpointRediscoveryEnabled); - } - [ConditionalFact] public void Can_create_options_with_gateway_mode_max_connection_limit() {