From 84e9155470311a9d768472530748aacbd83bb137 Mon Sep 17 00:00:00 2001 From: Oleksandr Kyselov Date: Tue, 30 Jan 2024 21:22:57 +0100 Subject: [PATCH 01/27] Allow compilation fresh clone --- .../Microsoft.Azure.CosmosEventSourcing.csproj | 2 +- .../Microsoft.Azure.CosmosRepository.AspNetCore.csproj | 2 +- .../Microsoft.Azure.CosmosRepository.csproj | 2 +- ...rosoft.Azure.CosmosEventSourcingAcceptanceTests.csproj | 4 ++++ .../Microsoft.Azure.CosmosEventSourcingTests.csproj | 1 + .../Microsoft.Azure.CosmosRepositoryTests.csproj | 3 +++ .../Providers/DefaultCosmosClientProviderTests.cs | 8 ++++---- 7 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.Azure.CosmosEventSourcing/Microsoft.Azure.CosmosEventSourcing.csproj b/src/Microsoft.Azure.CosmosEventSourcing/Microsoft.Azure.CosmosEventSourcing.csproj index ce9c249cc..6f739ca79 100644 --- a/src/Microsoft.Azure.CosmosEventSourcing/Microsoft.Azure.CosmosEventSourcing.csproj +++ b/src/Microsoft.Azure.CosmosEventSourcing/Microsoft.Azure.CosmosEventSourcing.csproj @@ -27,7 +27,7 @@ false false Microsoft.Azure.CosmosEventSourcing - NU5125 + NU5125;NU1507 true https://github.com/IEvangelist/azure-cosmos-dotnet-repository LICENSE diff --git a/src/Microsoft.Azure.CosmosRepository.AspNetCore/Microsoft.Azure.CosmosRepository.AspNetCore.csproj b/src/Microsoft.Azure.CosmosRepository.AspNetCore/Microsoft.Azure.CosmosRepository.AspNetCore.csproj index 69def86b0..70a57e377 100644 --- a/src/Microsoft.Azure.CosmosRepository.AspNetCore/Microsoft.Azure.CosmosRepository.AspNetCore.csproj +++ b/src/Microsoft.Azure.CosmosRepository.AspNetCore/Microsoft.Azure.CosmosRepository.AspNetCore.csproj @@ -31,7 +31,7 @@ false false Microsoft.Azure.CosmosRepository.AspNetCore - NU5125 + NU5125;NU1507 true https://github.com/IEvangelist/azure-cosmos-dotnet-repository LICENSE diff --git a/src/Microsoft.Azure.CosmosRepository/Microsoft.Azure.CosmosRepository.csproj b/src/Microsoft.Azure.CosmosRepository/Microsoft.Azure.CosmosRepository.csproj index cff3f7544..8daf36e78 100644 --- a/src/Microsoft.Azure.CosmosRepository/Microsoft.Azure.CosmosRepository.csproj +++ b/src/Microsoft.Azure.CosmosRepository/Microsoft.Azure.CosmosRepository.csproj @@ -34,7 +34,7 @@ false false Microsoft.Azure.CosmosRepository - NU5125 + NU5125;NU1507 true https://github.com/IEvangelist/azure-cosmos-dotnet-repository LICENSE diff --git a/tests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests.csproj b/tests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests.csproj index 15aada29f..cf0fdc3b4 100644 --- a/tests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests.csproj +++ b/tests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests.csproj @@ -28,4 +28,8 @@ + + $(NoWarn);NU1507 + + diff --git a/tests/Microsoft.Azure.CosmosEventSourcingTests/Microsoft.Azure.CosmosEventSourcingTests.csproj b/tests/Microsoft.Azure.CosmosEventSourcingTests/Microsoft.Azure.CosmosEventSourcingTests.csproj index 828ca6953..eaa2071e3 100644 --- a/tests/Microsoft.Azure.CosmosEventSourcingTests/Microsoft.Azure.CosmosEventSourcingTests.csproj +++ b/tests/Microsoft.Azure.CosmosEventSourcingTests/Microsoft.Azure.CosmosEventSourcingTests.csproj @@ -5,6 +5,7 @@ enable false True + NU5125;NU1507 diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/Microsoft.Azure.CosmosRepositoryTests.csproj b/tests/Microsoft.Azure.CosmosRepositoryTests/Microsoft.Azure.CosmosRepositoryTests.csproj index 71c0e4bd1..a4099a681 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/Microsoft.Azure.CosmosRepositoryTests.csproj +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/Microsoft.Azure.CosmosRepositoryTests.csproj @@ -28,4 +28,7 @@ + + $(NoWarn);NU1507 + diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/Providers/DefaultCosmosClientProviderTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/Providers/DefaultCosmosClientProviderTests.cs index 2ee2c9eea..eff86fdaf 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/Providers/DefaultCosmosClientProviderTests.cs +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/Providers/DefaultCosmosClientProviderTests.cs @@ -6,7 +6,7 @@ namespace Microsoft.Azure.CosmosRepositoryTests.Providers; public class DefaultCosmosClientProviderTests { [Fact] - public void DefaultCosmosClientProviderCorrectlyDisposesOverloadWithConnectionString() + public async Task DefaultCosmosClientProviderCorrectlyDisposesOverloadWithConnectionString() { var mock = new Mock(); mock.SetupGet(provider => provider.ClientOptions).Returns(new CosmosClientOptions()); @@ -21,12 +21,12 @@ public void DefaultCosmosClientProviderCorrectlyDisposesOverloadWithConnectionSt provider.Dispose(); - Assert.ThrowsAsync( + await Assert.ThrowsAsync( async () => await provider.UseClientAsync(client => client.ReadAccountAsync())); } [Fact] - public void DefaultCosmosClientProviderCorrectlyDisposesOverloadWithTokenCredential() + public async Task DefaultCosmosClientProviderCorrectlyDisposesOverloadWithTokenCredential() { var mock = new Mock(); mock.SetupGet(provider => provider.ClientOptions).Returns(new CosmosClientOptions()); @@ -41,7 +41,7 @@ public void DefaultCosmosClientProviderCorrectlyDisposesOverloadWithTokenCredent provider.Dispose(); - Assert.ThrowsAsync( + await Assert.ThrowsAsync( async () => await provider.UseClientAsync(client => client.ReadAccountAsync())); } } From 1737b45d46bce0f56e2bb197e8b48d61013848b4 Mon Sep 17 00:00:00 2001 From: Oleksandr Kyselov Date: Sat, 3 Feb 2024 13:41:45 +0100 Subject: [PATCH 02/27] Added PartitionKeyLists --- .../HierarchicalPartitionKeysPathAttribute.cs | 40 ++++++++++ .../Builders/ContainerOptionsBuilder.cs | 38 +++++++++ .../Options/ItemConfiguration.cs | 80 ++++++++++++++----- .../DefaultCosmosItemConfigurationProvider.cs | 31 ++++--- .../DefaultCosmosPartitionKeyPathProvider.cs | 30 +++++++ .../ICosmosPartitionKeyPathProvider.cs | 9 +++ .../Services/DefaultCosmosContainerService.cs | 1 + 7 files changed, 200 insertions(+), 29 deletions(-) create mode 100644 src/Microsoft.Azure.CosmosRepository/Attributes/HierarchicalPartitionKeysPathAttribute.cs diff --git a/src/Microsoft.Azure.CosmosRepository/Attributes/HierarchicalPartitionKeysPathAttribute.cs b/src/Microsoft.Azure.CosmosRepository/Attributes/HierarchicalPartitionKeysPathAttribute.cs new file mode 100644 index 000000000..acf6e3790 --- /dev/null +++ b/src/Microsoft.Azure.CosmosRepository/Attributes/HierarchicalPartitionKeysPathAttribute.cs @@ -0,0 +1,40 @@ +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Azure.CosmosRepository.Attributes; + +/// +/// The partition key path attribute exposes the ability to declaratively +/// specify an partition key path. This attribute should be used in +/// conjunction with a on the property +/// whose value will act as the partition key. Partition key paths should start with "/", +/// for example "/partition". For more information, +/// see https://docs.microsoft.com/azure/cosmos-db/partitioning-overview. +/// +/// +/// By default, "/id" is used. +/// +/// +/// Constructor accepting the of the partition key for a given . +/// +/// +/// +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public sealed class HierarchicalPartitionKeysPathAttribute(string path, string subPathOne, string? subPathTwo = null) : Attribute +{ + /// + /// Gets the path of the parition key. + /// + public string Path { get; } = path ?? throw new ArgumentNullException(nameof(path), "A path is required."); + + /// + /// Gets the path of the sub parition key one. + /// + public string SubPathOne { get; } = subPathOne ?? throw new ArgumentNullException(nameof(subPathOne), "A subpath is required."); + + /// + /// Gets the path of the sub parition key two. + /// + public string? SubPathTwo { get; } = subPathTwo; +} diff --git a/src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs b/src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs index 257162b7b..0b177b987 100644 --- a/src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs +++ b/src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs @@ -27,6 +27,11 @@ public class ContainerOptionsBuilder(Type type) /// internal string? PartitionKey { get; private set; } + /// + /// The partition keys for the container. + /// + internal IEnumerable? PartitionKeys { get; private set; } + /// /// The default time to live for a container. /// @@ -83,6 +88,39 @@ public ContainerOptionsBuilder WithPartitionKey(string partitionKey) return this; } + /// + /// Configures a hierarchical partition key structure for the container, consisting of a root key and optional sub-keys. + /// + /// The primary partition key. + /// The first level sub-partition key. + /// An optional second level sub-partition key. + /// The current instance of , enabling method chaining. + /// Thrown when either or is null or whitespace. + public ContainerOptionsBuilder WithHierarchicalPartitionKey(string partitionKey, string partitionSubKey1, string? partitionSubKey2 = null) + { + if (string.IsNullOrWhiteSpace(partitionKey)) + { + throw new ArgumentNullException(nameof(partitionKey), "Partition key cannot be null or whitespace."); + } + + if (string.IsNullOrWhiteSpace(partitionSubKey1)) + { + throw new ArgumentNullException(nameof(partitionSubKey1), "First sub-partition key cannot be null or whitespace."); + } + + var partitionKeys = new List { partitionKey, partitionSubKey1 }; + + if (partitionSubKey2 != null && string.IsNullOrWhiteSpace(partitionSubKey2)) + { + partitionKeys.Add(partitionSubKey2); + } + + PartitionKeys = partitionKeys; + + return this; + } + + /// /// Sets to true /// diff --git a/src/Microsoft.Azure.CosmosRepository/Options/ItemConfiguration.cs b/src/Microsoft.Azure.CosmosRepository/Options/ItemConfiguration.cs index 0b52ec62a..6b7f4044c 100644 --- a/src/Microsoft.Azure.CosmosRepository/Options/ItemConfiguration.cs +++ b/src/Microsoft.Azure.CosmosRepository/Options/ItemConfiguration.cs @@ -3,32 +3,72 @@ namespace Microsoft.Azure.CosmosRepository.Options; -internal class ItemConfiguration( - Type type, - string containerName, - string partitionKeyPath, - UniqueKeyPolicy? uniqueKeyPolicy, - ThroughputProperties? throughputProperties, - int defaultTimeToLive = -1, - bool syncContainerProperties = false, - ChangeFeedOptions? changeFeedOptions = null, - bool useStrictTypeChecking = true) +internal class ItemConfiguration { - public Type Type { get; } = type; + // Existing constructor + public ItemConfiguration( + Type type, + string containerName, + string partitionKeyPath, + UniqueKeyPolicy? uniqueKeyPolicy = null, + ThroughputProperties? throughputProperties = null, + int defaultTimeToLive = -1, + bool syncContainerProperties = false, + ChangeFeedOptions? changeFeedOptions = null, + bool useStrictTypeChecking = true) + { + Type = type; + ContainerName = containerName; + PartitionKeyPath = partitionKeyPath; + UniqueKeyPolicy = uniqueKeyPolicy; + ThroughputProperties = throughputProperties; + DefaultTimeToLive = defaultTimeToLive; + SyncContainerProperties = syncContainerProperties; + ChangeFeedOptions = changeFeedOptions; + UseStrictTypeChecking = useStrictTypeChecking; + } - public string ContainerName { get; } = containerName; + // New additional constructor + public ItemConfiguration( + Type type, + string containerName, + List partitionKeyPaths, + UniqueKeyPolicy? uniqueKeyPolicy = null, + ThroughputProperties? throughputProperties = null, + int defaultTimeToLive = -1, + bool syncContainerProperties = false, + ChangeFeedOptions? changeFeedOptions = null, + bool useStrictTypeChecking = true) + { + Type = type; + ContainerName = containerName; + PartitionKeyPaths = partitionKeyPaths; + UniqueKeyPolicy = uniqueKeyPolicy; + ThroughputProperties = throughputProperties; + DefaultTimeToLive = defaultTimeToLive; + SyncContainerProperties = syncContainerProperties; + ChangeFeedOptions = changeFeedOptions; + UseStrictTypeChecking = useStrictTypeChecking; + + } - public string PartitionKeyPath { get; } = partitionKeyPath; + public Type Type { get; } - public UniqueKeyPolicy? UniqueKeyPolicy { get; } = uniqueKeyPolicy; + public string ContainerName { get; } - public ThroughputProperties? ThroughputProperties { get; } = throughputProperties; + public string? PartitionKeyPath { get; } - public int DefaultTimeToLive { get; } = defaultTimeToLive; + public List? PartitionKeyPaths { get; } - public bool SyncContainerProperties { get; } = syncContainerProperties; + public UniqueKeyPolicy? UniqueKeyPolicy { get; } - public ChangeFeedOptions? ChangeFeedOptions { get; } = changeFeedOptions; + public ThroughputProperties? ThroughputProperties { get; } - public bool UseStrictTypeChecking { get; } = useStrictTypeChecking; -} \ No newline at end of file + public int DefaultTimeToLive { get; } + + public bool SyncContainerProperties { get; } + + public ChangeFeedOptions? ChangeFeedOptions { get; } + + public bool UseStrictTypeChecking { get; } +} diff --git a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosItemConfigurationProvider.cs b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosItemConfigurationProvider.cs index da4590f18..3d0463dc0 100644 --- a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosItemConfigurationProvider.cs +++ b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosItemConfigurationProvider.cs @@ -40,20 +40,33 @@ private ItemConfiguration AddOptions(Type itemType) var containerName = containerNameProvider.GetContainerName(itemType); var partitionKeyPath = cosmosPartitionKeyPathProvider.GetPartitionKeyPath(itemType); + var partitionKeyPaths = cosmosPartitionKeyPathProvider.GetPartitionKeyPaths(itemType).ToList(); UniqueKeyPolicy? uniqueKeyPolicy = cosmosUniqueKeyPolicyProvider.GetUniqueKeyPolicy(itemType); var timeToLive = containerDefaultTimeToLiveProvider.GetDefaultTimeToLive(itemType); var sync = syncContainerPropertiesProvider.GetWhetherToSyncContainerProperties(itemType); ThroughputProperties? throughputProperties = cosmosThroughputProvider.GetThroughputProperties(itemType); var useStrictTypeChecking = cosmosStrictTypeCheckingProvider.UseStrictTypeChecking(itemType); - return new( - itemType, - containerName, - partitionKeyPath, - uniqueKeyPolicy, - throughputProperties, - timeToLive, - sync, - useStrictTypeChecking: useStrictTypeChecking); + if(partitionKeyPaths != null && partitionKeyPaths.Any()) + return new( + itemType, + containerName, + partitionKeyPaths, + uniqueKeyPolicy, + throughputProperties, + timeToLive, + sync, + useStrictTypeChecking: useStrictTypeChecking); + else + return new( + itemType, + containerName, + partitionKeyPath, + uniqueKeyPolicy, + throughputProperties, + timeToLive, + sync, + useStrictTypeChecking: useStrictTypeChecking); + } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosPartitionKeyPathProvider.cs b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosPartitionKeyPathProvider.cs index d3e2fca60..202f18dd6 100644 --- a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosPartitionKeyPathProvider.cs +++ b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosPartitionKeyPathProvider.cs @@ -1,6 +1,7 @@ // Copyright (c) David Pine. All rights reserved. // Licensed under the MIT License. + namespace Microsoft.Azure.CosmosRepository.Providers; /// @@ -29,4 +30,33 @@ public string GetPartitionKeyPath(Type itemType) ? partitionKeyPathAttribute.Path : "/id"; } + + public IEnumerable GetPartitionKeyPaths() where TItem : IItem => GetPartitionKeyPaths(typeof(TItem)); + + public IEnumerable GetPartitionKeyPaths(Type itemType) + { + Type attributeType = typeof(HierarchicalPartitionKeysPathAttribute); + + ContainerOptionsBuilder? optionsBuilder = _options.Value.GetContainerOptions(itemType); + + if (optionsBuilder?.PartitionKeys?.Any() == true) + { + return optionsBuilder.PartitionKeys; + } + + if (Attribute.GetCustomAttribute(itemType, attributeType) is HierarchicalPartitionKeysPathAttribute attribute) + { + var paths = new List { attribute.Path, attribute.SubPathOne }; + + if (attribute.SubPathTwo is not null) + { + paths.Add(attribute.SubPathTwo); + } + + return paths; + } + + return new[] { "/id" }; + } + } diff --git a/src/Microsoft.Azure.CosmosRepository/Providers/ICosmosPartitionKeyPathProvider.cs b/src/Microsoft.Azure.CosmosRepository/Providers/ICosmosPartitionKeyPathProvider.cs index 618a47bbc..b9090707d 100644 --- a/src/Microsoft.Azure.CosmosRepository/Providers/ICosmosPartitionKeyPathProvider.cs +++ b/src/Microsoft.Azure.CosmosRepository/Providers/ICosmosPartitionKeyPathProvider.cs @@ -17,4 +17,13 @@ interface ICosmosPartitionKeyPathProvider string GetPartitionKeyPath() where TItem : IItem; string GetPartitionKeyPath(Type itemType); + + /// + /// Gets the partition key paths for a given type. + /// + /// The item for which the partition keys paths corresponds. + /// A string value representing the partition key path, i.e.; "/partion" + IEnumerable GetPartitionKeyPaths() where TItem : IItem; + + IEnumerable GetPartitionKeyPaths(Type itemType); } diff --git a/src/Microsoft.Azure.CosmosRepository/Services/DefaultCosmosContainerService.cs b/src/Microsoft.Azure.CosmosRepository/Services/DefaultCosmosContainerService.cs index 6adad2b61..8b83a7570 100644 --- a/src/Microsoft.Azure.CosmosRepository/Services/DefaultCosmosContainerService.cs +++ b/src/Microsoft.Azure.CosmosRepository/Services/DefaultCosmosContainerService.cs @@ -46,6 +46,7 @@ private async Task GetContainerAsync(Type itemType, bool forceContain ? itemConfiguration.ContainerName : _options.ContainerId, PartitionKeyPath = itemConfiguration.PartitionKeyPath, + PartitionKeyPaths = itemConfiguration.PartitionKeyPaths, UniqueKeyPolicy = itemConfiguration.UniqueKeyPolicy ?? new(), DefaultTimeToLive = itemConfiguration.DefaultTimeToLive }; From 0f5f81f87ad42180c560a9413196642b9d1ed7eb Mon Sep 17 00:00:00 2001 From: Oleksandr Kyselov Date: Mon, 5 Feb 2024 22:16:42 +0100 Subject: [PATCH 03/27] Adding interface hierarchical strategy logic --- .../Items/EventItem.cs | 4 +- .../Stores/DefaultEventStore.Read.cs | 20 +++--- .../Attributes/PartitionKeyPathAttribute.cs | 12 ++-- .../Builders/ContainerOptionsBuilder.cs | 44 ++----------- .../IHierarchialItem.cs | 15 +++++ src/Microsoft.Azure.CosmosRepository/IItem.cs | 9 ++- src/Microsoft.Azure.CosmosRepository/Item.cs | 18 +++-- .../Logging/LoggerExtensions.cs | 14 ++++ .../Logging/LoggerMessageDefinitions.cs | 15 +++++ .../Options/ItemConfiguration.cs | 29 +-------- .../DefaultCosmosItemConfigurationProvider.cs | 32 +++------ .../DefaultCosmosPartitionKeyPathProvider.cs | 43 +++--------- .../ICosmosPartitionKeyPathProvider.cs | 16 +---- .../Repositories/DefaultRepository.Batch.cs | 61 ++++++++++++----- .../Repositories/DefaultRepository.Create.cs | 6 +- .../Repositories/DefaultRepository.Delete.cs | 2 +- .../Repositories/DefaultRepository.Read.cs | 65 +++++++++++++++++-- .../Repositories/DefaultRepository.cs | 45 +++++++++++++ .../Repositories/IReadOnlyRepository.cs | 54 +++++++++++++++ .../Services/DefaultCosmosContainerService.cs | 7 +- ...aultCosmosPartitionKeyPathProviderTests.cs | 6 +- 21 files changed, 322 insertions(+), 195 deletions(-) create mode 100644 src/Microsoft.Azure.CosmosRepository/IHierarchialItem.cs diff --git a/src/Microsoft.Azure.CosmosEventSourcing/Items/EventItem.cs b/src/Microsoft.Azure.CosmosEventSourcing/Items/EventItem.cs index 0b5e3a729..000e3d001 100644 --- a/src/Microsoft.Azure.CosmosEventSourcing/Items/EventItem.cs +++ b/src/Microsoft.Azure.CosmosEventSourcing/Items/EventItem.cs @@ -56,9 +56,9 @@ public DomainEvent DomainEvent public string Type { get; set; } /// - /// The value used to partition the event. + /// The values used to partition the event. /// - public string PartitionKey { get; set; } = null!; + public string[] PartitionKeys { get; set; } = null!; /// /// The name of the event stored. diff --git a/src/Microsoft.Azure.CosmosEventSourcing/Stores/DefaultEventStore.Read.cs b/src/Microsoft.Azure.CosmosEventSourcing/Stores/DefaultEventStore.Read.cs index e88f78510..b2fa10c61 100644 --- a/src/Microsoft.Azure.CosmosEventSourcing/Stores/DefaultEventStore.Read.cs +++ b/src/Microsoft.Azure.CosmosEventSourcing/Stores/DefaultEventStore.Read.cs @@ -4,9 +4,9 @@ using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; +using Microsoft.Azure.Cosmos; using Microsoft.Azure.CosmosEventSourcing.Aggregates; using Microsoft.Azure.CosmosEventSourcing.Exceptions; -using Microsoft.Azure.CosmosEventSourcing.Extensions; using Microsoft.Azure.CosmosRepository.Paging; namespace Microsoft.Azure.CosmosEventSourcing.Stores; @@ -15,8 +15,7 @@ internal partial class DefaultEventStore { public ValueTask> ReadAsync(string partitionKey, CancellationToken cancellationToken = default) => - readOnlyRepository.GetAsync( - x => x.PartitionKey == partitionKey, + readOnlyRepository.GetAsync(new PartitionKey(partitionKey), cancellationToken); public async ValueTask ReadAggregateAsync( @@ -25,7 +24,7 @@ public async ValueTask ReadAggregateAsync( where TAggregateRoot : IAggregateRoot { IEnumerable events = await readOnlyRepository.GetAsync( - x => x.PartitionKey == partitionKey, + new PartitionKey(partitionKey), cancellationToken); var payloads = events @@ -46,7 +45,7 @@ public async ValueTask ReadAggregateAsync( CancellationToken cancellationToken = default) where TAggregateRoot : IAggregateRoot { IEnumerable events = await readOnlyRepository.GetAsync( - x => x.PartitionKey == partitionKey, + new PartitionKey(partitionKey), cancellationToken); return rootMapper.MapTo(events); @@ -57,9 +56,8 @@ public ValueTask> ReadAsync( Expression> predicate, CancellationToken cancellationToken = default) => readOnlyRepository.GetAsync( - predicate.Compose( - x => x.PartitionKey == partitionKey, - Expression.AndAlso), + new PartitionKey(partitionKey), + predicate, cancellationToken); public async IAsyncEnumerable StreamAsync( @@ -69,13 +67,11 @@ public async IAsyncEnumerable StreamAsync( { string? token = null; - Expression> expression = eventSource => - eventSource.PartitionKey == partitionKey; - do { IPage page = await readOnlyRepository.PageAsync( - expression, + new PartitionKey(partitionKey), + predicate: null, chunkSize, token, cancellationToken: cancellationToken); diff --git a/src/Microsoft.Azure.CosmosRepository/Attributes/PartitionKeyPathAttribute.cs b/src/Microsoft.Azure.CosmosRepository/Attributes/PartitionKeyPathAttribute.cs index 97123322e..e583c2496 100644 --- a/src/Microsoft.Azure.CosmosRepository/Attributes/PartitionKeyPathAttribute.cs +++ b/src/Microsoft.Azure.CosmosRepository/Attributes/PartitionKeyPathAttribute.cs @@ -9,20 +9,22 @@ namespace Microsoft.Azure.CosmosRepository.Attributes; /// conjunction with a on the property /// whose value will act as the partition key. Partition key paths should start with "/", /// for example "/partition". For more information, -/// see https://docs.microsoft.com/azure/cosmos-db/partitioning-overview. +/// see https://docs.microsoft.com/azure/cosmos-db/partitioning-overview and +/// https://learn.microsoft.com/en-us/azure/cosmos-db/hierarchical-partition-keys /// /// /// By default, "/id" is used. /// /// -/// Constructor accepting the of the partition key for a given . +/// Constructor accepting the of the partition keys for a given . /// -/// +/// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] -public sealed class PartitionKeyPathAttribute(string path) : Attribute +public sealed class PartitionKeyPathAttribute(params string[] paths) : Attribute { /// /// Gets the path of the parition key. /// - public string Path { get; } = path ?? throw new ArgumentNullException(nameof(path), "A path is required."); + public string[] Paths { get; } = paths != null && paths.Length > 1 ? paths : throw new ArgumentNullException(nameof(paths), "At least one path is required."); } + diff --git a/src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs b/src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs index 0b177b987..8e54cd5b0 100644 --- a/src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs +++ b/src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs @@ -22,15 +22,10 @@ public class ContainerOptionsBuilder(Type type) /// internal string? Name { get; private set; } - /// - /// The partition key for the container. - /// - internal string? PartitionKey { get; private set; } - /// /// The partition keys for the container. /// - internal IEnumerable? PartitionKeys { get; private set; } + internal IList? PartitionKeys { get; private set; } /// /// The default time to live for a container. @@ -84,43 +79,12 @@ public ContainerOptionsBuilder WithContainer(string name) /// public ContainerOptionsBuilder WithPartitionKey(string partitionKey) { - PartitionKey = partitionKey ?? throw new ArgumentNullException(nameof(partitionKey)); + if(string.IsNullOrWhiteSpace(partitionKey)) throw new ArgumentNullException(nameof(partitionKey)); + PartitionKeys ??= []; + PartitionKeys.Add(partitionKey); return this; } - /// - /// Configures a hierarchical partition key structure for the container, consisting of a root key and optional sub-keys. - /// - /// The primary partition key. - /// The first level sub-partition key. - /// An optional second level sub-partition key. - /// The current instance of , enabling method chaining. - /// Thrown when either or is null or whitespace. - public ContainerOptionsBuilder WithHierarchicalPartitionKey(string partitionKey, string partitionSubKey1, string? partitionSubKey2 = null) - { - if (string.IsNullOrWhiteSpace(partitionKey)) - { - throw new ArgumentNullException(nameof(partitionKey), "Partition key cannot be null or whitespace."); - } - - if (string.IsNullOrWhiteSpace(partitionSubKey1)) - { - throw new ArgumentNullException(nameof(partitionSubKey1), "First sub-partition key cannot be null or whitespace."); - } - - var partitionKeys = new List { partitionKey, partitionSubKey1 }; - - if (partitionSubKey2 != null && string.IsNullOrWhiteSpace(partitionSubKey2)) - { - partitionKeys.Add(partitionSubKey2); - } - - PartitionKeys = partitionKeys; - - return this; - } - - /// /// Sets to true /// diff --git a/src/Microsoft.Azure.CosmosRepository/IHierarchialItem.cs b/src/Microsoft.Azure.CosmosRepository/IHierarchialItem.cs new file mode 100644 index 000000000..3d49dc77d --- /dev/null +++ b/src/Microsoft.Azure.CosmosRepository/IHierarchialItem.cs @@ -0,0 +1,15 @@ +// Copyright (c) David Pine. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Azure.CosmosRepository; + +/// +/// The base interface used for all repository hierarchical object or object graphs. +/// +public interface IHierarchialItem : IItem +{ + /// + /// Gets the item's PartitionKeys. This list is used to instantiate the Cosmos.PartitionKeys struct. + /// + IEnumerable PartitionKeys { get; } +} diff --git a/src/Microsoft.Azure.CosmosRepository/IItem.cs b/src/Microsoft.Azure.CosmosRepository/IItem.cs index adad0264e..8605e4c80 100644 --- a/src/Microsoft.Azure.CosmosRepository/IItem.cs +++ b/src/Microsoft.Azure.CosmosRepository/IItem.cs @@ -18,8 +18,13 @@ public interface IItem /// string Type { get; set; } + ///// + ///// Gets the item's PartitionKey. This string is used to instantiate the Cosmos.PartitionKey struct. + ///// + //string PartitionKey { get; } + /// - /// Gets the item's PartitionKey. This string is used to instantiate the Cosmos.PartitionKey struct. + /// Gets the item PartitionKeys. This string array is used to instantiate the Cosmos.PartitionKeys struct. /// - string PartitionKey { get; } + string[] PartitionKeys { get; } } diff --git a/src/Microsoft.Azure.CosmosRepository/Item.cs b/src/Microsoft.Azure.CosmosRepository/Item.cs index a3b08499e..d6e03ebc9 100644 --- a/src/Microsoft.Azure.CosmosRepository/Item.cs +++ b/src/Microsoft.Azure.CosmosRepository/Item.cs @@ -44,10 +44,10 @@ public abstract class Item : IItem public string Type { get; set; } /// - /// Gets the PartitionKey based on . + /// Gets the PartitionKeys based on . /// Implemented explicitly to keep out of Item API /// - string IItem.PartitionKey => GetPartitionKeyValue(); + string[] IItem.PartitionKeys => GetPartitionKeyValues(); /// /// Default constructor, assigns type name to property. @@ -56,10 +56,20 @@ public abstract class Item : IItem /// /// Gets the partition key value for the given type. - /// When overridden, be sure that the value corresponds - /// to the value, i.e.; "/partition" and "partition" + /// When overridden, be sure that the values correspond + /// to the values, i.e.; "/partition" and "partition" /// respectively. If these two values do not correspond an error will occur. /// /// The unless overridden by the subclass. protected virtual string GetPartitionKeyValue() => Id; + + /// + /// Gets the partition key values for the given type. + /// When overridden, be sure that the values correspond + /// to the values, i.e.; "/partition" and "partition" + /// respectively. + /// respectively. If all provided key values do not have a matching property with the equivalent name, an error will occur. + /// + /// The list with unless overridden by the subclass. + protected virtual string[] GetPartitionKeyValues() => new string[] { GetPartitionKeyValue() }; } diff --git a/src/Microsoft.Azure.CosmosRepository/Logging/LoggerExtensions.cs b/src/Microsoft.Azure.CosmosRepository/Logging/LoggerExtensions.cs index aac8cb234..28f1549b6 100644 --- a/src/Microsoft.Azure.CosmosRepository/Logging/LoggerExtensions.cs +++ b/src/Microsoft.Azure.CosmosRepository/Logging/LoggerExtensions.cs @@ -19,6 +19,13 @@ public static void LogPointReadStarted( string partitionKey) where TItem : IItem => LoggerMessageDefinitions.PointReadStarted(logger, typeof(TItem).Name, id, partitionKey, null!); + //Info Logger Extensions + public static void LogPointReadStarted( + this ILogger logger, + string id, + string[] partitionKeys) where TItem : IItem => + LoggerMessageDefinitions.HierarchicalPointReadStarted(logger, typeof(TItem).Name, id, partitionKeys, null!); + public static void LogPointReadExecuted( this ILogger logger, double ruCharge) where TItem : IItem => @@ -41,4 +48,11 @@ public static void LogItemNotFoundHandled( string partitionKey, CosmosException e) where TItem : IItem => LoggerMessageDefinitions.ItemNotFoundHandled(logger, typeof(TItem).Name, id, partitionKey, e); + + public static void LogItemNotFoundHandled( + this ILogger logger, + string id, + string[] partitionKeys, + CosmosException e) where TItem : IItem => + LoggerMessageDefinitions.HierarchicalItemNotFoundHandled(logger, typeof(TItem).Name, id, partitionKeys, e); } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Logging/LoggerMessageDefinitions.cs b/src/Microsoft.Azure.CosmosRepository/Logging/LoggerMessageDefinitions.cs index 0b5a28616..6a1a6a1a3 100644 --- a/src/Microsoft.Azure.CosmosRepository/Logging/LoggerMessageDefinitions.cs +++ b/src/Microsoft.Azure.CosmosRepository/Logging/LoggerMessageDefinitions.cs @@ -20,6 +20,14 @@ internal static class LoggerMessageDefinitions "Point read started for item type {CosmosItemType} with id {CosmosItemId} and partitionKey {CosmosItemPartitionKey}" ); + // Info Definitions + internal static readonly Action HierarchicalPointReadStarted = + LoggerMessage.Define( + LogLevel.Information, + EventIds.CosmosPointReadStarted, + "Point read started for item type {CosmosItemType} with id {CosmosItemId} and partitionKeys {CosmosItemPartitionKeys}" + ); + internal static readonly Action PointReadExecuted = LoggerMessage.Define( LogLevel.Information, @@ -46,4 +54,11 @@ internal static class LoggerMessageDefinitions EventIds.ItemNotFoundHandled, "CosmosException Status Code 404 handled for item of type {CosmosItemType} with {CosmosItemId} and partition key {CosmosItemPartitionKey}" ); + + internal static readonly Action HierarchicalItemNotFoundHandled = + LoggerMessage.Define( + LogLevel.Information, + EventIds.ItemNotFoundHandled, + "CosmosException Status Code 404 handled for item of type {CosmosItemType} with {CosmosItemId} and partition keys {CosmosItemPartitionKeys}" + ); } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Options/ItemConfiguration.cs b/src/Microsoft.Azure.CosmosRepository/Options/ItemConfiguration.cs index 6b7f4044c..8b8b6939c 100644 --- a/src/Microsoft.Azure.CosmosRepository/Options/ItemConfiguration.cs +++ b/src/Microsoft.Azure.CosmosRepository/Options/ItemConfiguration.cs @@ -9,30 +9,7 @@ internal class ItemConfiguration public ItemConfiguration( Type type, string containerName, - string partitionKeyPath, - UniqueKeyPolicy? uniqueKeyPolicy = null, - ThroughputProperties? throughputProperties = null, - int defaultTimeToLive = -1, - bool syncContainerProperties = false, - ChangeFeedOptions? changeFeedOptions = null, - bool useStrictTypeChecking = true) - { - Type = type; - ContainerName = containerName; - PartitionKeyPath = partitionKeyPath; - UniqueKeyPolicy = uniqueKeyPolicy; - ThroughputProperties = throughputProperties; - DefaultTimeToLive = defaultTimeToLive; - SyncContainerProperties = syncContainerProperties; - ChangeFeedOptions = changeFeedOptions; - UseStrictTypeChecking = useStrictTypeChecking; - } - - // New additional constructor - public ItemConfiguration( - Type type, - string containerName, - List partitionKeyPaths, + string[] partitionKeyPaths, UniqueKeyPolicy? uniqueKeyPolicy = null, ThroughputProperties? throughputProperties = null, int defaultTimeToLive = -1, @@ -56,9 +33,7 @@ public ItemConfiguration( public string ContainerName { get; } - public string? PartitionKeyPath { get; } - - public List? PartitionKeyPaths { get; } + public string[] PartitionKeyPaths { get; } public UniqueKeyPolicy? UniqueKeyPolicy { get; } diff --git a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosItemConfigurationProvider.cs b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosItemConfigurationProvider.cs index 3d0463dc0..3704f29de 100644 --- a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosItemConfigurationProvider.cs +++ b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosItemConfigurationProvider.cs @@ -39,34 +39,22 @@ private ItemConfiguration AddOptions(Type itemType) itemType.IsItem(); var containerName = containerNameProvider.GetContainerName(itemType); - var partitionKeyPath = cosmosPartitionKeyPathProvider.GetPartitionKeyPath(itemType); - var partitionKeyPaths = cosmosPartitionKeyPathProvider.GetPartitionKeyPaths(itemType).ToList(); + var partitionKeyPaths = cosmosPartitionKeyPathProvider.GetPartitionKeyPaths(itemType); UniqueKeyPolicy? uniqueKeyPolicy = cosmosUniqueKeyPolicyProvider.GetUniqueKeyPolicy(itemType); var timeToLive = containerDefaultTimeToLiveProvider.GetDefaultTimeToLive(itemType); var sync = syncContainerPropertiesProvider.GetWhetherToSyncContainerProperties(itemType); ThroughputProperties? throughputProperties = cosmosThroughputProvider.GetThroughputProperties(itemType); var useStrictTypeChecking = cosmosStrictTypeCheckingProvider.UseStrictTypeChecking(itemType); - if(partitionKeyPaths != null && partitionKeyPaths.Any()) - return new( - itemType, - containerName, - partitionKeyPaths, - uniqueKeyPolicy, - throughputProperties, - timeToLive, - sync, - useStrictTypeChecking: useStrictTypeChecking); - else - return new( - itemType, - containerName, - partitionKeyPath, - uniqueKeyPolicy, - throughputProperties, - timeToLive, - sync, - useStrictTypeChecking: useStrictTypeChecking); + return new( + itemType, + containerName, + partitionKeyPaths, + uniqueKeyPolicy, + throughputProperties, + timeToLive, + sync, + useStrictTypeChecking: useStrictTypeChecking); } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosPartitionKeyPathProvider.cs b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosPartitionKeyPathProvider.cs index 202f18dd6..0cc918521 100644 --- a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosPartitionKeyPathProvider.cs +++ b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosPartitionKeyPathProvider.cs @@ -11,52 +11,25 @@ class DefaultCosmosPartitionKeyPathProvider(IOptions options) private readonly IOptions _options = options ?? throw new ArgumentNullException(nameof(options)); /// - public string GetPartitionKeyPath() where TItem : IItem => - GetPartitionKeyPath(typeof(TItem)); + public IEnumerable GetPartitionKeyPaths() where TItem : IItem => + GetPartitionKeyPaths(typeof(TItem)); - public string GetPartitionKeyPath(Type itemType) + public IEnumerable GetPartitionKeyPaths(Type itemType) { Type attributeType = typeof(PartitionKeyPathAttribute); ContainerOptionsBuilder? optionsBuilder = _options.Value.GetContainerOptions(itemType); - if (optionsBuilder is { } && string.IsNullOrWhiteSpace(optionsBuilder.PartitionKey) is false) + if (optionsBuilder is { } && optionsBuilder.PartitionKeys != null && optionsBuilder.PartitionKeys.Any() && optionsBuilder.PartitionKeys.All(x => !string.IsNullOrEmpty(x))) { - return optionsBuilder.PartitionKey!; + return optionsBuilder.PartitionKeys; } return Attribute.GetCustomAttribute( itemType, attributeType) is PartitionKeyPathAttribute partitionKeyPathAttribute - ? partitionKeyPathAttribute.Path - : "/id"; - } - - public IEnumerable GetPartitionKeyPaths() where TItem : IItem => GetPartitionKeyPaths(typeof(TItem)); - - public IEnumerable GetPartitionKeyPaths(Type itemType) - { - Type attributeType = typeof(HierarchicalPartitionKeysPathAttribute); - - ContainerOptionsBuilder? optionsBuilder = _options.Value.GetContainerOptions(itemType); - - if (optionsBuilder?.PartitionKeys?.Any() == true) - { - return optionsBuilder.PartitionKeys; - } - - if (Attribute.GetCustomAttribute(itemType, attributeType) is HierarchicalPartitionKeysPathAttribute attribute) - { - var paths = new List { attribute.Path, attribute.SubPathOne }; - - if (attribute.SubPathTwo is not null) - { - paths.Add(attribute.SubPathTwo); - } - - return paths; - } - - return new[] { "/id" }; + ? partitionKeyPathAttribute.Paths + : ["/id"]; } + } diff --git a/src/Microsoft.Azure.CosmosRepository/Providers/ICosmosPartitionKeyPathProvider.cs b/src/Microsoft.Azure.CosmosRepository/Providers/ICosmosPartitionKeyPathProvider.cs index b9090707d..ed6b187ec 100644 --- a/src/Microsoft.Azure.CosmosRepository/Providers/ICosmosPartitionKeyPathProvider.cs +++ b/src/Microsoft.Azure.CosmosRepository/Providers/ICosmosPartitionKeyPathProvider.cs @@ -9,21 +9,11 @@ namespace Microsoft.Azure.CosmosRepository.Providers; /// interface ICosmosPartitionKeyPathProvider { - /// - /// Gets the partition key path for a given type. - /// - /// The item for which the partition key path corresponds. - /// A string value representing the partition key path, i.e.; "/partion" - string GetPartitionKeyPath() where TItem : IItem; - - string GetPartitionKeyPath(Type itemType); - /// /// Gets the partition key paths for a given type. /// /// The item for which the partition keys paths corresponds. - /// A string value representing the partition key path, i.e.; "/partion" - IEnumerable GetPartitionKeyPaths() where TItem : IItem; - - IEnumerable GetPartitionKeyPaths(Type itemType); + /// A string array representing the partition key paths, i.e.; "/partion" + string[] GetPartitionKeyPaths() where TItem : IItem; + string[] GetPartitionKeyPaths(Type itemType); } diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Batch.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Batch.cs index 467c34ca2..738c7b240 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Batch.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Batch.cs @@ -2,6 +2,8 @@ // Licensed under the MIT License. // ReSharper disable once CheckNamespace +using Microsoft.Azure.CosmosRepository.Extensions; + namespace Microsoft.Azure.CosmosRepository; internal partial class DefaultRepository @@ -13,11 +15,22 @@ public async ValueTask UpdateAsBatchAsync( { var list = items.ToList(); - var partitionKey = GetPartitionKeyValue(list); + PartitionKey partitionKey = GetPartitionKey(list); + + await UpdateAsBatchAsync(items, partitionKey, cancellationToken); + } + + /// + public async ValueTask UpdateAsBatchAsync( + IEnumerable items, + PartitionKey partitionKey, + CancellationToken cancellationToken = default) + { + var list = items.ToList(); Container container = await containerProvider.GetContainerAsync(); - TransactionalBatch batch = container.CreateTransactionalBatch(new PartitionKey(partitionKey)); + TransactionalBatch batch = container.CreateTransactionalBatch(partitionKey); foreach (TItem item in list) { @@ -46,11 +59,22 @@ public async ValueTask CreateAsBatchAsync( { var list = items.ToList(); - var partitionKey = GetPartitionKeyValue(list); + PartitionKey partitionKey = GetPartitionKey(list); + + await CreateAsBatchAsync(items, partitionKey, cancellationToken); + } + + /// + public async ValueTask CreateAsBatchAsync( + IEnumerable items, + PartitionKey partitionKey, + CancellationToken cancellationToken = default) + { + var list = items.ToList(); Container container = await containerProvider.GetContainerAsync(); - TransactionalBatch batch = container.CreateTransactionalBatch(new PartitionKey(partitionKey)); + TransactionalBatch batch = container.CreateTransactionalBatch(partitionKey); foreach (TItem item in list) { @@ -65,17 +89,30 @@ public async ValueTask CreateAsBatchAsync( } } + /// public async ValueTask DeleteAsBatchAsync( IEnumerable items, CancellationToken cancellationToken = default) { var list = items.ToList(); - var partitionKey = GetPartitionKeyValue(list); + PartitionKey partitionKey = GetPartitionKey(list); + + await DeleteAsBatchAsync(items, partitionKey, cancellationToken); + + } + + /// + public async ValueTask DeleteAsBatchAsync( + IEnumerable items, + PartitionKey partitionKey, + CancellationToken cancellationToken = default) + { + var list = items.ToList(); Container container = await containerProvider.GetContainerAsync(); - TransactionalBatch batch = container.CreateTransactionalBatch(new PartitionKey(partitionKey)); + TransactionalBatch batch = container.CreateTransactionalBatch(partitionKey); foreach (TItem item in list) { @@ -89,16 +126,4 @@ public async ValueTask DeleteAsBatchAsync( throw new BatchOperationException(response); } } - - private static string GetPartitionKeyValue(List items) - { - if (!items.Any()) - { - throw new ArgumentException( - "Unable to perform batch operation with no items", - nameof(items)); - } - - return items[0].PartitionKey; - } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Create.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Create.cs index 3c1e0671d..3b3c9fe9e 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Create.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Create.cs @@ -18,10 +18,12 @@ public async ValueTask CreateAsync( if (value is IItemWithTimeStamps { CreatedTimeUtc: null } valueWithTimestamps) { valueWithTimestamps.CreatedTimeUtc = DateTime.UtcNow; - } + } + + PartitionKey partitionKey = GetPartitionKey(value); ItemResponse response = - await container.CreateItemAsync(value, new PartitionKey(value.PartitionKey), + await container.CreateItemAsync(value, partitionKey, cancellationToken: cancellationToken) .ConfigureAwait(false); diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Delete.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Delete.cs index f5c8bc3bc..dd67bb7d3 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Delete.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Delete.cs @@ -11,7 +11,7 @@ internal sealed partial class DefaultRepository public ValueTask DeleteAsync( TItem value, CancellationToken cancellationToken = default) => - DeleteAsync(value.Id, value.PartitionKey, cancellationToken); + DeleteAsync(value.Id, new PartitionKeyBuilder().Build(value.PartitionKeys), cancellationToken); /// public ValueTask DeleteAsync( diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs index 00438f95c..f34c82a01 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs @@ -5,7 +5,7 @@ namespace Microsoft.Azure.CosmosRepository; internal sealed partial class DefaultRepository -{ +{ /// public async ValueTask TryGetAsync( string id, @@ -21,6 +21,22 @@ internal sealed partial class DefaultRepository logger.LogItemNotFoundHandled(id, partitionKeyValue ?? id, e); return default; } + } + + public async ValueTask TryGetAsync( + string id, + string[] partitionKeyValues, + CancellationToken cancellationToken = default) + { + try + { + return await GetAsync(id, partitionKeyValues, cancellationToken); + } + catch (CosmosException e) when (e.StatusCode is HttpStatusCode.NotFound) + { + logger.LogItemNotFoundHandled(id, partitionKeyValues ?? [id], e); + return default; + } } /// @@ -28,7 +44,13 @@ public ValueTask GetAsync( string id, string? partitionKeyValue = null, CancellationToken cancellationToken = default) => - GetAsync(id, new PartitionKey(partitionKeyValue ?? id), cancellationToken); + GetAsync(id, new PartitionKey(partitionKeyValue ?? id), cancellationToken); + + public ValueTask GetAsync( + string id, + string[] partitionKeyValues, + CancellationToken cancellationToken = default) => + GetAsync(id, new PartitionKeyBuilder().Build(partitionKeyValues), cancellationToken); /// public async ValueTask GetAsync( @@ -56,18 +78,47 @@ await container.ReadItemAsync(id, partitionKey, cancellationToken: cancel logger.LogItemRead(item); return repositoryExpressionProvider.CheckItem(item); - } + } - /// public async ValueTask> GetAsync( - Expression> predicate, + PartitionKey partitionKey, CancellationToken cancellationToken = default) { Container container = - await containerProvider.GetContainerAsync().ConfigureAwait(false); + await containerProvider.GetContainerAsync().ConfigureAwait(false); + + IQueryable query = container.GetItemLinqQueryable( + requestOptions: new QueryRequestOptions() { PartitionKey = partitionKey }, + linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions); + + logger.LogQueryConstructed(query); + + (IEnumerable items, var charge) = + await cosmosQueryableProcessor.IterateAsync(query, cancellationToken); + + logger.LogQueryExecuted(query, charge); + + return items; + } + + public async ValueTask> GetAsync( + Expression> predicate, + PartitionKey partitionKey = default, + CancellationToken cancellationToken = default) + { + Container container = + await containerProvider.GetContainerAsync().ConfigureAwait(false); + + var requestOptions = new QueryRequestOptions(); + + if (partitionKey != default) + { + requestOptions.PartitionKey = partitionKey; + } IQueryable query = - container.GetItemLinqQueryable( + container.GetItemLinqQueryable( + requestOptions: requestOptions, linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions) .Where(repositoryExpressionProvider.Build(predicate)); diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs index ca09bc816..723a1f1a2 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs @@ -62,4 +62,49 @@ await iterator.ReadNextAsync(cancellationToken) return (results, charge, continuationToken); } + + internal static PartitionKey GetPartitionKey(List items) + { + if (!items.Any()) + { + throw new ArgumentException( + "Unable to perform batch operation with no items", + nameof(items)); + } + return GetPartitionKey(items[0]); + } + + internal static PartitionKey GetPartitionKey(string[] values, string? defaultValue = null) + { + var builder = new PartitionKeyBuilder(); + if (values == null || values.Length == 0) + { + return !string.IsNullOrEmpty(defaultValue) ? new PartitionKey(defaultValue) : default; + } + + foreach (var value in values) + { + builder.Add(value); + } + + return builder.Build(); + } + + internal static PartitionKey GetPartitionKey(string value) + { + return new PartitionKey(value); + } + + + internal static PartitionKey GetPartitionKey(TItem item) + { + if (item == null) + { + throw new ArgumentException( + "Unable to perform operation with null item", + nameof(item)); + } + + return GetPartitionKey(item.PartitionKeys); + } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/IReadOnlyRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/IReadOnlyRepository.cs index 222596d26..b4eb25d7d 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/IReadOnlyRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/IReadOnlyRepository.cs @@ -39,6 +39,18 @@ public interface IReadOnlyRepository where TItem : IItem string? partitionKeyValue = null, CancellationToken cancellationToken = default); + //TODO: Write doc + ValueTask TryGetAsync( + string id, + IEnumerable partitionKeyValues, + CancellationToken cancellationToken = default); + + //TODO: Should I implement this? + //ValueTask TryGetAsync( + // string id, + // PartitionKey partitionKey, + // CancellationToken cancellationToken = default); + /// /// Gets the implementation class instance as a that corresponds to the given . /// @@ -54,6 +66,12 @@ ValueTask GetAsync( string? partitionKeyValue = null, CancellationToken cancellationToken = default); + //TODO: Write doc + ValueTask GetAsync( + string id, + IEnumerable partitionKeyValues, + CancellationToken cancellationToken = default); + /// /// Gets the implementation class instance as a that corresponds to the given . /// @@ -69,6 +87,11 @@ ValueTask GetAsync( PartitionKey partitionKey, CancellationToken cancellationToken = default); + //TODO: Write doc + ValueTask> GetAsync( + PartitionKey partitionKey, + CancellationToken cancellationToken = default); + /// /// Gets an collection of /// implementation classes that match the given . @@ -83,6 +106,12 @@ ValueTask> GetAsync( Expression> predicate, CancellationToken cancellationToken = default); + //TODO: Write doc + ValueTask> GetAsync( + PartitionKey partitionKey, + Expression> predicate, + CancellationToken cancellationToken = default); + /// /// Gets an collection of /// by the given Cosmos SQL query @@ -142,6 +171,12 @@ ValueTask ExistsAsync( Expression> predicate, CancellationToken cancellationToken = default); + //TODO: Write doc + ValueTask ExistsAsync( + PartitionKey partitionKey, + Expression> predicate, + CancellationToken cancellationToken = default); + /// /// Queries cosmos DB to obtain the count of items. /// @@ -166,6 +201,12 @@ ValueTask CountAsync( Expression> predicate, CancellationToken cancellationToken = default); + //TODO: Write doc + ValueTask CountAsync( + PartitionKey partitionKey, + Expression> predicate, + CancellationToken cancellationToken = default); + /// /// Offers a load more paging implementation for infinite scroll scenarios. /// Allows for efficient paging making use of cosmos DBs continuation tokens, making this implementation cost effective. @@ -184,6 +225,15 @@ ValueTask> PageAsync( bool returnTotal = false, CancellationToken cancellationToken = default); + //TODO: Write doc + ValueTask> PageAsync( + PartitionKey partitionKey, + Expression>? predicate = null, + int pageSize = 25, + string? continuationToken = null, + bool returnTotal = false, + CancellationToken cancellationToken = default); + /// /// Get items based on a specification. /// The specification is used to define which filters are used, the order of the search results and how they are paged. @@ -214,12 +264,14 @@ ValueTask QueryAsync( /// An of s /// This method makes use of Cosmos DB's continuation tokens for efficient, cost effective paging utilizing low RUs ValueTask> PageAsync( + PartitionKey? partitionKey = null, Expression>? predicate = null, int pageNumber = 1, int pageSize = 25, bool returnTotal = false, CancellationToken cancellationToken = default); + #if NET7_0_OR_GREATER /// /// Wraps the existing paging support to return an @@ -231,6 +283,7 @@ ValueTask> PageAsync( /// An where T is . /// This method makes use of Cosmos DB's continuation tokens for efficient, cost effective paging utilizing low RUs async IAsyncEnumerable PageAsync( + PartitionKey? partitionKey = null, Expression>? predicate = null, int limit = 1_000, [EnumeratorCancellation] CancellationToken cancellationToken = default) @@ -243,6 +296,7 @@ async IAsyncEnumerable PageAsync( && cancellationToken.IsCancellationRequested is false) { IPageQueryResult page = await PageAsync( + partitionKey, predicate, pageNumber: ++ currentPage, 25, diff --git a/src/Microsoft.Azure.CosmosRepository/Services/DefaultCosmosContainerService.cs b/src/Microsoft.Azure.CosmosRepository/Services/DefaultCosmosContainerService.cs index 8b83a7570..701725649 100644 --- a/src/Microsoft.Azure.CosmosRepository/Services/DefaultCosmosContainerService.cs +++ b/src/Microsoft.Azure.CosmosRepository/Services/DefaultCosmosContainerService.cs @@ -45,12 +45,15 @@ private async Task GetContainerAsync(Type itemType, bool forceContain Id = _options.ContainerPerItemType ? itemConfiguration.ContainerName : _options.ContainerId, - PartitionKeyPath = itemConfiguration.PartitionKeyPath, - PartitionKeyPaths = itemConfiguration.PartitionKeyPaths, UniqueKeyPolicy = itemConfiguration.UniqueKeyPolicy ?? new(), DefaultTimeToLive = itemConfiguration.DefaultTimeToLive }; + if (itemConfiguration.PartitionKeyPaths.Length > 1) + containerProperties.PartitionKeyPaths = itemConfiguration.PartitionKeyPaths; + else + containerProperties.PartitionKeyPath = containerProperties.PartitionKeyPaths[0]; + Container container = _options.IsAutoResourceCreationIfNotExistsEnabled ? await database.CreateContainerIfNotExistsAsync( diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/Providers/DefaultCosmosPartitionKeyPathProviderTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/Providers/DefaultCosmosPartitionKeyPathProviderTests.cs index 79b21d974..b1eac85d5 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/Providers/DefaultCosmosPartitionKeyPathProviderTests.cs +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/Providers/DefaultCosmosPartitionKeyPathProviderTests.cs @@ -24,7 +24,7 @@ public void CosmosCosmosPartitionKeyPathProviderCorrectlyGetsPathWhenOptionsAreD ICosmosPartitionKeyPathProvider provider = new DefaultCosmosPartitionKeyPathProvider(_options.Object); - var path = provider.GetPartitionKeyPath(); + var path = provider.GetPartitionKeyPaths(); Assert.Equal("/firstName", path); } @@ -35,7 +35,7 @@ public void CosmosCosmosPartitionKeyPathProviderCorrectlyGetsPathWhenOptionsAreD ICosmosPartitionKeyPathProvider provider = new DefaultCosmosPartitionKeyPathProvider(_options.Object); - var path = provider.GetPartitionKeyPath(); + var path = provider.GetPartitionKeyPaths(); Assert.Equal("/email", path); } @@ -44,7 +44,7 @@ public void CosmosPartitionKeyPathProviderCorrectlyGetsPathWhenAttributeIsDefine { ICosmosPartitionKeyPathProvider provider = new DefaultCosmosPartitionKeyPathProvider(_options.Object); - var path = provider.GetPartitionKeyPath(); + var path = provider.GetPartitionKeyPaths(); Assert.Equal("/pickles", path); Assert.Equal("[\"Hey, where's the chips?!\"]", new Cosmos.PartitionKey(((IItem)new PickleChipsItem()).PartitionKey).ToString()); } From 94595383279b0901f7a721c0fd96e19112e83bb6 Mon Sep 17 00:00:00 2001 From: Oleksandr Kyselov Date: Tue, 6 Feb 2024 22:07:41 +0100 Subject: [PATCH 04/27] Save changes --- .../HierarchicalPartitionKeysPathAttribute.cs | 40 ----------- .../Attributes/PartitionKeyPathAttribute.cs | 2 +- src/Microsoft.Azure.CosmosRepository/IItem.cs | 2 +- src/Microsoft.Azure.CosmosRepository/Item.cs | 6 ++ .../Logging/LoggerExtensions.cs | 4 +- .../Logging/LoggerMessageDefinitions.cs | 8 +-- .../Repositories/DefaultRepository.Batch.cs | 6 +- .../Repositories/DefaultRepository.Count.cs | 58 +++++++++++---- .../Repositories/DefaultRepository.Create.cs | 2 +- .../Repositories/DefaultRepository.Delete.cs | 12 +++- .../Repositories/DefaultRepository.Exists.cs | 26 +++++-- .../Repositories/DefaultRepository.Paging.cs | 71 +++++++++++++++++-- .../Repositories/DefaultRepository.Read.cs | 29 ++++++-- .../Repositories/DefaultRepository.Specs.cs | 9 ++- .../Repositories/DefaultRepository.Update.cs | 40 +++++++++-- .../Repositories/DefaultRepository.cs | 49 ++++++++++--- .../Repositories/IReadOnlyRepository.cs | 33 ++++++++- .../Repositories/IWriteOnlyRepository.cs | 8 ++- .../Specification/BaseSpecification.cs | 3 + .../Builder/SpecificationBuilder.cs | 6 ++ .../Specification/ISpecification.cs | 2 + 21 files changed, 309 insertions(+), 107 deletions(-) delete mode 100644 src/Microsoft.Azure.CosmosRepository/Attributes/HierarchicalPartitionKeysPathAttribute.cs diff --git a/src/Microsoft.Azure.CosmosRepository/Attributes/HierarchicalPartitionKeysPathAttribute.cs b/src/Microsoft.Azure.CosmosRepository/Attributes/HierarchicalPartitionKeysPathAttribute.cs deleted file mode 100644 index acf6e3790..000000000 --- a/src/Microsoft.Azure.CosmosRepository/Attributes/HierarchicalPartitionKeysPathAttribute.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) David Pine. All rights reserved. -// Licensed under the MIT License. - -namespace Microsoft.Azure.CosmosRepository.Attributes; - -/// -/// The partition key path attribute exposes the ability to declaratively -/// specify an partition key path. This attribute should be used in -/// conjunction with a on the property -/// whose value will act as the partition key. Partition key paths should start with "/", -/// for example "/partition". For more information, -/// see https://docs.microsoft.com/azure/cosmos-db/partitioning-overview. -/// -/// -/// By default, "/id" is used. -/// -/// -/// Constructor accepting the of the partition key for a given . -/// -/// -/// -/// -[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] -public sealed class HierarchicalPartitionKeysPathAttribute(string path, string subPathOne, string? subPathTwo = null) : Attribute -{ - /// - /// Gets the path of the parition key. - /// - public string Path { get; } = path ?? throw new ArgumentNullException(nameof(path), "A path is required."); - - /// - /// Gets the path of the sub parition key one. - /// - public string SubPathOne { get; } = subPathOne ?? throw new ArgumentNullException(nameof(subPathOne), "A subpath is required."); - - /// - /// Gets the path of the sub parition key two. - /// - public string? SubPathTwo { get; } = subPathTwo; -} diff --git a/src/Microsoft.Azure.CosmosRepository/Attributes/PartitionKeyPathAttribute.cs b/src/Microsoft.Azure.CosmosRepository/Attributes/PartitionKeyPathAttribute.cs index e583c2496..938402b2e 100644 --- a/src/Microsoft.Azure.CosmosRepository/Attributes/PartitionKeyPathAttribute.cs +++ b/src/Microsoft.Azure.CosmosRepository/Attributes/PartitionKeyPathAttribute.cs @@ -23,7 +23,7 @@ namespace Microsoft.Azure.CosmosRepository.Attributes; public sealed class PartitionKeyPathAttribute(params string[] paths) : Attribute { /// - /// Gets the path of the parition key. + /// Gets the path values of the parition key. /// public string[] Paths { get; } = paths != null && paths.Length > 1 ? paths : throw new ArgumentNullException(nameof(paths), "At least one path is required."); } diff --git a/src/Microsoft.Azure.CosmosRepository/IItem.cs b/src/Microsoft.Azure.CosmosRepository/IItem.cs index 8605e4c80..efce694b9 100644 --- a/src/Microsoft.Azure.CosmosRepository/IItem.cs +++ b/src/Microsoft.Azure.CosmosRepository/IItem.cs @@ -21,7 +21,7 @@ public interface IItem ///// ///// Gets the item's PartitionKey. This string is used to instantiate the Cosmos.PartitionKey struct. ///// - //string PartitionKey { get; } + string PartitionKey { get; } /// /// Gets the item PartitionKeys. This string array is used to instantiate the Cosmos.PartitionKeys struct. diff --git a/src/Microsoft.Azure.CosmosRepository/Item.cs b/src/Microsoft.Azure.CosmosRepository/Item.cs index d6e03ebc9..a600f0de0 100644 --- a/src/Microsoft.Azure.CosmosRepository/Item.cs +++ b/src/Microsoft.Azure.CosmosRepository/Item.cs @@ -43,6 +43,12 @@ public abstract class Item : IItem [JsonProperty("type")] public string Type { get; set; } + /// + /// Gets the PartitionKey based on last record. + /// Implemented explicitly to keep out of Item API + /// + string IItem.PartitionKey => GetPartitionKeyValues().Last(); + /// /// Gets the PartitionKeys based on . /// Implemented explicitly to keep out of Item API diff --git a/src/Microsoft.Azure.CosmosRepository/Logging/LoggerExtensions.cs b/src/Microsoft.Azure.CosmosRepository/Logging/LoggerExtensions.cs index 28f1549b6..23425dd10 100644 --- a/src/Microsoft.Azure.CosmosRepository/Logging/LoggerExtensions.cs +++ b/src/Microsoft.Azure.CosmosRepository/Logging/LoggerExtensions.cs @@ -23,7 +23,7 @@ public static void LogPointReadStarted( public static void LogPointReadStarted( this ILogger logger, string id, - string[] partitionKeys) where TItem : IItem => + IEnumerable partitionKeys) where TItem : IItem => LoggerMessageDefinitions.HierarchicalPointReadStarted(logger, typeof(TItem).Name, id, partitionKeys, null!); public static void LogPointReadExecuted( @@ -52,7 +52,7 @@ public static void LogItemNotFoundHandled( public static void LogItemNotFoundHandled( this ILogger logger, string id, - string[] partitionKeys, + IEnumerable partitionKeys, CosmosException e) where TItem : IItem => LoggerMessageDefinitions.HierarchicalItemNotFoundHandled(logger, typeof(TItem).Name, id, partitionKeys, e); } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Logging/LoggerMessageDefinitions.cs b/src/Microsoft.Azure.CosmosRepository/Logging/LoggerMessageDefinitions.cs index 6a1a6a1a3..15beae74d 100644 --- a/src/Microsoft.Azure.CosmosRepository/Logging/LoggerMessageDefinitions.cs +++ b/src/Microsoft.Azure.CosmosRepository/Logging/LoggerMessageDefinitions.cs @@ -21,8 +21,8 @@ internal static class LoggerMessageDefinitions ); // Info Definitions - internal static readonly Action HierarchicalPointReadStarted = - LoggerMessage.Define( + internal static readonly Action, Exception?> HierarchicalPointReadStarted = + LoggerMessage.Define>( LogLevel.Information, EventIds.CosmosPointReadStarted, "Point read started for item type {CosmosItemType} with id {CosmosItemId} and partitionKeys {CosmosItemPartitionKeys}" @@ -55,8 +55,8 @@ internal static class LoggerMessageDefinitions "CosmosException Status Code 404 handled for item of type {CosmosItemType} with {CosmosItemId} and partition key {CosmosItemPartitionKey}" ); - internal static readonly Action HierarchicalItemNotFoundHandled = - LoggerMessage.Define( + internal static readonly Action, Exception?> HierarchicalItemNotFoundHandled = + LoggerMessage.Define>( LogLevel.Information, EventIds.ItemNotFoundHandled, "CosmosException Status Code 404 handled for item of type {CosmosItemType} with {CosmosItemId} and partition keys {CosmosItemPartitionKeys}" diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Batch.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Batch.cs index 738c7b240..c33fae26c 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Batch.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Batch.cs @@ -15,7 +15,7 @@ public async ValueTask UpdateAsBatchAsync( { var list = items.ToList(); - PartitionKey partitionKey = GetPartitionKey(list); + PartitionKey partitionKey = BuildPartitionKey(list); await UpdateAsBatchAsync(items, partitionKey, cancellationToken); } @@ -59,7 +59,7 @@ public async ValueTask CreateAsBatchAsync( { var list = items.ToList(); - PartitionKey partitionKey = GetPartitionKey(list); + PartitionKey partitionKey = BuildPartitionKey(list); await CreateAsBatchAsync(items, partitionKey, cancellationToken); } @@ -96,7 +96,7 @@ public async ValueTask DeleteAsBatchAsync( { var list = items.ToList(); - PartitionKey partitionKey = GetPartitionKey(list); + PartitionKey partitionKey = BuildPartitionKey(list); await DeleteAsBatchAsync(items, partitionKey, cancellationToken); diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs index 74c1b9aef..0c19e9221 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs @@ -11,16 +11,30 @@ internal sealed partial class DefaultRepository public async ValueTask CountAsync( CancellationToken cancellationToken = default) { - Container container = - await containerProvider.GetContainerAsync() - .ConfigureAwait(false); + return await InternalCountAsync(cancellationToken: cancellationToken); + } - IQueryable query = container.GetItemLinqQueryable( - linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions); + /// + public async ValueTask CountAsync( + Expression> predicate, + CancellationToken cancellationToken = default) + { + return await InternalCountAsync(predicate, default, cancellationToken); + } - TryLogDebugDetails(logger, () => $"Read: {query}"); + public async ValueTask CountAsync( + Expression> predicate, + PartitionKey partitionKey, + CancellationToken cancellationToken = default) + { + return await InternalCountAsync(predicate, partitionKey, cancellationToken); + } - return await cosmosQueryableProcessor.CountAsync(query, cancellationToken); + public async ValueTask CountAsync( + PartitionKey partitionKey, + CancellationToken cancellationToken = default) + { + return await InternalCountAsync(null, partitionKey, cancellationToken); } private async ValueTask> CountAsync( @@ -32,7 +46,15 @@ private async ValueTask> CountAsync( await containerProvider.GetContainerAsync() .ConfigureAwait(false); + QueryRequestOptions options = new(); + + if(specification.PartitionKey != null) + { + options.PartitionKey = specification.PartitionKey; + } + IQueryable query = container.GetItemLinqQueryable( + requestOptions: options, linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions); query = specificationEvaluator.GetQuery(query, specification, evaluateCriteriaOnly: true); @@ -41,19 +63,31 @@ await containerProvider.GetContainerAsync() return await query.CountAsync(cancellationToken); } - /// - public async ValueTask CountAsync( - Expression> predicate, + private async ValueTask InternalCountAsync( + Expression>? predicate = null, + PartitionKey partitionKey = default, CancellationToken cancellationToken = default) { Container container = await containerProvider.GetContainerAsync() .ConfigureAwait(false); + QueryRequestOptions requestOptions = new (); + + if (partitionKey != default) + { + requestOptions.PartitionKey = partitionKey; + } + IQueryable query = container.GetItemLinqQueryable( - linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions) - .Where(repositoryExpressionProvider.Build(predicate)); + requestOptions: requestOptions, + linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions); + + if(predicate != null) + { + query = query.Where(repositoryExpressionProvider.Build(predicate)); + } TryLogDebugDetails(logger, () => $"Read: {query}"); diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Create.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Create.cs index 3b3c9fe9e..599de1887 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Create.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Create.cs @@ -20,7 +20,7 @@ public async ValueTask CreateAsync( valueWithTimestamps.CreatedTimeUtc = DateTime.UtcNow; } - PartitionKey partitionKey = GetPartitionKey(value); + PartitionKey partitionKey = BuildPartitionKey(value); ItemResponse response = await container.CreateItemAsync(value, partitionKey, diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Delete.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Delete.cs index dd67bb7d3..5193c4ffc 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Delete.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Delete.cs @@ -3,6 +3,8 @@ // ReSharper disable once CheckNamespace +using Newtonsoft.Json.Linq; + namespace Microsoft.Azure.CosmosRepository; internal sealed partial class DefaultRepository @@ -11,14 +13,20 @@ internal sealed partial class DefaultRepository public ValueTask DeleteAsync( TItem value, CancellationToken cancellationToken = default) => - DeleteAsync(value.Id, new PartitionKeyBuilder().Build(value.PartitionKeys), cancellationToken); + DeleteAsync(value.Id, BuildPartitionKey(value.PartitionKeys), cancellationToken); /// public ValueTask DeleteAsync( string id, string? partitionKeyValue = null, CancellationToken cancellationToken = default) => - DeleteAsync(id, new PartitionKey(partitionKeyValue ?? id), cancellationToken); + DeleteAsync(id, BuildPartitionKey(partitionKeyValue ?? id), cancellationToken); + + public ValueTask DeleteAsync( + string id, + IEnumerable partitionKeyValues, + CancellationToken cancellationToken = default) => + DeleteAsync(id, BuildPartitionKey(partitionKeyValues, id), cancellationToken); /// public async ValueTask DeleteAsync( diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs index bafcbdbc3..4501ce3e7 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs @@ -12,7 +12,7 @@ public ValueTask ExistsAsync( string id, string? partitionKeyValue = null, CancellationToken cancellationToken = default) => - ExistsAsync(id, new PartitionKey(partitionKeyValue ?? id), cancellationToken); + ExistsAsync(id, BuildPartitionKey(partitionKeyValue ?? id), cancellationToken); /// public async ValueTask ExistsAsync( @@ -41,16 +41,32 @@ public async ValueTask ExistsAsync( return true; } - /// + /// + public async ValueTask ExistsAsync( + Expression> predicate, + CancellationToken cancellationToken = default) + { + return await ExistsAsync(predicate, default, cancellationToken); + } + public async ValueTask ExistsAsync( - Expression> predicate, + Expression> predicate, + PartitionKey partitionKey = default, CancellationToken cancellationToken = default) { Container container = - await containerProvider.GetContainerAsync().ConfigureAwait(false); + await containerProvider.GetContainerAsync().ConfigureAwait(false); + + var requestOptions = new QueryRequestOptions(); + + if (partitionKey != default) + { + requestOptions.PartitionKey = partitionKey; + } IQueryable query = - container.GetItemLinqQueryable( + container.GetItemLinqQueryable( + requestOptions: requestOptions, linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions) .Where(repositoryExpressionProvider.Build(predicate)); diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs index 4bd2a486f..30823d273 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs @@ -3,17 +3,40 @@ // ReSharper disable once CheckNamespace +using Microsoft.Extensions.Options; + namespace Microsoft.Azure.CosmosRepository; internal sealed partial class DefaultRepository { /// public async ValueTask> PageAsync( - Expression>? predicate = null, + Expression>? predicate = null, int pageSize = 25, string? continuationToken = null, bool returnTotal = false, CancellationToken cancellationToken = default) + { + return await PageAsync(predicate, default, pageSize, continuationToken, returnTotal, cancellationToken); + } + + public async ValueTask> PageAsync( + PartitionKey partitionKey, + int pageSize = 25, + string? continuationToken = null, + bool returnTotal = false, + CancellationToken cancellationToken = default) + { + return await PageAsync(null, partitionKey, pageSize, continuationToken, returnTotal, cancellationToken); + } + + public async ValueTask> PageAsync( + Expression>? predicate = null, + PartitionKey partitionKey = default, + int pageSize = 25, + string? continuationToken = null, + bool returnTotal = false, + CancellationToken cancellationToken = default) { Container container = await containerProvider.GetContainerAsync() .ConfigureAwait(false); @@ -21,7 +44,12 @@ public async ValueTask> PageAsync( QueryRequestOptions options = new() { MaxItemCount = pageSize - }; + }; + + if (partitionKey != default) + { + options.PartitionKey = partitionKey; + } IQueryable query = container .GetItemLinqQueryable( @@ -52,21 +80,50 @@ public async ValueTask> PageAsync( items.AsReadOnly(), charge + countResponse?.RequestCharge ?? 0, resultingContinuationToken); - } + } + + public async ValueTask> PageAsync( + PartitionKey partitionKey, + int pageNumber = 1, + int pageSize = 25, + bool returnTotal = false, + CancellationToken cancellationToken = default) + { + return await PageAsync(null, partitionKey, pageNumber, pageSize, returnTotal, cancellationToken); + } + + /// + public async ValueTask> PageAsync( + Expression>? predicate = null, + int pageNumber = 1, + int pageSize = 25, + bool returnTotal = false, + CancellationToken cancellationToken = default) + { + return await PageAsync(predicate, default, pageNumber, pageSize, returnTotal, cancellationToken); + } - /// public async ValueTask> PageAsync( - Expression>? predicate = null, + Expression>? predicate = null, + PartitionKey partitionKey = default, int pageNumber = 1, int pageSize = 25, bool returnTotal = false, CancellationToken cancellationToken = default) { Container container = await containerProvider.GetContainerAsync() - .ConfigureAwait(false); + .ConfigureAwait(false); + + var options = new QueryRequestOptions(); + + if(partitionKey != default) + { + options.PartitionKey = partitionKey; + } IQueryable query = container - .GetItemLinqQueryable( + .GetItemLinqQueryable( + requestOptions: options, linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions) .Where(repositoryExpressionProvider .Build(predicate ?? repositoryExpressionProvider.Default())); diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs index f34c82a01..301bf5ed2 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs @@ -25,7 +25,7 @@ internal sealed partial class DefaultRepository public async ValueTask TryGetAsync( string id, - string[] partitionKeyValues, + IEnumerable partitionKeyValues, CancellationToken cancellationToken = default) { try @@ -37,7 +37,8 @@ internal sealed partial class DefaultRepository logger.LogItemNotFoundHandled(id, partitionKeyValues ?? [id], e); return default; } - } + } + /// public ValueTask GetAsync( @@ -48,9 +49,9 @@ public ValueTask GetAsync( public ValueTask GetAsync( string id, - string[] partitionKeyValues, + IEnumerable partitionKeyValues, CancellationToken cancellationToken = default) => - GetAsync(id, new PartitionKeyBuilder().Build(partitionKeyValues), cancellationToken); + GetAsync(id, BuildPartitionKey(partitionKeyValues), cancellationToken); /// public async ValueTask GetAsync( @@ -100,10 +101,26 @@ public async ValueTask> GetAsync( return items; } + + + public async ValueTask> GetAsync( + Expression> predicate, + CancellationToken cancellationToken = default) + { + return await GetAsync(predicate, default, cancellationToken); + } - public async ValueTask> GetAsync( + public async ValueTask> GetAsync( + PartitionKey partitionKey, + Expression> predicate, + CancellationToken cancellationToken = default) + { + return await GetAsync(predicate, partitionKey, cancellationToken); + } + + private async ValueTask> GetAsync( Expression> predicate, - PartitionKey partitionKey = default, + PartitionKey partitionKey = default, CancellationToken cancellationToken = default) { Container container = diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Specs.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Specs.cs index 47e5f6857..4d5d926b6 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Specs.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Specs.cs @@ -19,9 +19,11 @@ public async ValueTask QueryAsync( QueryRequestOptions options = new(); if (specification.UseContinuationToken) - { options.MaxItemCount = specification.PageSize; - } + + if(specification.PartitionKey != null) + options.PartitionKey = specification.PartitionKey; + IQueryable query = container .GetItemLinqQueryable( @@ -30,7 +32,8 @@ public async ValueTask QueryAsync( linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions) .Where(repositoryExpressionProvider.Default()); - query = specificationEvaluator.GetQuery(query, specification); + query = specificationEvaluator.GetQuery(query, specification); + logger.LogQueryConstructed(query); diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs index 4de7db98f..33327ccc6 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs @@ -22,10 +22,12 @@ await containerProvider.GetContainerAsync() options.IfMatchEtag = string.IsNullOrWhiteSpace(valueWithEtag.Etag) ? default : valueWithEtag.Etag; - } + } + + PartitionKey partitionKey = BuildPartitionKey(value); ItemResponse response = - await container.UpsertItemAsync(value, new PartitionKey(value.PartitionKey), options, + await container.UpsertItemAsync(value, partitionKey, options, cancellationToken) .ConfigureAwait(false); @@ -47,13 +49,39 @@ public async ValueTask> UpdateAsync( await Task.WhenAll(updateTasks).ConfigureAwait(false); return updateTasks.Select(x => x.Result); - } + } + + public async ValueTask UpdateAsync(string id, + Action> builder, + string? etag = default, + CancellationToken cancellationToken = default) + { + await InternalUpdateAsync(id, builder, null, etag, cancellationToken); + } public async ValueTask UpdateAsync(string id, Action> builder, - string? partitionKeyValue = null, + string partitionKeyValue, string? etag = default, CancellationToken cancellationToken = default) + { + await InternalUpdateAsync(id, builder, BuildPartitionKey(partitionKeyValue, id), etag, cancellationToken); + } + + public async ValueTask UpdateAsync(string id, + Action> builder, + IEnumerable partitionKeyValues, + string? etag = default, + CancellationToken cancellationToken = default) + { + await InternalUpdateAsync(id, builder, BuildPartitionKey(partitionKeyValues, id), etag, cancellationToken); + } + + public async ValueTask InternalUpdateAsync(string id, + Action> builder, + PartitionKey? partitionKey = null, + string? etag = default, + CancellationToken cancellationToken = default) { CosmosPropertyNamingPolicy? propertyNamingPolicy = optionsMonitor.CurrentValue.SerializationOptions?.PropertyNamingPolicy; @@ -63,15 +91,13 @@ public async ValueTask UpdateAsync(string id, Container container = await containerProvider.GetContainerAsync(); - partitionKeyValue ??= id; - PatchItemRequestOptions patchItemRequestOptions = new(); if (etag != default && !string.IsNullOrWhiteSpace(etag)) { patchItemRequestOptions.IfMatchEtag = etag; } - await container.PatchItemAsync(id, new PartitionKey(partitionKeyValue), + await container.PatchItemAsync(id, partitionKey ?? new PartitionKey(id), patchOperationBuilder.PatchOperations, patchItemRequestOptions, cancellationToken); } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs index 723a1f1a2..f210e0627 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. // ReSharper disable once CheckNamespace + namespace Microsoft.Azure.CosmosRepository; /// @@ -63,25 +64,43 @@ await iterator.ReadNextAsync(cancellationToken) return (results, charge, continuationToken); } - internal static PartitionKey GetPartitionKey(List items) + /// + /// Builds a partition key from the first item in a list. This method assumes all items in the list belong to the same partition. + /// Throws an exception if the list is empty. + /// + /// A list of items from which the partition key is to be derived. + /// A PartitionKey object constructed from the first item in the list. + /// Thrown when the items list is empty. + internal static PartitionKey BuildPartitionKey(List items) { - if (!items.Any()) + if (items.Count == 0) { throw new ArgumentException( "Unable to perform batch operation with no items", nameof(items)); } - return GetPartitionKey(items[0]); + return BuildPartitionKey(items[0]); } - internal static PartitionKey GetPartitionKey(string[] values, string? defaultValue = null) + /// + /// Constructs a partition key from a collection of string values. If the collection is empty or null, + /// the method uses the defaultValue, if provided, to construct the PartitionKey. + /// + /// An IEnumerable collection of string values for constructing the partition key. + /// An optional default value used to construct the PartitionKey if values are null or empty. + /// Thrown when the number of provided partition key values exceeds 3. + /// A PartitionKey object constructed from the values or the defaultValue if values are empty or null. + internal static PartitionKey BuildPartitionKey(IEnumerable values, string? defaultValue = null) { var builder = new PartitionKeyBuilder(); - if (values == null || values.Length == 0) + if (values == null || !values.Any()) { return !string.IsNullOrEmpty(defaultValue) ? new PartitionKey(defaultValue) : default; } + if (values.Count() > 3) throw new ArgumentException("Unable to build partition key. The max allowed partition key values is 3", nameof(values)); + + foreach (var value in values) { builder.Add(value); @@ -90,13 +109,25 @@ internal static PartitionKey GetPartitionKey(string[] values, string? defaultVal return builder.Build(); } - internal static PartitionKey GetPartitionKey(string value) + /// + /// Creates a partition key from a single string value. + /// + /// The string value to use for constructing the partition key. + /// A PartitionKey object constructed from the provided string value. + internal static PartitionKey BuildPartitionKey(string value, string? defaultValue = null) { + if (string.IsNullOrWhiteSpace(value) && !string.IsNullOrWhiteSpace(value)) return new PartitionKey(defaultValue); return new PartitionKey(value); } - - internal static PartitionKey GetPartitionKey(TItem item) + /// + /// Retrieves a partition key from an item by extracting its partition keys and using them to construct a new PartitionKey. + /// Throws an exception if the item is null. + /// + /// The item from which to extract the partition keys. + /// A PartitionKey object constructed from the item's partition keys. + /// Thrown when the provided item is null. + internal static PartitionKey BuildPartitionKey(TItem item) { if (item == null) { @@ -105,6 +136,6 @@ internal static PartitionKey GetPartitionKey(TItem item) nameof(item)); } - return GetPartitionKey(item.PartitionKeys); + return BuildPartitionKey(item.PartitionKeys); } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/IReadOnlyRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/IReadOnlyRepository.cs index b4eb25d7d..a16efb19d 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/IReadOnlyRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/IReadOnlyRepository.cs @@ -203,8 +203,13 @@ ValueTask CountAsync( //TODO: Write doc ValueTask CountAsync( - PartitionKey partitionKey, Expression> predicate, + PartitionKey partitionKey, + CancellationToken cancellationToken = default); + + //TODO: Write doc + ValueTask CountAsync( + PartitionKey partitionKey, CancellationToken cancellationToken = default); /// @@ -218,8 +223,17 @@ ValueTask CountAsync( /// The cancellation token to use when making asynchronous operations. /// An of s /// This method makes use of cosmos dbs continuation tokens for efficient, cost effective paging utilising low RUs + ValueTask> PageAsync( + Expression>? predicate, + int pageSize = 25, + string? continuationToken = null, + bool returnTotal = false, + CancellationToken cancellationToken = default); + + //TODO: Write doc ValueTask> PageAsync( Expression>? predicate = null, + PartitionKey partitionKey = default, int pageSize = 25, string? continuationToken = null, bool returnTotal = false, @@ -228,7 +242,6 @@ ValueTask> PageAsync( //TODO: Write doc ValueTask> PageAsync( PartitionKey partitionKey, - Expression>? predicate = null, int pageSize = 25, string? continuationToken = null, bool returnTotal = false, @@ -264,8 +277,22 @@ ValueTask QueryAsync( /// An of s /// This method makes use of Cosmos DB's continuation tokens for efficient, cost effective paging utilizing low RUs ValueTask> PageAsync( - PartitionKey? partitionKey = null, + Expression>? predicate, + int pageNumber = 1, + int pageSize = 25, + bool returnTotal = false, + CancellationToken cancellationToken = default); + + ValueTask> PageAsync( + PartitionKey partitionKey, + int pageNumber = 1, + int pageSize = 25, + bool returnTotal = false, + CancellationToken cancellationToken = default); + + ValueTask> PageAsync( Expression>? predicate = null, + PartitionKey? partitionKey = default, int pageNumber = 1, int pageSize = 25, bool returnTotal = false, diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs index 3c0aa18ad..49032ce06 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs @@ -81,7 +81,7 @@ ValueTask> UpdateAsync( ValueTask UpdateAsync( string id, Action> builder, - string? partitionKeyValue = null, + string partitionKeyValue, string? etag = default, CancellationToken cancellationToken = default); @@ -107,6 +107,12 @@ ValueTask DeleteAsync( string? partitionKeyValue = null, CancellationToken cancellationToken = default); + //TODO: Write docs + ValueTask DeleteAsync( + string id, + IEnumerable partitionKeyValues, + CancellationToken cancellationToken = default); + /// /// Deletes the cosmos object that corresponds to the given . /// diff --git a/src/Microsoft.Azure.CosmosRepository/Specification/BaseSpecification.cs b/src/Microsoft.Azure.CosmosRepository/Specification/BaseSpecification.cs index 931b90f2c..6cd9e3ac1 100644 --- a/src/Microsoft.Azure.CosmosRepository/Specification/BaseSpecification.cs +++ b/src/Microsoft.Azure.CosmosRepository/Specification/BaseSpecification.cs @@ -43,6 +43,9 @@ internal void Add(OrderExpressionInfo expression) => /// public int? PageNumber { get; internal set; } + //TODO: Write doc + public PartitionKey? PartitionKey { get; internal set; } + /// public int PageSize { get; internal set; } = 25; diff --git a/src/Microsoft.Azure.CosmosRepository/Specification/Builder/SpecificationBuilder.cs b/src/Microsoft.Azure.CosmosRepository/Specification/Builder/SpecificationBuilder.cs index 1579bedca..2ffe12eb3 100644 --- a/src/Microsoft.Azure.CosmosRepository/Specification/Builder/SpecificationBuilder.cs +++ b/src/Microsoft.Azure.CosmosRepository/Specification/Builder/SpecificationBuilder.cs @@ -64,4 +64,10 @@ public ISpecificationBuilder ContinuationToken(string continuati Specification.ContinuationToken = continuationToken; return this; } + + public ISpecificationBuilder PartitionKey(PartitionKey partitionKey) + { + Specification.PartitionKey = partitionKey; + return this; + } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Specification/ISpecification.cs b/src/Microsoft.Azure.CosmosRepository/Specification/ISpecification.cs index 20d1ed7a2..8083125a2 100644 --- a/src/Microsoft.Azure.CosmosRepository/Specification/ISpecification.cs +++ b/src/Microsoft.Azure.CosmosRepository/Specification/ISpecification.cs @@ -38,6 +38,8 @@ TResult PostProcessingAction( /// int? PageNumber { get; } + //TODO: Write doc + public PartitionKey? PartitionKey { get; } /// /// Paginate results, selects how many results should be returned From 4f7e3cd95117d38c2365a5a79d25684bb0679d6e Mon Sep 17 00:00:00 2001 From: Oleksandr Kyselov Date: Sat, 10 Feb 2024 13:05:16 +0100 Subject: [PATCH 05/27] - Checkpoint default repository --- src/Microsoft.Azure.CosmosRepository/IItem.cs | 2 +- src/Microsoft.Azure.CosmosRepository/Item.cs | 6 +-- .../ICosmosPartitionKeyPathProvider.cs | 4 +- .../Repositories/DefaultRepository.Count.cs | 6 +++ .../Repositories/DefaultRepository.Exists.cs | 29 ++++++++---- .../Repositories/DefaultRepository.Paging.cs | 18 +++++--- .../Repositories/DefaultRepository.Read.cs | 40 ++++++++++++---- .../Repositories/DefaultRepository.Update.cs | 13 ++++-- .../Repositories/DefaultRepository.cs | 3 ++ .../Repositories/IReadOnlyRepository.cs | 46 +++++++++---------- 10 files changed, 104 insertions(+), 63 deletions(-) diff --git a/src/Microsoft.Azure.CosmosRepository/IItem.cs b/src/Microsoft.Azure.CosmosRepository/IItem.cs index efce694b9..ab0986777 100644 --- a/src/Microsoft.Azure.CosmosRepository/IItem.cs +++ b/src/Microsoft.Azure.CosmosRepository/IItem.cs @@ -26,5 +26,5 @@ public interface IItem /// /// Gets the item PartitionKeys. This string array is used to instantiate the Cosmos.PartitionKeys struct. /// - string[] PartitionKeys { get; } + IEnumerable PartitionKeys { get; } } diff --git a/src/Microsoft.Azure.CosmosRepository/Item.cs b/src/Microsoft.Azure.CosmosRepository/Item.cs index a600f0de0..3a751c7ac 100644 --- a/src/Microsoft.Azure.CosmosRepository/Item.cs +++ b/src/Microsoft.Azure.CosmosRepository/Item.cs @@ -53,7 +53,7 @@ public abstract class Item : IItem /// Gets the PartitionKeys based on . /// Implemented explicitly to keep out of Item API /// - string[] IItem.PartitionKeys => GetPartitionKeyValues(); + IEnumerable IItem.PartitionKeys => GetPartitionKeyValues(); /// /// Default constructor, assigns type name to property. @@ -73,9 +73,9 @@ public abstract class Item : IItem /// Gets the partition key values for the given type. /// When overridden, be sure that the values correspond /// to the values, i.e.; "/partition" and "partition" - /// respectively. /// respectively. If all provided key values do not have a matching property with the equivalent name, an error will occur. + /// Make sure to add the latest inner /// /// The list with unless overridden by the subclass. - protected virtual string[] GetPartitionKeyValues() => new string[] { GetPartitionKeyValue() }; + protected virtual IEnumerable GetPartitionKeyValues() => new string[] { GetPartitionKeyValue() }; } diff --git a/src/Microsoft.Azure.CosmosRepository/Providers/ICosmosPartitionKeyPathProvider.cs b/src/Microsoft.Azure.CosmosRepository/Providers/ICosmosPartitionKeyPathProvider.cs index ed6b187ec..b17341c7b 100644 --- a/src/Microsoft.Azure.CosmosRepository/Providers/ICosmosPartitionKeyPathProvider.cs +++ b/src/Microsoft.Azure.CosmosRepository/Providers/ICosmosPartitionKeyPathProvider.cs @@ -14,6 +14,6 @@ interface ICosmosPartitionKeyPathProvider /// /// The item for which the partition keys paths corresponds. /// A string array representing the partition key paths, i.e.; "/partion" - string[] GetPartitionKeyPaths() where TItem : IItem; - string[] GetPartitionKeyPaths(Type itemType); + IEnumerable GetPartitionKeyPaths() where TItem : IItem; + IEnumerable GetPartitionKeyPaths(Type itemType); } diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs index 0c19e9221..ff7904e53 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs @@ -94,4 +94,10 @@ await containerProvider.GetContainerAsync() return await cosmosQueryableProcessor.CountAsync( query, cancellationToken); } + + //TODO: Write docs + public async ValueTask CountAsync(Expression> predicate, IEnumerable partitionKeyValues, CancellationToken cancellationToken = default) + { + return await CountAsync(predicate, BuildPartitionKey(partitionKeyValues), cancellationToken); + } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs index 4501ce3e7..68eb99252 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs @@ -39,20 +39,21 @@ public async ValueTask ExistsAsync( } return true; - } - + } + /// public async ValueTask ExistsAsync( - Expression> predicate, - CancellationToken cancellationToken = default) + Expression> predicate, + CancellationToken cancellationToken = default) { return await ExistsAsync(predicate, default, cancellationToken); } - - public async ValueTask ExistsAsync( - Expression> predicate, - PartitionKey partitionKey = default, - CancellationToken cancellationToken = default) + + //TODO: Write docs + public async ValueTask ExistsAsync( + Expression> predicate, + PartitionKey partitionKey = default, + CancellationToken cancellationToken = default) { Container container = await containerProvider.GetContainerAsync().ConfigureAwait(false); @@ -74,5 +75,13 @@ public async ValueTask ExistsAsync( var count = await cosmosQueryableProcessor.CountAsync(query, cancellationToken); return count > 0; - } + } + + + //TODO: Write doc + public async ValueTask ExistsAsync(string id, IEnumerable partitionKeyValues, CancellationToken cancellationToken = default) + { + return await ExistsAsync(id, BuildPartitionKey(partitionKeyValues, id), cancellationToken); + } + } diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs index 30823d273..99833eb70 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs @@ -17,20 +17,22 @@ public async ValueTask> PageAsync( bool returnTotal = false, CancellationToken cancellationToken = default) { - return await PageAsync(predicate, default, pageSize, continuationToken, returnTotal, cancellationToken); + return await InternalPageAsync(predicate, default, pageSize, continuationToken, returnTotal, cancellationToken); } + //TODO: Write doc public async ValueTask> PageAsync( PartitionKey partitionKey, + Expression>? predicate = null, int pageSize = 25, string? continuationToken = null, bool returnTotal = false, - CancellationToken cancellationToken = default) + CancellationToken cancellationToken = default) { - return await PageAsync(null, partitionKey, pageSize, continuationToken, returnTotal, cancellationToken); + return await InternalPageAsync(predicate, partitionKey, pageSize, continuationToken, returnTotal, cancellationToken); } - public async ValueTask> PageAsync( + private async ValueTask> InternalPageAsync( Expression>? predicate = null, PartitionKey partitionKey = default, int pageSize = 25, @@ -84,12 +86,13 @@ public async ValueTask> PageAsync( public async ValueTask> PageAsync( PartitionKey partitionKey, + Expression>? predicate = null, int pageNumber = 1, int pageSize = 25, bool returnTotal = false, CancellationToken cancellationToken = default) { - return await PageAsync(null, partitionKey, pageNumber, pageSize, returnTotal, cancellationToken); + return await InternalPageAsync(predicate, partitionKey, pageNumber, pageSize, returnTotal, cancellationToken); } /// @@ -100,10 +103,11 @@ public async ValueTask> PageAsync( bool returnTotal = false, CancellationToken cancellationToken = default) { - return await PageAsync(predicate, default, pageNumber, pageSize, returnTotal, cancellationToken); + return await InternalPageAsync(predicate, default, pageNumber, pageSize, returnTotal, cancellationToken); } + - public async ValueTask> PageAsync( + private async ValueTask> InternalPageAsync( Expression>? predicate = null, PartitionKey partitionKey = default, int pageNumber = 1, diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs index 301bf5ed2..bec644ac4 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs @@ -22,10 +22,28 @@ internal sealed partial class DefaultRepository return default; } } - - public async ValueTask TryGetAsync( - string id, - IEnumerable partitionKeyValues, + + //TODO: Write doc + public async ValueTask TryGetAsync( + string id, + PartitionKey partitionKey, + CancellationToken cancellationToken = default) + { + try + { + return await GetAsync(id, partitionKey, cancellationToken); + } + catch (CosmosException e) when (e.StatusCode is HttpStatusCode.NotFound) + { + logger.LogItemNotFoundHandled(id, partitionKey.ToString() ?? id, e); + return default; + } + } + + //TODO: Write doc + public async ValueTask TryGetAsync( + string id, + IEnumerable partitionKeyValues, CancellationToken cancellationToken = default) { try @@ -39,7 +57,6 @@ internal sealed partial class DefaultRepository } } - /// public ValueTask GetAsync( string id, @@ -47,6 +64,7 @@ public ValueTask GetAsync( CancellationToken cancellationToken = default) => GetAsync(id, new PartitionKey(partitionKeyValue ?? id), cancellationToken); + //TODO: Write doc public ValueTask GetAsync( string id, IEnumerable partitionKeyValues, @@ -80,7 +98,8 @@ await container.ReadItemAsync(id, partitionKey, cancellationToken: cancel return repositoryExpressionProvider.CheckItem(item); } - + + //TODO: Write doc public async ValueTask> GetAsync( PartitionKey partitionKey, CancellationToken cancellationToken = default) @@ -102,20 +121,21 @@ public async ValueTask> GetAsync( return items; } - + /// public async ValueTask> GetAsync( Expression> predicate, CancellationToken cancellationToken = default) { return await GetAsync(predicate, default, cancellationToken); } - + + //TODO: Write doc public async ValueTask> GetAsync( PartitionKey partitionKey, Expression> predicate, CancellationToken cancellationToken = default) - { - return await GetAsync(predicate, partitionKey, cancellationToken); + { + return await GetAsync(predicate, partitionKey, cancellationToken); } private async ValueTask> GetAsync( diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs index 33327ccc6..7b16da97f 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs @@ -51,14 +51,16 @@ public async ValueTask> UpdateAsync( return updateTasks.Select(x => x.Result); } + //TODO: Write docs public async ValueTask UpdateAsync(string id, - Action> builder, - string? etag = default, - CancellationToken cancellationToken = default) + Action> builder, + string? etag = default, + CancellationToken cancellationToken = default) { await InternalUpdateAsync(id, builder, null, etag, cancellationToken); } - + + /// public async ValueTask UpdateAsync(string id, Action> builder, string partitionKeyValue, @@ -68,6 +70,7 @@ public async ValueTask UpdateAsync(string id, await InternalUpdateAsync(id, builder, BuildPartitionKey(partitionKeyValue, id), etag, cancellationToken); } + //TODO: Write docs public async ValueTask UpdateAsync(string id, Action> builder, IEnumerable partitionKeyValues, @@ -77,7 +80,7 @@ public async ValueTask UpdateAsync(string id, await InternalUpdateAsync(id, builder, BuildPartitionKey(partitionKeyValues, id), etag, cancellationToken); } - public async ValueTask InternalUpdateAsync(string id, + private async ValueTask InternalUpdateAsync(string id, Action> builder, PartitionKey? partitionKey = null, string? etag = default, diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs index f210e0627..37cc8023f 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs @@ -3,6 +3,8 @@ // ReSharper disable once CheckNamespace + + namespace Microsoft.Azure.CosmosRepository; /// @@ -138,4 +140,5 @@ internal static PartitionKey BuildPartitionKey(TItem item) return BuildPartitionKey(item.PartitionKeys); } + } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/IReadOnlyRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/IReadOnlyRepository.cs index a16efb19d..c96fb5301 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/IReadOnlyRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/IReadOnlyRepository.cs @@ -45,11 +45,11 @@ public interface IReadOnlyRepository where TItem : IItem IEnumerable partitionKeyValues, CancellationToken cancellationToken = default); - //TODO: Should I implement this? - //ValueTask TryGetAsync( - // string id, - // PartitionKey partitionKey, - // CancellationToken cancellationToken = default); + //TODO: Write doc + ValueTask TryGetAsync( + string id, + PartitionKey partitionKey, + CancellationToken cancellationToken = default); /// /// Gets the implementation class instance as a that corresponds to the given . @@ -147,6 +147,12 @@ ValueTask ExistsAsync( string? partitionKeyValue = null, CancellationToken cancellationToken = default); + //TODO: Write doc + ValueTask ExistsAsync( + string id, + IEnumerable partitionKeyValues, + CancellationToken cancellationToken = default); + /// /// Queries cosmos DB to see if an item exists. /// @@ -173,8 +179,8 @@ ValueTask ExistsAsync( //TODO: Write doc ValueTask ExistsAsync( - PartitionKey partitionKey, Expression> predicate, + PartitionKey partitionKey, CancellationToken cancellationToken = default); /// @@ -201,6 +207,12 @@ ValueTask CountAsync( Expression> predicate, CancellationToken cancellationToken = default); + //TODO: Write doc + ValueTask CountAsync( + Expression> predicate, + IEnumerable partitionKeyValues, + CancellationToken cancellationToken = default); + //TODO: Write doc ValueTask CountAsync( Expression> predicate, @@ -230,18 +242,10 @@ ValueTask> PageAsync( bool returnTotal = false, CancellationToken cancellationToken = default); - //TODO: Write doc - ValueTask> PageAsync( - Expression>? predicate = null, - PartitionKey partitionKey = default, - int pageSize = 25, - string? continuationToken = null, - bool returnTotal = false, - CancellationToken cancellationToken = default); - //TODO: Write doc ValueTask> PageAsync( PartitionKey partitionKey, + Expression>? predicate = null, int pageSize = 25, string? continuationToken = null, bool returnTotal = false, @@ -285,20 +289,12 @@ ValueTask> PageAsync( ValueTask> PageAsync( PartitionKey partitionKey, - int pageNumber = 1, - int pageSize = 25, - bool returnTotal = false, - CancellationToken cancellationToken = default); - - ValueTask> PageAsync( Expression>? predicate = null, - PartitionKey? partitionKey = default, int pageNumber = 1, int pageSize = 25, bool returnTotal = false, CancellationToken cancellationToken = default); - #if NET7_0_OR_GREATER /// /// Wraps the existing paging support to return an @@ -310,8 +306,8 @@ ValueTask> PageAsync( /// An where T is . /// This method makes use of Cosmos DB's continuation tokens for efficient, cost effective paging utilizing low RUs async IAsyncEnumerable PageAsync( - PartitionKey? partitionKey = null, Expression>? predicate = null, + PartitionKey partitionKey = default, int limit = 1_000, [EnumeratorCancellation] CancellationToken cancellationToken = default) { @@ -323,8 +319,8 @@ async IAsyncEnumerable PageAsync( && cancellationToken.IsCancellationRequested is false) { IPageQueryResult page = await PageAsync( - partitionKey, predicate, + partitionKey, pageNumber: ++ currentPage, 25, returnTotal: false, From 3957c20d02fb534edd2cd59e055d669c122bdd4d Mon Sep 17 00:00:00 2001 From: Oleksandr Kyselov Date: Sat, 10 Feb 2024 22:48:12 +0100 Subject: [PATCH 06/27] Save changes --- .../Items/EventItem.cs | 7 +- .../IHierarchialItem.cs | 15 ---- .../Options/ItemConfiguration.cs | 20 ++++- .../Repositories/DefaultRepository.Exists.cs | 2 +- .../Repositories/DefaultRepository.Update.cs | 2 +- .../Repositories/DefaultRepository.cs | 4 +- .../Repositories/IReadOnlyRepository.cs | 60 +++++++-------- .../Repositories/IWriteOnlyRepository.cs | 14 ++++ .../Repositories/InMemoryRepository.Update.cs | 4 +- .../Repositories/InMemoryRepository.cs | 73 +++++++++++++++++++ .../Services/DefaultCosmosContainerService.cs | 4 +- .../DefaultRepositoryFactoryTests.cs | 3 + .../Options/RepositoryOptionsTests.cs | 2 +- ...ultCosmosItemConfigurationProviderTests.cs | 4 +- ...aultCosmosPartitionKeyPathProviderTests.cs | 20 +++-- 15 files changed, 167 insertions(+), 67 deletions(-) delete mode 100644 src/Microsoft.Azure.CosmosRepository/IHierarchialItem.cs diff --git a/src/Microsoft.Azure.CosmosEventSourcing/Items/EventItem.cs b/src/Microsoft.Azure.CosmosEventSourcing/Items/EventItem.cs index 000e3d001..f96c6a51e 100644 --- a/src/Microsoft.Azure.CosmosEventSourcing/Items/EventItem.cs +++ b/src/Microsoft.Azure.CosmosEventSourcing/Items/EventItem.cs @@ -55,10 +55,15 @@ public DomainEvent DomainEvent /// public string Type { get; set; } + /// + /// The value used to partition the event. + /// + public string PartitionKey { get; set; } = null!; + /// /// The values used to partition the event. /// - public string[] PartitionKeys { get; set; } = null!; + public IEnumerable PartitionKeys { get; set; } = null!; /// /// The name of the event stored. diff --git a/src/Microsoft.Azure.CosmosRepository/IHierarchialItem.cs b/src/Microsoft.Azure.CosmosRepository/IHierarchialItem.cs deleted file mode 100644 index 3d49dc77d..000000000 --- a/src/Microsoft.Azure.CosmosRepository/IHierarchialItem.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) David Pine. All rights reserved. -// Licensed under the MIT License. - -namespace Microsoft.Azure.CosmosRepository; - -/// -/// The base interface used for all repository hierarchical object or object graphs. -/// -public interface IHierarchialItem : IItem -{ - /// - /// Gets the item's PartitionKeys. This list is used to instantiate the Cosmos.PartitionKeys struct. - /// - IEnumerable PartitionKeys { get; } -} diff --git a/src/Microsoft.Azure.CosmosRepository/Options/ItemConfiguration.cs b/src/Microsoft.Azure.CosmosRepository/Options/ItemConfiguration.cs index 8b8b6939c..05df68c5f 100644 --- a/src/Microsoft.Azure.CosmosRepository/Options/ItemConfiguration.cs +++ b/src/Microsoft.Azure.CosmosRepository/Options/ItemConfiguration.cs @@ -5,11 +5,24 @@ namespace Microsoft.Azure.CosmosRepository.Options; internal class ItemConfiguration { - // Existing constructor public ItemConfiguration( Type type, string containerName, - string[] partitionKeyPaths, + string partitionKeyPath, + UniqueKeyPolicy? uniqueKeyPolicy = null, + ThroughputProperties? throughputProperties = null, + int defaultTimeToLive = -1, + bool syncContainerProperties = false, + ChangeFeedOptions? changeFeedOptions = null, + bool useStrictTypeChecking = true) + : this(type, containerName, new[] { partitionKeyPath }, uniqueKeyPolicy, throughputProperties, defaultTimeToLive, syncContainerProperties, changeFeedOptions, useStrictTypeChecking) + { + } + + public ItemConfiguration( + Type type, + string containerName, + IEnumerable partitionKeyPaths, UniqueKeyPolicy? uniqueKeyPolicy = null, ThroughputProperties? throughputProperties = null, int defaultTimeToLive = -1, @@ -26,14 +39,13 @@ public ItemConfiguration( SyncContainerProperties = syncContainerProperties; ChangeFeedOptions = changeFeedOptions; UseStrictTypeChecking = useStrictTypeChecking; - } public Type Type { get; } public string ContainerName { get; } - public string[] PartitionKeyPaths { get; } + public IEnumerable PartitionKeyPaths { get; } public UniqueKeyPolicy? UniqueKeyPolicy { get; } diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs index 68eb99252..fc78bc731 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs @@ -52,7 +52,7 @@ public async ValueTask ExistsAsync( //TODO: Write docs public async ValueTask ExistsAsync( Expression> predicate, - PartitionKey partitionKey = default, + PartitionKey partitionKey, CancellationToken cancellationToken = default) { Container container = diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs index 7b16da97f..a399b1d33 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs @@ -63,7 +63,7 @@ public async ValueTask UpdateAsync(string id, /// public async ValueTask UpdateAsync(string id, Action> builder, - string partitionKeyValue, + string? partitionKeyValue = null, string? etag = default, CancellationToken cancellationToken = default) { diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs index 37cc8023f..42d4c7650 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs @@ -116,9 +116,9 @@ internal static PartitionKey BuildPartitionKey(IEnumerable values, strin /// /// The string value to use for constructing the partition key. /// A PartitionKey object constructed from the provided string value. - internal static PartitionKey BuildPartitionKey(string value, string? defaultValue = null) + internal static PartitionKey BuildPartitionKey(string? value, string? defaultValue = null) { - if (string.IsNullOrWhiteSpace(value) && !string.IsNullOrWhiteSpace(value)) return new PartitionKey(defaultValue); + if ((value == null || string.IsNullOrWhiteSpace(value)) && !string.IsNullOrWhiteSpace(value)) return new PartitionKey(defaultValue); return new PartitionKey(value); } diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/IReadOnlyRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/IReadOnlyRepository.cs index c96fb5301..88bc44e08 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/IReadOnlyRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/IReadOnlyRepository.cs @@ -224,33 +224,6 @@ ValueTask CountAsync( PartitionKey partitionKey, CancellationToken cancellationToken = default); - /// - /// Offers a load more paging implementation for infinite scroll scenarios. - /// Allows for efficient paging making use of cosmos DBs continuation tokens, making this implementation cost effective. - /// - /// A filter criteria for the paging operation, if null it will get all s - /// The size of the page to return from cosmos db. - /// The token returned from a previous query, if null starts at the beginning of the data - /// Specifies whether or not to return the total number of items that matched the query. This defaults to false as it can be a very expensive operation. - /// The cancellation token to use when making asynchronous operations. - /// An of s - /// This method makes use of cosmos dbs continuation tokens for efficient, cost effective paging utilising low RUs - ValueTask> PageAsync( - Expression>? predicate, - int pageSize = 25, - string? continuationToken = null, - bool returnTotal = false, - CancellationToken cancellationToken = default); - - //TODO: Write doc - ValueTask> PageAsync( - PartitionKey partitionKey, - Expression>? predicate = null, - int pageSize = 25, - string? continuationToken = null, - bool returnTotal = false, - CancellationToken cancellationToken = default); - /// /// Get items based on a specification. /// The specification is used to define which filters are used, the order of the search results and how they are paged. @@ -281,12 +254,13 @@ ValueTask QueryAsync( /// An of s /// This method makes use of Cosmos DB's continuation tokens for efficient, cost effective paging utilizing low RUs ValueTask> PageAsync( - Expression>? predicate, + Expression>? predicate = null, int pageNumber = 1, int pageSize = 25, bool returnTotal = false, CancellationToken cancellationToken = default); + //TODO: Write doc ValueTask> PageAsync( PartitionKey partitionKey, Expression>? predicate = null, @@ -295,6 +269,34 @@ ValueTask> PageAsync( bool returnTotal = false, CancellationToken cancellationToken = default); + /// + /// Offers a load more paging implementation for infinite scroll scenarios. + /// Allows for efficient paging making use of cosmos DBs continuation tokens, making this implementation cost effective. + /// + /// A filter criteria for the paging operation, if null it will get all s + /// The size of the page to return from cosmos db. + /// The token returned from a previous query, if null starts at the beginning of the data + /// Specifies whether or not to return the total number of items that matched the query. This defaults to false as it can be a very expensive operation. + /// The cancellation token to use when making asynchronous operations. + /// An of s + /// This method makes use of cosmos dbs continuation tokens for efficient, cost effective paging utilising low RUs + ValueTask> PageAsync( + Expression>? predicate = null, + int pageSize = 25, + string? continuationToken = null, + bool returnTotal = false, + CancellationToken cancellationToken = default); + + //TODO: Write doc + ValueTask> PageAsync( + PartitionKey partitionKey, + Expression>? predicate = null, + int pageSize = 25, + string? continuationToken = null, + bool returnTotal = false, + CancellationToken cancellationToken = default); + + #if NET7_0_OR_GREATER /// /// Wraps the existing paging support to return an @@ -319,8 +321,8 @@ async IAsyncEnumerable PageAsync( && cancellationToken.IsCancellationRequested is false) { IPageQueryResult page = await PageAsync( - predicate, partitionKey, + predicate, pageNumber: ++ currentPage, 25, returnTotal: false, diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs index 49032ce06..9e4562712 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs @@ -69,6 +69,20 @@ ValueTask> UpdateAsync( bool ignoreEtag = false, CancellationToken cancellationToken = default); + /// + /// Updates the given cosmos item using the provided and supported patch operations. + /// + /// The string identifier. + /// The that will define the update operations to perform. + /// The cancellation token to use when making asynchronous operations. + /// Indicate to set IfMatchEtag in the ItemRequestOptions in the underlying Cosmos call. This requires TItem to implement the IItemWithEtag interface. + /// A representing the asynchronous operation. + ValueTask UpdateAsync( + string id, + Action> builder, + string? etag = default, + CancellationToken cancellationToken = default); + /// /// Updates the given cosmos item using the provided and supported patch operations. /// diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.Update.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.Update.cs index 4fd45f65d..210ce44e2 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.Update.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.Update.cs @@ -70,7 +70,7 @@ public async ValueTask> UpdateAsync(IEnumerable values /// public async ValueTask UpdateAsync(string id, Action> builder, - string? partitionKeyValue = null, + string partitionKeyValue, string? etag = default, CancellationToken cancellationToken = default) { @@ -80,8 +80,6 @@ public async ValueTask UpdateAsync(string id, await Task.CompletedTask; #endif - partitionKeyValue ??= id; - TItem? item = InMemoryStorage .GetValues() .Select(DeserializeItem) diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.cs index 8d9d2366f..247f756a0 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.cs @@ -3,6 +3,9 @@ // ReSharper disable once CheckNamespace + + + namespace Microsoft.Azure.CosmosRepository; /// @@ -22,4 +25,74 @@ public InMemoryRepository(ISpecificationEvaluator specificationEvaluator) => private void NotFound() => throw new CosmosException(string.Empty, HttpStatusCode.NotFound, 0, string.Empty, 0); private void Conflict() => throw new CosmosException(string.Empty, HttpStatusCode.Conflict, 0, string.Empty, 0); + + public ValueTask TryGetAsync(string id, IEnumerable partitionKeyValues, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask TryGetAsync(string id, PartitionKey partitionKey, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask GetAsync(string id, IEnumerable partitionKeyValues, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask> GetAsync(PartitionKey partitionKey, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask> GetAsync(PartitionKey partitionKey, Expression> predicate, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask ExistsAsync(string id, IEnumerable partitionKeyValues, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask ExistsAsync(Expression> predicate, PartitionKey partitionKey, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask CountAsync(Expression> predicate, IEnumerable partitionKeyValues, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask CountAsync(Expression> predicate, PartitionKey partitionKey, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask CountAsync(PartitionKey partitionKey, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask> PageAsync(PartitionKey partitionKey, Expression>? predicate = null, int pageNumber = 1, int pageSize = 25, bool returnTotal = false, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask> PageAsync(PartitionKey partitionKey, Expression>? predicate = null, int pageSize = 25, string? continuationToken = null, bool returnTotal = false, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask UpdateAsync(string id, Action> builder, string? etag = null, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask DeleteAsync(string id, IEnumerable partitionKeyValues, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Services/DefaultCosmosContainerService.cs b/src/Microsoft.Azure.CosmosRepository/Services/DefaultCosmosContainerService.cs index 701725649..061b59ec3 100644 --- a/src/Microsoft.Azure.CosmosRepository/Services/DefaultCosmosContainerService.cs +++ b/src/Microsoft.Azure.CosmosRepository/Services/DefaultCosmosContainerService.cs @@ -49,8 +49,8 @@ private async Task GetContainerAsync(Type itemType, bool forceContain DefaultTimeToLive = itemConfiguration.DefaultTimeToLive }; - if (itemConfiguration.PartitionKeyPaths.Length > 1) - containerProperties.PartitionKeyPaths = itemConfiguration.PartitionKeyPaths; + if (itemConfiguration.PartitionKeyPaths.Count() > 1) + containerProperties.PartitionKeyPaths = itemConfiguration.PartitionKeyPaths.ToList(); else containerProperties.PartitionKeyPath = containerProperties.PartitionKeyPaths[0]; diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/DefaultRepositoryFactoryTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/DefaultRepositoryFactoryTests.cs index 9999e61f2..c74cd5ba8 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/DefaultRepositoryFactoryTests.cs +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/DefaultRepositoryFactoryTests.cs @@ -1,6 +1,7 @@ // Copyright (c) David Pine. All rights reserved. // Licensed under the MIT License. + namespace Microsoft.Azure.CosmosRepositoryTests; public class DefaultRepositoryFactoryTests @@ -78,6 +79,8 @@ public abstract class CustomEntityBase : IItem string IItem.PartitionKey => GetPartitionKeyValue(); + IEnumerable IItem.PartitionKeys => [GetPartitionKeyValue()]; + public CustomEntityBase() => Type = GetType().Name; protected virtual string GetPartitionKeyValue() => Id; diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/Options/RepositoryOptionsTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/Options/RepositoryOptionsTests.cs index 541e01355..e84d53fc1 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/Options/RepositoryOptionsTests.cs +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/Options/RepositoryOptionsTests.cs @@ -17,7 +17,7 @@ public void RepositoryOptionsBuilderConfiguresItemCorrectly() Assert.Single(options.ContainerOptions); Assert.Equal("products", options.ContainerOptions[0].Name); - Assert.Equal("/category", options.ContainerOptions[0].PartitionKey); + Assert.Equal("/category", options.ContainerOptions[0].PartitionKeys?[0]); } [Fact] diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/Providers/DefaultCosmosItemConfigurationProviderTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/Providers/DefaultCosmosItemConfigurationProviderTests.cs index 83462e489..706514cd9 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/Providers/DefaultCosmosItemConfigurationProviderTests.cs +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/Providers/DefaultCosmosItemConfigurationProviderTests.cs @@ -29,7 +29,7 @@ public void GetOptionsAlwaysGetOptionsForItem() var throughputProperties = ThroughputProperties.CreateAutoscaleThroughput(400); _containerNameProvider.Setup(o => o.GetContainerName(typeof(Item1))).Returns(t => t.FullName!); - _partitionKeyPathProvider.Setup(o => o.GetPartitionKeyPath(typeof(Item1))).Returns("/id"); + _partitionKeyPathProvider.Setup(o => o.GetPartitionKeyPaths(typeof(Item1))).Returns(["/id"]); _uniqueKeyPolicyProvider.Setup(o => o.GetUniqueKeyPolicy(typeof(Item1))).Returns(uniqueKeyPolicy); _defaultTimeToLiveProvider.Setup(o => o.GetDefaultTimeToLive(typeof(Item1))).Returns(10); _syncContainerPropertiesProvider.Setup(o => o.GetWhetherToSyncContainerProperties(typeof(Item1))).Returns(true); @@ -38,7 +38,7 @@ public void GetOptionsAlwaysGetOptionsForItem() ItemConfiguration configuration = provider.GetItemConfiguration(); Assert.Equal(typeof(Item1).FullName, configuration.ContainerName); - Assert.Equal("/id", configuration.PartitionKeyPath); + Assert.Equal("/id", configuration.PartitionKeyPaths.Last()); Assert.Equal(uniqueKeyPolicy, configuration.UniqueKeyPolicy); Assert.Equal(10, configuration.DefaultTimeToLive); Assert.True(configuration.SyncContainerProperties); diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/Providers/DefaultCosmosPartitionKeyPathProviderTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/Providers/DefaultCosmosPartitionKeyPathProviderTests.cs index b1eac85d5..306e2f1d5 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/Providers/DefaultCosmosPartitionKeyPathProviderTests.cs +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/Providers/DefaultCosmosPartitionKeyPathProviderTests.cs @@ -24,8 +24,11 @@ public void CosmosCosmosPartitionKeyPathProviderCorrectlyGetsPathWhenOptionsAreD ICosmosPartitionKeyPathProvider provider = new DefaultCosmosPartitionKeyPathProvider(_options.Object); - var path = provider.GetPartitionKeyPaths(); - Assert.Equal("/firstName", path); + var paths = provider.GetPartitionKeyPaths(); + + Assert.NotEmpty(paths); + Assert.Single(paths); + Assert.Equal("/firstName", paths.First()); } [Fact] @@ -35,8 +38,10 @@ public void CosmosCosmosPartitionKeyPathProviderCorrectlyGetsPathWhenOptionsAreD ICosmosPartitionKeyPathProvider provider = new DefaultCosmosPartitionKeyPathProvider(_options.Object); - var path = provider.GetPartitionKeyPaths(); - Assert.Equal("/email", path); + var paths = provider.GetPartitionKeyPaths(); + Assert.NotEmpty(paths); + Assert.Single(paths); + Assert.Equal("/email", paths.First()); } [Fact] @@ -44,8 +49,11 @@ public void CosmosPartitionKeyPathProviderCorrectlyGetsPathWhenAttributeIsDefine { ICosmosPartitionKeyPathProvider provider = new DefaultCosmosPartitionKeyPathProvider(_options.Object); - var path = provider.GetPartitionKeyPaths(); - Assert.Equal("/pickles", path); + var paths = provider.GetPartitionKeyPaths(); + + Assert.NotEmpty(paths); + Assert.Single(paths); + Assert.Equal("/pickles", paths.First()); Assert.Equal("[\"Hey, where's the chips?!\"]", new Cosmos.PartitionKey(((IItem)new PickleChipsItem()).PartitionKey).ToString()); } } From 5ac57625d7dd15bef206437c3d5eafd889fa5127 Mon Sep 17 00:00:00 2001 From: Oleksandr Kyselov Date: Sun, 11 Feb 2024 14:09:25 +0100 Subject: [PATCH 07/27] - Checkpoint current tests working --- .../Attributes/PartitionKeyPathAttribute.cs | 2 +- .../Builders/ContainerOptionsBuilder.cs | 2 +- .../Repositories/DefaultRepository.Update.cs | 16 +++++++-- .../Repositories/DefaultRepository.cs | 11 ++++++ .../Repositories/IWriteOnlyRepository.cs | 36 +++++++++++++++++-- .../Repositories/InMemoryRepository.cs | 12 +++++-- .../Services/DefaultCosmosContainerService.cs | 2 +- ....CosmosEventSourcingAcceptanceTests.csproj | 6 ++++ .../Stores/EventStoreTests.ReadAggregate.cs | 5 +-- .../Stores/EventStoreTests.cs | 6 ++-- .../DefaultRepositoryTests.cs | 12 ++++--- .../InMemoryRepositoryTests.cs | 13 ++++--- 12 files changed, 98 insertions(+), 25 deletions(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Attributes/PartitionKeyPathAttribute.cs b/src/Microsoft.Azure.CosmosRepository/Attributes/PartitionKeyPathAttribute.cs index 938402b2e..2877337d1 100644 --- a/src/Microsoft.Azure.CosmosRepository/Attributes/PartitionKeyPathAttribute.cs +++ b/src/Microsoft.Azure.CosmosRepository/Attributes/PartitionKeyPathAttribute.cs @@ -25,6 +25,6 @@ public sealed class PartitionKeyPathAttribute(params string[] paths) : Attribute /// /// Gets the path values of the parition key. /// - public string[] Paths { get; } = paths != null && paths.Length > 1 ? paths : throw new ArgumentNullException(nameof(paths), "At least one path is required."); + public string[] Paths { get; } = paths != null && paths.Length >= 1 ? paths : throw new ArgumentNullException(nameof(paths), "At least one path is required."); } diff --git a/src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs b/src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs index 8e54cd5b0..55a6e5472 100644 --- a/src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs +++ b/src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs @@ -79,7 +79,7 @@ public ContainerOptionsBuilder WithContainer(string name) /// public ContainerOptionsBuilder WithPartitionKey(string partitionKey) { - if(string.IsNullOrWhiteSpace(partitionKey)) throw new ArgumentNullException(nameof(partitionKey)); + if(partitionKey == null) throw new ArgumentNullException(nameof(partitionKey)); PartitionKeys ??= []; PartitionKeys.Add(partitionKey); return this; diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs index a399b1d33..defbafc5a 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs @@ -2,6 +2,8 @@ // Licensed under the MIT License. // ReSharper disable once CheckNamespace +using Microsoft.Azure.Cosmos; + namespace Microsoft.Azure.CosmosRepository; internal sealed partial class DefaultRepository @@ -63,7 +65,7 @@ public async ValueTask UpdateAsync(string id, /// public async ValueTask UpdateAsync(string id, Action> builder, - string? partitionKeyValue = null, + string? partitionKeyValue, string? etag = default, CancellationToken cancellationToken = default) { @@ -72,14 +74,24 @@ public async ValueTask UpdateAsync(string id, //TODO: Write docs public async ValueTask UpdateAsync(string id, - Action> builder, IEnumerable partitionKeyValues, + Action> builder, string? etag = default, CancellationToken cancellationToken = default) { await InternalUpdateAsync(id, builder, BuildPartitionKey(partitionKeyValues, id), etag, cancellationToken); } + //TODO: Write docs + public async ValueTask UpdateAsync(string id, + PartitionKey partitionKey, + Action> builder, + string? etag = null, + CancellationToken cancellationToken = default) + { + await InternalUpdateAsync(id, builder, partitionKey, etag, cancellationToken); + } + private async ValueTask InternalUpdateAsync(string id, Action> builder, PartitionKey? partitionKey = null, diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs index 42d4c7650..a8d50406d 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs @@ -5,6 +5,8 @@ + + namespace Microsoft.Azure.CosmosRepository; /// @@ -141,4 +143,13 @@ internal static PartitionKey BuildPartitionKey(TItem item) return BuildPartitionKey(item.PartitionKeys); } + public ValueTask UpdateAsync(string id, Action> builder, IEnumerable partitionKeyValues, string? etag = null, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask UpdateAsync(string id, Action> builder, PartitionKey partitionKey, string? etag = null, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } } \ No newline at end of file diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs index 9e4562712..d048bf036 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs @@ -73,6 +73,7 @@ ValueTask> UpdateAsync( /// Updates the given cosmos item using the provided and supported patch operations. /// /// The string identifier. + /// The partition key value if different than the . /// The that will define the update operations to perform. /// The cancellation token to use when making asynchronous operations. /// Indicate to set IfMatchEtag in the ItemRequestOptions in the underlying Cosmos call. This requires TItem to implement the IItemWithEtag interface. @@ -80,6 +81,7 @@ ValueTask> UpdateAsync( ValueTask UpdateAsync( string id, Action> builder, + string partitionKeyValue, string? etag = default, CancellationToken cancellationToken = default); @@ -87,7 +89,6 @@ ValueTask UpdateAsync( /// Updates the given cosmos item using the provided and supported patch operations. /// /// The string identifier. - /// The partition key value if different than the . /// The that will define the update operations to perform. /// The cancellation token to use when making asynchronous operations. /// Indicate to set IfMatchEtag in the ItemRequestOptions in the underlying Cosmos call. This requires TItem to implement the IItemWithEtag interface. @@ -95,7 +96,38 @@ ValueTask UpdateAsync( ValueTask UpdateAsync( string id, Action> builder, - string partitionKeyValue, + string? etag = default, + CancellationToken cancellationToken = default); + + /// + /// Updates the given cosmos item using the provided and supported patch operations. + /// + /// The string identifier. + /// The partition key values if different than the . + /// The that will define the update operations to perform. + /// The cancellation token to use when making asynchronous operations. + /// Indicate to set IfMatchEtag in the ItemRequestOptions in the underlying Cosmos call. This requires TItem to implement the IItemWithEtag interface. + /// A representing the asynchronous operation. + ValueTask UpdateAsync( + string id, + Action> builder, + IEnumerable partitionKeyValues, + string? etag = default, + CancellationToken cancellationToken = default); + + /// + /// Updates the given cosmos item using the provided and supported patch operations. + /// + /// The string identifier. + /// The partition key if different than the . + /// The that will define the update operations to perform. + /// The cancellation token to use when making asynchronous operations. + /// Indicate to set IfMatchEtag in the ItemRequestOptions in the underlying Cosmos call. This requires TItem to implement the IItemWithEtag interface. + /// A representing the asynchronous operation. + ValueTask UpdateAsync( + string id, + Action> builder, + PartitionKey partitionKey, string? etag = default, CancellationToken cancellationToken = default); diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.cs index 247f756a0..8e3cb21fe 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.cs @@ -4,8 +4,6 @@ // ReSharper disable once CheckNamespace - - namespace Microsoft.Azure.CosmosRepository; /// @@ -91,6 +89,16 @@ public ValueTask UpdateAsync(string id, Action> bu throw new NotImplementedException(); } + public ValueTask UpdateAsync(string id, Action> builder, IEnumerable partitionKeyValues, string? etag = null, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public ValueTask UpdateAsync(string id, Action> builder, PartitionKey partitionKey, string? etag = null, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + public ValueTask DeleteAsync(string id, IEnumerable partitionKeyValues, CancellationToken cancellationToken = default) { throw new NotImplementedException(); diff --git a/src/Microsoft.Azure.CosmosRepository/Services/DefaultCosmosContainerService.cs b/src/Microsoft.Azure.CosmosRepository/Services/DefaultCosmosContainerService.cs index 061b59ec3..9268a47ae 100644 --- a/src/Microsoft.Azure.CosmosRepository/Services/DefaultCosmosContainerService.cs +++ b/src/Microsoft.Azure.CosmosRepository/Services/DefaultCosmosContainerService.cs @@ -52,7 +52,7 @@ private async Task GetContainerAsync(Type itemType, bool forceContain if (itemConfiguration.PartitionKeyPaths.Count() > 1) containerProperties.PartitionKeyPaths = itemConfiguration.PartitionKeyPaths.ToList(); else - containerProperties.PartitionKeyPath = containerProperties.PartitionKeyPaths[0]; + containerProperties.PartitionKeyPath = itemConfiguration.PartitionKeyPaths.Last(); Container container = _options.IsAutoResourceCreationIfNotExistsEnabled diff --git a/tests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests.csproj b/tests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests.csproj index cf0fdc3b4..f0c6b64e2 100644 --- a/tests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests.csproj +++ b/tests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests.csproj @@ -28,6 +28,12 @@ + + + Always + + + $(NoWarn);NU1507 diff --git a/tests/Microsoft.Azure.CosmosEventSourcingTests/Stores/EventStoreTests.ReadAggregate.cs b/tests/Microsoft.Azure.CosmosEventSourcingTests/Stores/EventStoreTests.ReadAggregate.cs index 25499b15d..ea7321905 100644 --- a/tests/Microsoft.Azure.CosmosEventSourcingTests/Stores/EventStoreTests.ReadAggregate.cs +++ b/tests/Microsoft.Azure.CosmosEventSourcingTests/Stores/EventStoreTests.ReadAggregate.cs @@ -7,6 +7,7 @@ using System.Runtime.CompilerServices; using System.Threading.Tasks; using FluentAssertions; +using Microsoft.Azure.Cosmos; using Microsoft.Azure.CosmosEventSourcing.Aggregates; using Microsoft.Azure.CosmosEventSourcing.Events; using Microsoft.Azure.CosmosEventSourcing.Exceptions; @@ -95,7 +96,7 @@ public async Task ReadAggregateAsync_AggregateWithReplayMethod_ReplaysEvents() repository .Setup(o => - o.GetAsync(x => x.PartitionKey == "A", default)) + o.GetAsync(new PartitionKey("A"), default)) .ReturnsAsync(events); //Act @@ -124,7 +125,7 @@ public async Task ReadAggregateAsync_AggregateMapper_MapsAggregateCorrectly() repository .Setup(o => - o.GetAsync(x => x.PartitionKey == "A", default)) + o.GetAsync(new PartitionKey("A"), default)) .ReturnsAsync(events); //Act diff --git a/tests/Microsoft.Azure.CosmosEventSourcingTests/Stores/EventStoreTests.cs b/tests/Microsoft.Azure.CosmosEventSourcingTests/Stores/EventStoreTests.cs index 394cc3c54..47d5aa76a 100644 --- a/tests/Microsoft.Azure.CosmosEventSourcingTests/Stores/EventStoreTests.cs +++ b/tests/Microsoft.Azure.CosmosEventSourcingTests/Stores/EventStoreTests.cs @@ -7,6 +7,7 @@ using System.Runtime.CompilerServices.Context; using System.Threading.Tasks; using FluentAssertions; +using Microsoft.Azure.Cosmos; using Microsoft.Azure.CosmosEventSourcing.Events; using Microsoft.Azure.CosmosEventSourcing.Exceptions; using Microsoft.Azure.CosmosEventSourcing.Stores; @@ -141,7 +142,7 @@ public async Task GetAsync_EventsInDb_GetsAllEvents() _readonlyRepository .Setup(o => - o.GetAsync(x => x.PartitionKey == Pk, default)) + o.GetAsync(new PartitionKey(Pk), default)) .ReturnsAsync(_eventItemsWithAtomicEvents); //Act @@ -179,7 +180,8 @@ public async Task StreamAsync_EventsInDb_StreamsAllEvents() _readonlyRepository .SetupSequence(o => o.PageAsync( - x => x.PartitionKey == Pk, + new PartitionKey(Pk), + default, 5, It.IsAny(), false, diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/DefaultRepositoryTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/DefaultRepositoryTests.cs index 6b5a5bae7..ca7cede43 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/DefaultRepositoryTests.cs +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/DefaultRepositoryTests.cs @@ -55,7 +55,7 @@ public async Task GetAsyncGivenExpressionQueriesContainerCorrectly() _container .Setup(o => o.GetItemLinqQueryable( - false, null, null, It.IsAny())) + false, null, It.IsAny(), It.IsAny())) .Returns(queryable); _queryableProcessor.Setup(o => o.IterateAsync(queryable, It.IsAny())) @@ -80,6 +80,8 @@ public async Task ExistsAsyncGivenExpressionQueriesContainerCorrectly() new(){Id = "ab"} }; + QueryRequestOptions options = new (); + Expression> predicate = item => item.Id == "a" || item.Id == "ab"; Expression> expectedPredicate = _expressionProvider.Build(predicate)!; @@ -89,7 +91,7 @@ public async Task ExistsAsyncGivenExpressionQueriesContainerCorrectly() _container .Setup(o => o.GetItemLinqQueryable( - false, null, null, It.IsAny())) + false, null, It.IsAny(), It.IsAny())) .Returns(queryable); _queryableProcessor.Setup(o => o.CountAsync(queryable, It.IsAny())) @@ -414,7 +416,7 @@ public async Task UpdateAsync_Patch_WhenEtagIsEmpty_UseDefaultEtagValueInPatchOp _containerProviderForTestItemWithETag.Setup(cp => cp.GetContainerAsync()).ReturnsAsync(_container.Object); // Act - await RepositoryForItemWithETag.UpdateAsync(id, _ => { }, null, etag, default); + await RepositoryForItemWithETag.UpdateAsync(id, _ => { }, etag, default); // Assert _container.Verify( @@ -433,7 +435,7 @@ public async Task UpdateAsync_Patch_WhenEtagIsWhiteSpace_UseDefaultEtagValueInPa _containerProviderForTestItemWithETag.Setup(cp => cp.GetContainerAsync()).ReturnsAsync(_container.Object); // Act - await RepositoryForItemWithETag.UpdateAsync(id, _ => { }, null, etag, default); + await RepositoryForItemWithETag.UpdateAsync(id, _ => { }, etag, default); // Assert _container.Verify( @@ -452,7 +454,7 @@ public async Task UpdateAsync_Patch_WhenEtagIsSet_UseSetEtagValueInPatchOptions( _containerProviderForTestItemWithETag.Setup(cp => cp.GetContainerAsync()).ReturnsAsync(_container.Object); // Act - await RepositoryForItemWithETag.UpdateAsync(id, _ => { }, null, etag, default); + await RepositoryForItemWithETag.UpdateAsync(id, _ => { }, etag, default); // Assert _container.Verify( diff --git a/tests/Microsoft.Azure.CosmosRepositoryTests/InMemoryRepositoryTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/InMemoryRepositoryTests.cs index f64a22bc3..8249b48cc 100644 --- a/tests/Microsoft.Azure.CosmosRepositoryTests/InMemoryRepositoryTests.cs +++ b/tests/Microsoft.Azure.CosmosRepositoryTests/InMemoryRepositoryTests.cs @@ -827,7 +827,7 @@ public async Task UpdateAsync_PropertiesToPatch_UpdatesValues() InMemoryStorage.GetDictionary().TryAddAsJson(dog.Id, dog); //Act - await _dogRepository.UpdateAsync(dog.Id, builder => builder.Replace(d => d.Name, "kenny"), dog.Breed); + await _dogRepository.UpdateAsync(dog.Id, builder => builder.Replace(d => d.Name, "kenny"), dog.Breed, default, default); //Assert Dog addedDog = await _dogRepository.GetAsync(dog.Id, dog.Breed); @@ -851,7 +851,7 @@ public async Task UpdateAsync_PropertiesToPatch_WhenEtagMatches_UpdatesValues() InMemoryStorage.GetDictionary().TryAddAsJson(person.Id, person); //Act - await _personRepository.UpdateAsync(person.Id, builder => builder.Replace(p => p.Name, "kenny"), default, + await _personRepository.UpdateAsync(person.Id, builder => builder.Replace(p => p.Name, "kenny"), originalEtag, default); //Assert @@ -874,8 +874,7 @@ public async Task UpdateAsync_PropertiesToPatch_WhenEtagIsNull_UpdatesValues() InMemoryStorage.GetDictionary().TryAddAsJson(person.Id, person); //Act - await _personRepository.UpdateAsync(person.Id, builder => builder.Replace(p => p.Name, "kenny"), default, null, - default); + await _personRepository.UpdateAsync(person.Id, builder => builder.Replace(p => p.Name, "kenny")); //Assert Person updatedPerson = _personRepository.DeserializeItem(InMemoryStorage.GetDictionary().First().Value); @@ -898,7 +897,7 @@ public async Task UpdateAsync_PropertiesToPatch_WhenEtagIsEmpty_UpdatesValues() InMemoryStorage.GetDictionary().TryAddAsJson(person.Id, person); //Act - await _personRepository.UpdateAsync(person.Id, builder => builder.Replace(p => p.Name, "kenny"), default, + await _personRepository.UpdateAsync(person.Id, builder => builder.Replace(p => p.Name, "kenny"), string.Empty, default); //Assert @@ -921,7 +920,7 @@ public async Task UpdateAsync_PropertiesToPatch_WhenEtagIsWhiteSpace_UpdatesValu InMemoryStorage.GetDictionary().TryAddAsJson(person.Id, person); //Act - await _personRepository.UpdateAsync(person.Id, builder => builder.Replace(p => p.Name, "kenny"), default, + await _personRepository.UpdateAsync(person.Id, builder => builder.Replace(p => p.Name, "kenny"), " ", default); //Assert @@ -945,7 +944,7 @@ public async Task UpdateAsync_PropertiesToPatch_WhenEtagDoesNotMatch_ThrowsCosmo //Act & Assert CosmosException cosmosException = await Assert.ThrowsAsync(() => - _personRepository.UpdateAsync(person.Id, builder => builder.Replace(p => p.Name, "kenny"), default, + _personRepository.UpdateAsync(person.Id, builder => builder.Replace(p => p.Name, "kenny"), Guid.NewGuid().ToString(), default).AsTask()); Assert.Equal(HttpStatusCode.PreconditionFailed, cosmosException.StatusCode); From 7a495057c7c7d07aef5ae4bb977dca6f14a7e598 Mon Sep 17 00:00:00 2001 From: xInfinitYz <123937346+xInfinitYz@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:15:11 +0100 Subject: [PATCH 08/27] Update src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs Co-authored-by: David Pine --- .../Builders/ContainerOptionsBuilder.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs b/src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs index 55a6e5472..63d593ffe 100644 --- a/src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs +++ b/src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs @@ -79,9 +79,11 @@ public ContainerOptionsBuilder WithContainer(string name) /// public ContainerOptionsBuilder WithPartitionKey(string partitionKey) { - if(partitionKey == null) throw new ArgumentNullException(nameof(partitionKey)); + if (partitionKey is null) throw new ArgumentNullException(nameof(partitionKey)); + PartitionKeys ??= []; PartitionKeys.Add(partitionKey); + return this; } From 1af90586ff6f809cc7bc7b2c8925c808e0eb7489 Mon Sep 17 00:00:00 2001 From: xInfinitYz <123937346+xInfinitYz@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:15:25 +0100 Subject: [PATCH 09/27] Update src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosPartitionKeyPathProvider.cs Co-authored-by: David Pine --- .../Providers/DefaultCosmosPartitionKeyPathProvider.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosPartitionKeyPathProvider.cs b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosPartitionKeyPathProvider.cs index 0cc918521..31e4fa7a9 100644 --- a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosPartitionKeyPathProvider.cs +++ b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosPartitionKeyPathProvider.cs @@ -20,7 +20,9 @@ public IEnumerable GetPartitionKeyPaths(Type itemType) ContainerOptionsBuilder? optionsBuilder = _options.Value.GetContainerOptions(itemType); - if (optionsBuilder is { } && optionsBuilder.PartitionKeys != null && optionsBuilder.PartitionKeys.Any() && optionsBuilder.PartitionKeys.All(x => !string.IsNullOrEmpty(x))) + if (optionsBuilder is { PartitionKeys: { } } && + optionsBuilder.PartitionKeys.Any() && + optionsBuilder.PartitionKeys.All(x => !string.IsNullOrWhiteSpace(x))) { return optionsBuilder.PartitionKeys; } From cd2048a0aa786ba94493366decfcffe4dbd5019c Mon Sep 17 00:00:00 2001 From: xInfinitYz <123937346+xInfinitYz@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:15:33 +0100 Subject: [PATCH 10/27] Update src/Microsoft.Azure.CosmosRepository/Providers/ICosmosPartitionKeyPathProvider.cs Co-authored-by: David Pine --- .../Providers/ICosmosPartitionKeyPathProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.Azure.CosmosRepository/Providers/ICosmosPartitionKeyPathProvider.cs b/src/Microsoft.Azure.CosmosRepository/Providers/ICosmosPartitionKeyPathProvider.cs index b17341c7b..041176832 100644 --- a/src/Microsoft.Azure.CosmosRepository/Providers/ICosmosPartitionKeyPathProvider.cs +++ b/src/Microsoft.Azure.CosmosRepository/Providers/ICosmosPartitionKeyPathProvider.cs @@ -15,5 +15,6 @@ interface ICosmosPartitionKeyPathProvider /// The item for which the partition keys paths corresponds. /// A string array representing the partition key paths, i.e.; "/partion" IEnumerable GetPartitionKeyPaths() where TItem : IItem; + IEnumerable GetPartitionKeyPaths(Type itemType); } From 3da89ac7c4cb30449d6d1f8ec9ce1904ad16547e Mon Sep 17 00:00:00 2001 From: xInfinitYz <123937346+xInfinitYz@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:16:12 +0100 Subject: [PATCH 11/27] Update src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs Co-authored-by: David Pine --- .../Repositories/DefaultRepository.Count.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs index ff7904e53..524bb8472 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs @@ -32,7 +32,7 @@ public async ValueTask CountAsync( public async ValueTask CountAsync( PartitionKey partitionKey, - CancellationToken cancellationToken = default) + CancellationToken cancellationToken = default) { return await InternalCountAsync(null, partitionKey, cancellationToken); } From 9ca24b4bbe3dbd294a402074ed280304507e064a Mon Sep 17 00:00:00 2001 From: xInfinitYz <123937346+xInfinitYz@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:16:22 +0100 Subject: [PATCH 12/27] Update src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs Co-authored-by: David Pine --- .../Repositories/DefaultRepository.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs index a8d50406d..aaf0b2153 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs @@ -2,11 +2,6 @@ // Licensed under the MIT License. // ReSharper disable once CheckNamespace - - - - - namespace Microsoft.Azure.CosmosRepository; /// From 84ccff2cc9807cb89d32ab7eb040474ad4903313 Mon Sep 17 00:00:00 2001 From: xInfinitYz <123937346+xInfinitYz@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:16:45 +0100 Subject: [PATCH 13/27] Update src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs Co-authored-by: David Pine --- .../Repositories/DefaultRepository.Count.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs index 524bb8472..bb2942536 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs @@ -48,7 +48,7 @@ await containerProvider.GetContainerAsync() QueryRequestOptions options = new(); - if(specification.PartitionKey != null) + if (specification.PartitionKey is not null) { options.PartitionKey = specification.PartitionKey; } From 7d6a3d8b515638e56495cfe0338c5c56773692ea Mon Sep 17 00:00:00 2001 From: xInfinitYz <123937346+xInfinitYz@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:16:52 +0100 Subject: [PATCH 14/27] Update src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs Co-authored-by: David Pine --- .../Repositories/DefaultRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs index aaf0b2153..ea4df546f 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs @@ -72,7 +72,7 @@ await iterator.ReadNextAsync(cancellationToken) /// Thrown when the items list is empty. internal static PartitionKey BuildPartitionKey(List items) { - if (items.Count == 0) + if (items.Count is 0) { throw new ArgumentException( "Unable to perform batch operation with no items", From 8e4780d5ffe8cb84ecf5474b6e1560cad3619687 Mon Sep 17 00:00:00 2001 From: xInfinitYz <123937346+xInfinitYz@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:17:00 +0100 Subject: [PATCH 15/27] Update src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs Co-authored-by: David Pine --- .../Repositories/DefaultRepository.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs index ea4df546f..0ff0f134e 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs @@ -92,12 +92,20 @@ internal static PartitionKey BuildPartitionKey(List items) internal static PartitionKey BuildPartitionKey(IEnumerable values, string? defaultValue = null) { var builder = new PartitionKeyBuilder(); - if (values == null || !values.Any()) + var keys = values?.ToList(); + if (keys is null or keys is { Count: 0 }) { - return !string.IsNullOrEmpty(defaultValue) ? new PartitionKey(defaultValue) : default; + return !string.IsNullOrWhiteSpace(defaultValue) + ? new PartitionKey(defaultValue) + : default; } - if (values.Count() > 3) throw new ArgumentException("Unable to build partition key. The max allowed partition key values is 3", nameof(values)); + if (keys.Count > 3) + { + throw new ArgumentException( + "Unable to build partition key. The max allowed number of partition key values is 3.", + nameof(values)); + } foreach (var value in values) From f3e0d3677e4d4da2b99f2cfae89f897d36d4f738 Mon Sep 17 00:00:00 2001 From: xInfinitYz <123937346+xInfinitYz@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:17:06 +0100 Subject: [PATCH 16/27] Update src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs Co-authored-by: David Pine --- .../Repositories/DefaultRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs index 0ff0f134e..8d77684c6 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs @@ -136,7 +136,7 @@ internal static PartitionKey BuildPartitionKey(string? value, string? defaultVal /// Thrown when the provided item is null. internal static PartitionKey BuildPartitionKey(TItem item) { - if (item == null) + if (item is null) { throw new ArgumentException( "Unable to perform operation with null item", From 0a89c795104547a22a1bdd64cd446f2b2f5f3ae6 Mon Sep 17 00:00:00 2001 From: xInfinitYz <123937346+xInfinitYz@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:17:19 +0100 Subject: [PATCH 17/27] Update src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs Co-authored-by: David Pine --- .../Repositories/DefaultRepository.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs index 8d77684c6..3364a39ec 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs @@ -123,7 +123,10 @@ internal static PartitionKey BuildPartitionKey(IEnumerable values, strin /// A PartitionKey object constructed from the provided string value. internal static PartitionKey BuildPartitionKey(string? value, string? defaultValue = null) { - if ((value == null || string.IsNullOrWhiteSpace(value)) && !string.IsNullOrWhiteSpace(value)) return new PartitionKey(defaultValue); + if (string.IsNullOrWhiteSpace(value) && !string.IsNullOrWhiteSpace(defaultValue)) + { + return new PartitionKey(defaultValue); + } return new PartitionKey(value); } From 0acb9857e45e8fa5fe8f62f1012fdfa023f36128 Mon Sep 17 00:00:00 2001 From: xInfinitYz <123937346+xInfinitYz@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:17:36 +0100 Subject: [PATCH 18/27] Update src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs Co-authored-by: David Pine --- .../Repositories/DefaultRepository.Count.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs index bb2942536..18cef5721 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs @@ -84,7 +84,7 @@ await containerProvider.GetContainerAsync() requestOptions: requestOptions, linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions); - if(predicate != null) + if (predicate is not null) { query = query.Where(repositoryExpressionProvider.Build(predicate)); } From 7a4ace82f526320be5c15361c88ca1783b051e5f Mon Sep 17 00:00:00 2001 From: xInfinitYz <123937346+xInfinitYz@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:22:41 +0100 Subject: [PATCH 19/27] Update src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs Co-authored-by: David Pine --- .../Repositories/DefaultRepository.Exists.cs | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs index fc78bc731..17a120d06 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs @@ -39,34 +39,34 @@ public async ValueTask ExistsAsync( } return true; - } - - /// - public async ValueTask ExistsAsync( - Expression> predicate, - CancellationToken cancellationToken = default) - { - return await ExistsAsync(predicate, default, cancellationToken); - } - - //TODO: Write docs - public async ValueTask ExistsAsync( - Expression> predicate, - PartitionKey partitionKey, + } + + /// + public async ValueTask ExistsAsync( + Expression> predicate, + CancellationToken cancellationToken = default) + { + return await ExistsAsync(predicate, default, cancellationToken); + } + + //TODO: Write docs + public async ValueTask ExistsAsync( + Expression> predicate, + PartitionKey partitionKey, CancellationToken cancellationToken = default) { Container container = - await containerProvider.GetContainerAsync().ConfigureAwait(false); - - var requestOptions = new QueryRequestOptions(); - + await containerProvider.GetContainerAsync().ConfigureAwait(false); + + var requestOptions = new QueryRequestOptions(); + if (partitionKey != default) { requestOptions.PartitionKey = partitionKey; - } + } IQueryable query = - container.GetItemLinqQueryable( + container.GetItemLinqQueryable( requestOptions: requestOptions, linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions) .Where(repositoryExpressionProvider.Build(predicate)); @@ -75,13 +75,13 @@ public async ValueTask ExistsAsync( var count = await cosmosQueryableProcessor.CountAsync(query, cancellationToken); return count > 0; - } - - - //TODO: Write doc - public async ValueTask ExistsAsync(string id, IEnumerable partitionKeyValues, CancellationToken cancellationToken = default) - { - return await ExistsAsync(id, BuildPartitionKey(partitionKeyValues, id), cancellationToken); - } + } + + + //TODO: Write doc + public async ValueTask ExistsAsync(string id, IEnumerable partitionKeyValues, CancellationToken cancellationToken = default) + { + return await ExistsAsync(id, BuildPartitionKey(partitionKeyValues, id), cancellationToken); + } } From 779eb45a1a4b8bc96baec2fdf3c8358d31a8a778 Mon Sep 17 00:00:00 2001 From: xInfinitYz <123937346+xInfinitYz@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:26:03 +0100 Subject: [PATCH 20/27] Update src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs Co-authored-by: David Pine --- .../Repositories/DefaultRepository.Paging.cs | 120 +++++++++--------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs index 99833eb70..895aedac8 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs @@ -11,33 +11,33 @@ internal sealed partial class DefaultRepository { /// public async ValueTask> PageAsync( - Expression>? predicate = null, + Expression>? predicate = null, int pageSize = 25, string? continuationToken = null, bool returnTotal = false, CancellationToken cancellationToken = default) - { + { return await InternalPageAsync(predicate, default, pageSize, continuationToken, returnTotal, cancellationToken); - } - - //TODO: Write doc - public async ValueTask> PageAsync( - PartitionKey partitionKey, - Expression>? predicate = null, - int pageSize = 25, - string? continuationToken = null, - bool returnTotal = false, - CancellationToken cancellationToken = default) - { - return await InternalPageAsync(predicate, partitionKey, pageSize, continuationToken, returnTotal, cancellationToken); - } - - private async ValueTask> InternalPageAsync( - Expression>? predicate = null, - PartitionKey partitionKey = default, - int pageSize = 25, - string? continuationToken = null, - bool returnTotal = false, + } + + //TODO: Write doc + public async ValueTask> PageAsync( + PartitionKey partitionKey, + Expression>? predicate = null, + int pageSize = 25, + string? continuationToken = null, + bool returnTotal = false, + CancellationToken cancellationToken = default) + { + return await InternalPageAsync(predicate, partitionKey, pageSize, continuationToken, returnTotal, cancellationToken); + } + + private async ValueTask> InternalPageAsync( + Expression>? predicate = null, + PartitionKey partitionKey = default, + int pageSize = 25, + string? continuationToken = null, + bool returnTotal = false, CancellationToken cancellationToken = default) { Container container = await containerProvider.GetContainerAsync() @@ -46,11 +46,11 @@ private async ValueTask> InternalPageAsync( QueryRequestOptions options = new() { MaxItemCount = pageSize - }; - - if (partitionKey != default) - { - options.PartitionKey = partitionKey; + }; + + if (partitionKey != default) + { + options.PartitionKey = partitionKey; } IQueryable query = container @@ -82,33 +82,33 @@ private async ValueTask> InternalPageAsync( items.AsReadOnly(), charge + countResponse?.RequestCharge ?? 0, resultingContinuationToken); - } - - public async ValueTask> PageAsync( - PartitionKey partitionKey, - Expression>? predicate = null, - int pageNumber = 1, - int pageSize = 25, - bool returnTotal = false, - CancellationToken cancellationToken = default) - { - return await InternalPageAsync(predicate, partitionKey, pageNumber, pageSize, returnTotal, cancellationToken); - } - - /// - public async ValueTask> PageAsync( - Expression>? predicate = null, - int pageNumber = 1, - int pageSize = 25, - bool returnTotal = false, - CancellationToken cancellationToken = default) - { - return await InternalPageAsync(predicate, default, pageNumber, pageSize, returnTotal, cancellationToken); - } - + } + + public async ValueTask> PageAsync( + PartitionKey partitionKey, + Expression>? predicate = null, + int pageNumber = 1, + int pageSize = 25, + bool returnTotal = false, + CancellationToken cancellationToken = default) + { + return await InternalPageAsync(predicate, partitionKey, pageNumber, pageSize, returnTotal, cancellationToken); + } + + /// + public async ValueTask> PageAsync( + Expression>? predicate = null, + int pageNumber = 1, + int pageSize = 25, + bool returnTotal = false, + CancellationToken cancellationToken = default) + { + return await InternalPageAsync(predicate, default, pageNumber, pageSize, returnTotal, cancellationToken); + } + private async ValueTask> InternalPageAsync( - Expression>? predicate = null, + Expression>? predicate = null, PartitionKey partitionKey = default, int pageNumber = 1, int pageSize = 25, @@ -116,17 +116,17 @@ private async ValueTask> InternalPageAsync( CancellationToken cancellationToken = default) { Container container = await containerProvider.GetContainerAsync() - .ConfigureAwait(false); - - var options = new QueryRequestOptions(); - - if(partitionKey != default) - { - options.PartitionKey = partitionKey; + .ConfigureAwait(false); + + var options = new QueryRequestOptions(); + + if (partitionKey != default) + { + options.PartitionKey = partitionKey; } IQueryable query = container - .GetItemLinqQueryable( + .GetItemLinqQueryable( requestOptions: options, linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions) .Where(repositoryExpressionProvider From 1fe41da7e178c81e168b6561c7e6e027f886522b Mon Sep 17 00:00:00 2001 From: xInfinitYz <123937346+xInfinitYz@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:26:31 +0100 Subject: [PATCH 21/27] Update src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs Co-authored-by: David Pine --- .../Repositories/DefaultRepository.Read.cs | 106 +++++++++--------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs index bec644ac4..d327b66da 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs @@ -5,7 +5,7 @@ namespace Microsoft.Azure.CosmosRepository; internal sealed partial class DefaultRepository -{ +{ /// public async ValueTask TryGetAsync( string id, @@ -21,14 +21,14 @@ internal sealed partial class DefaultRepository logger.LogItemNotFoundHandled(id, partitionKeyValue ?? id, e); return default; } - } - - //TODO: Write doc - public async ValueTask TryGetAsync( - string id, - PartitionKey partitionKey, + } + + //TODO: Write doc + public async ValueTask TryGetAsync( + string id, + PartitionKey partitionKey, CancellationToken cancellationToken = default) - { + { try { return await GetAsync(id, partitionKey, cancellationToken); @@ -37,13 +37,13 @@ internal sealed partial class DefaultRepository { logger.LogItemNotFoundHandled(id, partitionKey.ToString() ?? id, e); return default; - } - } - - //TODO: Write doc - public async ValueTask TryGetAsync( - string id, - IEnumerable partitionKeyValues, + } + } + + //TODO: Write doc + public async ValueTask TryGetAsync( + string id, + IEnumerable partitionKeyValues, CancellationToken cancellationToken = default) { try @@ -55,21 +55,21 @@ internal sealed partial class DefaultRepository logger.LogItemNotFoundHandled(id, partitionKeyValues ?? [id], e); return default; } - } + } /// public ValueTask GetAsync( string id, string? partitionKeyValue = null, CancellationToken cancellationToken = default) => - GetAsync(id, new PartitionKey(partitionKeyValue ?? id), cancellationToken); - - //TODO: Write doc - public ValueTask GetAsync( - string id, - IEnumerable partitionKeyValues, - CancellationToken cancellationToken = default) => - GetAsync(id, BuildPartitionKey(partitionKeyValues), cancellationToken); + GetAsync(id, new PartitionKey(partitionKeyValue ?? id), cancellationToken); + + //TODO: Write doc + public ValueTask GetAsync( + string id, + IEnumerable partitionKeyValues, + CancellationToken cancellationToken = default) => + GetAsync(id, BuildPartitionKey(partitionKeyValues), cancellationToken); /// public async ValueTask GetAsync( @@ -97,19 +97,19 @@ await container.ReadItemAsync(id, partitionKey, cancellationToken: cancel logger.LogItemRead(item); return repositoryExpressionProvider.CheckItem(item); - } - + } + //TODO: Write doc public async ValueTask> GetAsync( PartitionKey partitionKey, CancellationToken cancellationToken = default) { Container container = - await containerProvider.GetContainerAsync().ConfigureAwait(false); - - IQueryable query = container.GetItemLinqQueryable( - requestOptions: new QueryRequestOptions() { PartitionKey = partitionKey }, - linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions); + await containerProvider.GetContainerAsync().ConfigureAwait(false); + + IQueryable query = container.GetItemLinqQueryable( + requestOptions: new QueryRequestOptions() { PartitionKey = partitionKey }, + linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions); logger.LogQueryConstructed(query); @@ -119,42 +119,42 @@ public async ValueTask> GetAsync( logger.LogQueryExecuted(query, charge); return items; - } - - /// - public async ValueTask> GetAsync( - Expression> predicate, - CancellationToken cancellationToken = default) - { + } + + /// + public async ValueTask> GetAsync( + Expression> predicate, + CancellationToken cancellationToken = default) + { return await GetAsync(predicate, default, cancellationToken); - } - + } + //TODO: Write doc - public async ValueTask> GetAsync( + public async ValueTask> GetAsync( PartitionKey partitionKey, Expression> predicate, CancellationToken cancellationToken = default) - { + { return await GetAsync(predicate, partitionKey, cancellationToken); - } - - private async ValueTask> GetAsync( - Expression> predicate, - PartitionKey partitionKey = default, + } + + private async ValueTask> GetAsync( + Expression> predicate, + PartitionKey partitionKey = default, CancellationToken cancellationToken = default) { Container container = - await containerProvider.GetContainerAsync().ConfigureAwait(false); - - var requestOptions = new QueryRequestOptions(); - + await containerProvider.GetContainerAsync().ConfigureAwait(false); + + var requestOptions = new QueryRequestOptions(); + if (partitionKey != default) { requestOptions.PartitionKey = partitionKey; - } + } IQueryable query = - container.GetItemLinqQueryable( + container.GetItemLinqQueryable( requestOptions: requestOptions, linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions) .Where(repositoryExpressionProvider.Build(predicate)); From a3fd25af37064b95e025cbb3bb3abe5bb422fded Mon Sep 17 00:00:00 2001 From: xInfinitYz <123937346+xInfinitYz@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:26:59 +0100 Subject: [PATCH 22/27] Update src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Specs.cs Co-authored-by: David Pine --- .../Repositories/DefaultRepository.Specs.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Specs.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Specs.cs index 4d5d926b6..1c9686e8a 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Specs.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Specs.cs @@ -20,9 +20,9 @@ public async ValueTask QueryAsync( if (specification.UseContinuationToken) options.MaxItemCount = specification.PageSize; - - if(specification.PartitionKey != null) - options.PartitionKey = specification.PartitionKey; + + if (specification.PartitionKey is not null) + options.PartitionKey = specification.PartitionKey; IQueryable query = container @@ -32,7 +32,7 @@ public async ValueTask QueryAsync( linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions) .Where(repositoryExpressionProvider.Default()); - query = specificationEvaluator.GetQuery(query, specification); + query = specificationEvaluator.GetQuery(query, specification); logger.LogQueryConstructed(query); From 2cb6092e87585783a1f5e4bd340be8764498c186 Mon Sep 17 00:00:00 2001 From: xInfinitYz <123937346+xInfinitYz@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:27:17 +0100 Subject: [PATCH 23/27] Update src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs Co-authored-by: David Pine --- .../Repositories/DefaultRepository.Update.cs | 71 +++++++++---------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs index defbafc5a..c94c7769b 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. // ReSharper disable once CheckNamespace -using Microsoft.Azure.Cosmos; namespace Microsoft.Azure.CosmosRepository; @@ -24,8 +23,8 @@ await containerProvider.GetContainerAsync() options.IfMatchEtag = string.IsNullOrWhiteSpace(valueWithEtag.Etag) ? default : valueWithEtag.Etag; - } - + } + PartitionKey partitionKey = BuildPartitionKey(value); ItemResponse response = @@ -51,51 +50,51 @@ public async ValueTask> UpdateAsync( await Task.WhenAll(updateTasks).ConfigureAwait(false); return updateTasks.Select(x => x.Result); - } - - //TODO: Write docs - public async ValueTask UpdateAsync(string id, - Action> builder, - string? etag = default, + } + + //TODO: Write docs + public async ValueTask UpdateAsync(string id, + Action> builder, + string? etag = default, CancellationToken cancellationToken = default) - { + { await InternalUpdateAsync(id, builder, null, etag, cancellationToken); - } - + } + /// public async ValueTask UpdateAsync(string id, Action> builder, string? partitionKeyValue, string? etag = default, CancellationToken cancellationToken = default) - { + { await InternalUpdateAsync(id, builder, BuildPartitionKey(partitionKeyValue, id), etag, cancellationToken); - } - - //TODO: Write docs - public async ValueTask UpdateAsync(string id, - IEnumerable partitionKeyValues, - Action> builder, - string? etag = default, + } + + //TODO: Write docs + public async ValueTask UpdateAsync(string id, + IEnumerable partitionKeyValues, + Action> builder, + string? etag = default, CancellationToken cancellationToken = default) { await InternalUpdateAsync(id, builder, BuildPartitionKey(partitionKeyValues, id), etag, cancellationToken); - } - - //TODO: Write docs - public async ValueTask UpdateAsync(string id, - PartitionKey partitionKey, - Action> builder, - string? etag = null, - CancellationToken cancellationToken = default) - { - await InternalUpdateAsync(id, builder, partitionKey, etag, cancellationToken); - } - - private async ValueTask InternalUpdateAsync(string id, - Action> builder, - PartitionKey? partitionKey = null, - string? etag = default, + } + + //TODO: Write docs + public async ValueTask UpdateAsync(string id, + PartitionKey partitionKey, + Action> builder, + string? etag = null, + CancellationToken cancellationToken = default) + { + await InternalUpdateAsync(id, builder, partitionKey, etag, cancellationToken); + } + + private async ValueTask InternalUpdateAsync(string id, + Action> builder, + PartitionKey? partitionKey = null, + string? etag = default, CancellationToken cancellationToken = default) { CosmosPropertyNamingPolicy? propertyNamingPolicy = From 86d2310831943f8aa18bcfc8082be8f35da5e026 Mon Sep 17 00:00:00 2001 From: Oleksandr Kyselov Date: Thu, 15 Feb 2024 21:28:13 +0100 Subject: [PATCH 24/27] Implemented PR suggested changes --- .../Repositories/DefaultRepository.Batch.cs | 2 -- .../Repositories/DefaultRepository.Delete.cs | 1 - .../Repositories/DefaultRepository.Exists.cs | 6 +++--- .../Repositories/DefaultRepository.Paging.cs | 2 -- 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Batch.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Batch.cs index c33fae26c..08c103bf7 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Batch.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Batch.cs @@ -2,8 +2,6 @@ // Licensed under the MIT License. // ReSharper disable once CheckNamespace -using Microsoft.Azure.CosmosRepository.Extensions; - namespace Microsoft.Azure.CosmosRepository; internal partial class DefaultRepository diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Delete.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Delete.cs index 5193c4ffc..70cb3fe72 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Delete.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Delete.cs @@ -3,7 +3,6 @@ // ReSharper disable once CheckNamespace -using Newtonsoft.Json.Linq; namespace Microsoft.Azure.CosmosRepository; diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs index 17a120d06..4ad9b1d83 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Exists.cs @@ -51,9 +51,9 @@ public async ValueTask ExistsAsync( //TODO: Write docs public async ValueTask ExistsAsync( - Expression> predicate, - PartitionKey partitionKey, - CancellationToken cancellationToken = default) + Expression> predicate, + PartitionKey partitionKey, + CancellationToken cancellationToken = default) { Container container = await containerProvider.GetContainerAsync().ConfigureAwait(false); diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs index 99833eb70..e4f67eb6f 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs @@ -3,8 +3,6 @@ // ReSharper disable once CheckNamespace -using Microsoft.Extensions.Options; - namespace Microsoft.Azure.CosmosRepository; internal sealed partial class DefaultRepository From 3441f65e35da5e4d1d37f619654ed5704fc4a814 Mon Sep 17 00:00:00 2001 From: Oleksandr Kyselov Date: Sun, 18 Feb 2024 08:31:46 +0100 Subject: [PATCH 25/27] fix conditional statements --- .../Repositories/DefaultRepository.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs index 3364a39ec..b67d619a1 100644 --- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs +++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs @@ -93,14 +93,14 @@ internal static PartitionKey BuildPartitionKey(IEnumerable values, strin { var builder = new PartitionKeyBuilder(); var keys = values?.ToList(); - if (keys is null or keys is { Count: 0 }) + if (keys is null or { Count: 0 }) { return !string.IsNullOrWhiteSpace(defaultValue) ? new PartitionKey(defaultValue) : default; } - if (keys.Count > 3) + if (keys?.Count > 3) { throw new ArgumentException( "Unable to build partition key. The max allowed number of partition key values is 3.", @@ -108,7 +108,7 @@ internal static PartitionKey BuildPartitionKey(IEnumerable values, strin } - foreach (var value in values) + foreach (var value in values!) { builder.Add(value); } From be93da4eced5731d159f8ae797c9a9c011706db0 Mon Sep 17 00:00:00 2001 From: Oleksandr Kyselov Date: Sun, 12 May 2024 18:54:51 +0200 Subject: [PATCH 26/27] Exposed partition key method to specification builder. --- .../Specification/Builder/ISpecificationBuilder.cs | 7 +++++++ .../Specification/Builder/SpecificationBuilder.cs | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Azure.CosmosRepository/Specification/Builder/ISpecificationBuilder.cs b/src/Microsoft.Azure.CosmosRepository/Specification/Builder/ISpecificationBuilder.cs index 4fea5cc9a..cbf9e24a0 100644 --- a/src/Microsoft.Azure.CosmosRepository/Specification/Builder/ISpecificationBuilder.cs +++ b/src/Microsoft.Azure.CosmosRepository/Specification/Builder/ISpecificationBuilder.cs @@ -58,4 +58,11 @@ public interface ISpecificationBuilder /// The token used by Cosmos DB to provide efficient, cost effective paging. /// An instance of a ISpecificationBuilder ContinuationToken(string continuationToken); + + /// + /// Sets the partition key for the query. + /// + /// + /// + ISpecificationBuilder PartitionKey(PartitionKey partitionKey); } diff --git a/src/Microsoft.Azure.CosmosRepository/Specification/Builder/SpecificationBuilder.cs b/src/Microsoft.Azure.CosmosRepository/Specification/Builder/SpecificationBuilder.cs index 2ffe12eb3..0892a89d2 100644 --- a/src/Microsoft.Azure.CosmosRepository/Specification/Builder/SpecificationBuilder.cs +++ b/src/Microsoft.Azure.CosmosRepository/Specification/Builder/SpecificationBuilder.cs @@ -45,7 +45,7 @@ public ISpecificationBuilder PageSize(int pageSize) return this; } - + /// public ISpecificationBuilder PageNumber(int pageNumber) { Specification.PageNumber = pageNumber; @@ -65,6 +65,7 @@ public ISpecificationBuilder ContinuationToken(string continuati return this; } + /// public ISpecificationBuilder PartitionKey(PartitionKey partitionKey) { Specification.PartitionKey = partitionKey; From ff1fb9f31463a210bb33fcc712839d6dd3aeb5a3 Mon Sep 17 00:00:00 2001 From: xInfinitYz <123937346+xInfinitYz@users.noreply.github.com> Date: Tue, 14 May 2024 19:51:03 +0200 Subject: [PATCH 27/27] Added partitionkey to specification builder --- .../Specification/Builder/ISpecificationBuilder.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Microsoft.Azure.CosmosRepository/Specification/Builder/ISpecificationBuilder.cs b/src/Microsoft.Azure.CosmosRepository/Specification/Builder/ISpecificationBuilder.cs index 4fea5cc9a..dcf0499e6 100644 --- a/src/Microsoft.Azure.CosmosRepository/Specification/Builder/ISpecificationBuilder.cs +++ b/src/Microsoft.Azure.CosmosRepository/Specification/Builder/ISpecificationBuilder.cs @@ -58,4 +58,11 @@ public interface ISpecificationBuilder /// The token used by Cosmos DB to provide efficient, cost effective paging. /// An instance of a ISpecificationBuilder ContinuationToken(string continuationToken); + + /// + /// Sets the partition key for the query. + /// + /// The partition key + /// An instance of a + ISpecificationBuilder PartitionKey(PartitionKey partitionKey); }