diff --git a/src/Microsoft.Azure.CosmosEventSourcing/Items/EventItem.cs b/src/Microsoft.Azure.CosmosEventSourcing/Items/EventItem.cs
index 0b5e3a729..f96c6a51e 100644
--- a/src/Microsoft.Azure.CosmosEventSourcing/Items/EventItem.cs
+++ b/src/Microsoft.Azure.CosmosEventSourcing/Items/EventItem.cs
@@ -60,6 +60,11 @@ public DomainEvent DomainEvent
///
public string PartitionKey { get; set; } = null!;
+ ///
+ /// The values used to partition the event.
+ ///
+ public IEnumerable PartitionKeys { get; set; } = null!;
+
///
/// The name of the event stored.
///
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.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.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/Attributes/PartitionKeyPathAttribute.cs b/src/Microsoft.Azure.CosmosRepository/Attributes/PartitionKeyPathAttribute.cs
index 97123322e..2877337d1 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.
+ /// Gets the path values 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 257162b7b..63d593ffe 100644
--- a/src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs
+++ b/src/Microsoft.Azure.CosmosRepository/Builders/ContainerOptionsBuilder.cs
@@ -23,9 +23,9 @@ public class ContainerOptionsBuilder(Type type)
internal string? Name { get; private set; }
///
- /// The partition key for the container.
+ /// The partition keys for the container.
///
- internal string? PartitionKey { get; private set; }
+ internal IList? PartitionKeys { get; private set; }
///
/// The default time to live for a container.
@@ -79,7 +79,11 @@ public ContainerOptionsBuilder WithContainer(string name)
///
public ContainerOptionsBuilder WithPartitionKey(string partitionKey)
{
- PartitionKey = partitionKey ?? throw new ArgumentNullException(nameof(partitionKey));
+ if (partitionKey is null) throw new ArgumentNullException(nameof(partitionKey));
+
+ PartitionKeys ??= [];
+ PartitionKeys.Add(partitionKey);
+
return this;
}
diff --git a/src/Microsoft.Azure.CosmosRepository/IItem.cs b/src/Microsoft.Azure.CosmosRepository/IItem.cs
index adad0264e..ab0986777 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; }
+ IEnumerable PartitionKeys { get; }
}
diff --git a/src/Microsoft.Azure.CosmosRepository/Item.cs b/src/Microsoft.Azure.CosmosRepository/Item.cs
index a3b08499e..3a751c7ac 100644
--- a/src/Microsoft.Azure.CosmosRepository/Item.cs
+++ b/src/Microsoft.Azure.CosmosRepository/Item.cs
@@ -44,10 +44,16 @@ public abstract class Item : IItem
public string Type { get; set; }
///
- /// Gets the PartitionKey based on .
+ /// Gets the PartitionKey based on last record.
/// Implemented explicitly to keep out of Item API
///
- string IItem.PartitionKey => GetPartitionKeyValue();
+ string IItem.PartitionKey => GetPartitionKeyValues().Last();
+
+ ///
+ /// Gets the PartitionKeys based on .
+ /// Implemented explicitly to keep out of Item API
+ ///
+ IEnumerable IItem.PartitionKeys => GetPartitionKeyValues();
///
/// Default constructor, assigns type name to property.
@@ -56,10 +62,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. 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 IEnumerable GetPartitionKeyValues() => new string[] { GetPartitionKeyValue() };
}
diff --git a/src/Microsoft.Azure.CosmosRepository/Logging/LoggerExtensions.cs b/src/Microsoft.Azure.CosmosRepository/Logging/LoggerExtensions.cs
index aac8cb234..23425dd10 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,
+ IEnumerable 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,
+ 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 0b5a28616..15beae74d 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, Exception?> 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, Exception?> 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/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/src/Microsoft.Azure.CosmosRepository/Options/ItemConfiguration.cs b/src/Microsoft.Azure.CosmosRepository/Options/ItemConfiguration.cs
index 0b52ec62a..05df68c5f 100644
--- a/src/Microsoft.Azure.CosmosRepository/Options/ItemConfiguration.cs
+++ b/src/Microsoft.Azure.CosmosRepository/Options/ItemConfiguration.cs
@@ -3,32 +3,59 @@
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;
-
- public string ContainerName { get; } = containerName;
-
- public string PartitionKeyPath { get; } = partitionKeyPath;
-
- public UniqueKeyPolicy? UniqueKeyPolicy { get; } = uniqueKeyPolicy;
-
- public ThroughputProperties? ThroughputProperties { get; } = throughputProperties;
-
- public int DefaultTimeToLive { get; } = defaultTimeToLive;
-
- public bool SyncContainerProperties { get; } = syncContainerProperties;
-
- public ChangeFeedOptions? ChangeFeedOptions { get; } = changeFeedOptions;
-
- public bool UseStrictTypeChecking { get; } = useStrictTypeChecking;
-}
\ No newline at end of file
+ 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)
+ : 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,
+ 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 Type Type { get; }
+
+ public string ContainerName { get; }
+
+ public IEnumerable PartitionKeyPaths { get; }
+
+ public UniqueKeyPolicy? UniqueKeyPolicy { get; }
+
+ public ThroughputProperties? ThroughputProperties { get; }
+
+ 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..3704f29de 100644
--- a/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosItemConfigurationProvider.cs
+++ b/src/Microsoft.Azure.CosmosRepository/Providers/DefaultCosmosItemConfigurationProvider.cs
@@ -39,7 +39,7 @@ private ItemConfiguration AddOptions(Type itemType)
itemType.IsItem();
var containerName = containerNameProvider.GetContainerName(itemType);
- var partitionKeyPath = cosmosPartitionKeyPathProvider.GetPartitionKeyPath(itemType);
+ var partitionKeyPaths = cosmosPartitionKeyPathProvider.GetPartitionKeyPaths(itemType);
UniqueKeyPolicy? uniqueKeyPolicy = cosmosUniqueKeyPolicyProvider.GetUniqueKeyPolicy(itemType);
var timeToLive = containerDefaultTimeToLiveProvider.GetDefaultTimeToLive(itemType);
var sync = syncContainerPropertiesProvider.GetWhetherToSyncContainerProperties(itemType);
@@ -49,11 +49,12 @@ private ItemConfiguration AddOptions(Type itemType)
return new(
itemType,
containerName,
- partitionKeyPath,
+ 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 d3e2fca60..31e4fa7a9 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;
///
@@ -10,23 +11,27 @@ 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 { PartitionKeys: { } } &&
+ optionsBuilder.PartitionKeys.Any() &&
+ optionsBuilder.PartitionKeys.All(x => !string.IsNullOrWhiteSpace(x)))
{
- return optionsBuilder.PartitionKey!;
+ return optionsBuilder.PartitionKeys;
}
return Attribute.GetCustomAttribute(
itemType, attributeType) is PartitionKeyPathAttribute partitionKeyPathAttribute
- ? partitionKeyPathAttribute.Path
- : "/id";
+ ? partitionKeyPathAttribute.Paths
+ : ["/id"];
}
+
+
}
diff --git a/src/Microsoft.Azure.CosmosRepository/Providers/ICosmosPartitionKeyPathProvider.cs b/src/Microsoft.Azure.CosmosRepository/Providers/ICosmosPartitionKeyPathProvider.cs
index 618a47bbc..041176832 100644
--- a/src/Microsoft.Azure.CosmosRepository/Providers/ICosmosPartitionKeyPathProvider.cs
+++ b/src/Microsoft.Azure.CosmosRepository/Providers/ICosmosPartitionKeyPathProvider.cs
@@ -10,11 +10,11 @@ namespace Microsoft.Azure.CosmosRepository.Providers;
interface ICosmosPartitionKeyPathProvider
{
///
- /// Gets the partition key path for a given type.
+ /// Gets the partition key paths 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);
+ /// 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);
}
diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Batch.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Batch.cs
index 467c34ca2..08c103bf7 100644
--- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Batch.cs
+++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Batch.cs
@@ -13,11 +13,22 @@ public async ValueTask UpdateAsBatchAsync(
{
var list = items.ToList();
- var partitionKey = GetPartitionKeyValue(list);
+ PartitionKey partitionKey = BuildPartitionKey(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 +57,22 @@ public async ValueTask CreateAsBatchAsync(
{
var list = items.ToList();
- var partitionKey = GetPartitionKeyValue(list);
+ PartitionKey partitionKey = BuildPartitionKey(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 +87,30 @@ public async ValueTask CreateAsBatchAsync(
}
}
+ ///
public async ValueTask DeleteAsBatchAsync(
IEnumerable items,
CancellationToken cancellationToken = default)
{
var list = items.ToList();
- var partitionKey = GetPartitionKeyValue(list);
+ PartitionKey partitionKey = BuildPartitionKey(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 +124,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.Count.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Count.cs
index 74c1b9aef..18cef5721 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 is not null)
+ {
+ options.PartitionKey = specification.PartitionKey;
+ }
+
IQueryable query = container.GetItemLinqQueryable(
+ requestOptions: options,
linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions);
query = specificationEvaluator.GetQuery(query, specification, evaluateCriteriaOnly: true);
@@ -41,23 +63,41 @@ 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 is not null)
+ {
+ query = query.Where(repositoryExpressionProvider.Build(predicate));
+ }
TryLogDebugDetails(logger, () => $"Read: {query}");
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.Create.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Create.cs
index 3c1e0671d..599de1887 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 = BuildPartitionKey(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..70cb3fe72 100644
--- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Delete.cs
+++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Delete.cs
@@ -3,6 +3,7 @@
// ReSharper disable once CheckNamespace
+
namespace Microsoft.Azure.CosmosRepository;
internal sealed partial class DefaultRepository
@@ -11,14 +12,20 @@ internal sealed partial class DefaultRepository
public ValueTask DeleteAsync(
TItem value,
CancellationToken cancellationToken = default) =>
- DeleteAsync(value.Id, value.PartitionKey, 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..4ad9b1d83 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(
@@ -45,12 +45,29 @@ public async ValueTask ExistsAsync(
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();
+
+ if (partitionKey != default)
+ {
+ requestOptions.PartitionKey = partitionKey;
+ }
+
IQueryable query =
container.GetItemLinqQueryable(
+ requestOptions: requestOptions,
linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions)
.Where(repositoryExpressionProvider.Build(predicate));
@@ -59,4 +76,12 @@ 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 4bd2a486f..4f96d2ece 100644
--- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs
+++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Paging.cs
@@ -14,6 +14,29 @@ public async ValueTask> PageAsync(
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,
+ CancellationToken cancellationToken = default)
{
Container container = await containerProvider.GetContainerAsync()
.ConfigureAwait(false);
@@ -23,6 +46,11 @@ public async ValueTask> PageAsync(
MaxItemCount = pageSize
};
+ if (partitionKey != default)
+ {
+ options.PartitionKey = partitionKey;
+ }
+
IQueryable query = container
.GetItemLinqQueryable(
requestOptions: options,
@@ -54,6 +82,17 @@ public async ValueTask> PageAsync(
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,
@@ -61,12 +100,32 @@ public async ValueTask> PageAsync(
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,
+ PartitionKey partitionKey = default,
+ int pageNumber = 1,
+ int pageSize = 25,
+ bool returnTotal = false,
+ CancellationToken cancellationToken = default)
{
Container container = await containerProvider.GetContainerAsync()
.ConfigureAwait(false);
+ var options = new QueryRequestOptions();
+
+ if (partitionKey != default)
+ {
+ options.PartitionKey = partitionKey;
+ }
+
IQueryable query = container
.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 00438f95c..d327b66da 100644
--- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs
+++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Read.cs
@@ -23,6 +23,40 @@ internal sealed partial class DefaultRepository
}
}
+ //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
+ {
+ return await GetAsync(id, partitionKeyValues, cancellationToken);
+ }
+ catch (CosmosException e) when (e.StatusCode is HttpStatusCode.NotFound)
+ {
+ logger.LogItemNotFoundHandled(id, partitionKeyValues ?? [id], e);
+ return default;
+ }
+ }
+
///
public ValueTask GetAsync(
string id,
@@ -30,6 +64,13 @@ public ValueTask GetAsync(
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);
+
///
public async ValueTask GetAsync(
string id,
@@ -58,16 +99,63 @@ await container.ReadItemAsync(id, partitionKey, cancellationToken: cancel
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);
+
+ logger.LogQueryConstructed(query);
+
+ (IEnumerable items, var charge) =
+ await cosmosQueryableProcessor.IterateAsync(query, cancellationToken);
+
+ logger.LogQueryExecuted(query, charge);
+
+ 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);
+ }
+
+ private 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(
+ requestOptions: requestOptions,
linqSerializerOptions: optionsMonitor.CurrentValue.SerializationOptions)
.Where(repositoryExpressionProvider.Build(predicate));
diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Specs.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Specs.cs
index 47e5f6857..1c9686e8a 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 is not null)
+ options.PartitionKey = specification.PartitionKey;
+
IQueryable query = container
.GetItemLinqQueryable(
@@ -32,6 +34,7 @@ public async ValueTask QueryAsync(
query = specificationEvaluator.GetQuery(query, specification);
+
logger.LogQueryConstructed(query);
(List items, var charge, var continuationToken) =
diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs
index 4de7db98f..c94c7769b 100644
--- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs
+++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.Update.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
// ReSharper disable once CheckNamespace
+
namespace Microsoft.Azure.CosmosRepository;
internal sealed partial class DefaultRepository
@@ -24,8 +25,10 @@ await containerProvider.GetContainerAsync()
: 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);
@@ -49,9 +52,48 @@ 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)
+ {
+ 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? partitionKeyValue = null,
+ 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,
CancellationToken cancellationToken = default)
{
@@ -63,15 +105,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 ca09bc816..b67d619a1 100644
--- a/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs
+++ b/src/Microsoft.Azure.CosmosRepository/Repositories/DefaultRepository.cs
@@ -62,4 +62,100 @@ await iterator.ReadNextAsync(cancellationToken)
return (results, charge, continuationToken);
}
+
+ ///
+ /// 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.Count is 0)
+ {
+ throw new ArgumentException(
+ "Unable to perform batch operation with no items",
+ nameof(items));
+ }
+ return BuildPartitionKey(items[0]);
+ }
+
+ ///
+ /// 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();
+ var keys = values?.ToList();
+ if (keys is null or { Count: 0 })
+ {
+ return !string.IsNullOrWhiteSpace(defaultValue)
+ ? new PartitionKey(defaultValue)
+ : default;
+ }
+
+ 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!)
+ {
+ builder.Add(value);
+ }
+
+ return builder.Build();
+ }
+
+ ///
+ /// 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(defaultValue))
+ {
+ return new PartitionKey(defaultValue);
+ }
+ return new PartitionKey(value);
+ }
+
+ ///
+ /// 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 is null)
+ {
+ throw new ArgumentException(
+ "Unable to perform operation with null item",
+ nameof(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/IReadOnlyRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/IReadOnlyRepository.cs
index 222596d26..88bc44e08 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: Write doc
+ 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
@@ -118,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.
///
@@ -142,6 +177,12 @@ ValueTask ExistsAsync(
Expression> predicate,
CancellationToken cancellationToken = default);
+ //TODO: Write doc
+ ValueTask ExistsAsync(
+ Expression> predicate,
+ PartitionKey partitionKey,
+ CancellationToken cancellationToken = default);
+
///
/// Queries cosmos DB to obtain the count of items.
///
@@ -166,22 +207,21 @@ ValueTask CountAsync(
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.
- ///
- /// 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,
+ //TODO: Write doc
+ ValueTask CountAsync(
+ Expression> predicate,
+ IEnumerable partitionKeyValues,
+ CancellationToken cancellationToken = default);
+
+ //TODO: Write doc
+ ValueTask CountAsync(
+ Expression> predicate,
+ PartitionKey partitionKey,
+ CancellationToken cancellationToken = default);
+
+ //TODO: Write doc
+ ValueTask CountAsync(
+ PartitionKey partitionKey,
CancellationToken cancellationToken = default);
///
@@ -220,6 +260,43 @@ ValueTask> PageAsync(
bool returnTotal = false,
CancellationToken cancellationToken = default);
+ //TODO: Write doc
+ ValueTask> PageAsync(
+ PartitionKey partitionKey,
+ Expression>? predicate = null,
+ int pageNumber = 1,
+ int pageSize = 25,
+ 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
@@ -232,6 +309,7 @@ ValueTask> PageAsync(
/// This method makes use of Cosmos DB's continuation tokens for efficient, cost effective paging utilizing low RUs
async IAsyncEnumerable PageAsync(
Expression>? predicate = null,
+ PartitionKey partitionKey = default,
int limit = 1_000,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
@@ -243,6 +321,7 @@ async IAsyncEnumerable PageAsync(
&& cancellationToken.IsCancellationRequested is false)
{
IPageQueryResult page = await PageAsync(
+ partitionKey,
predicate,
pageNumber: ++ currentPage,
25,
diff --git a/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs b/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs
index 3c0aa18ad..d048bf036 100644
--- a/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs
+++ b/src/Microsoft.Azure.CosmosRepository/Repositories/IWriteOnlyRepository.cs
@@ -81,7 +81,53 @@ ValueTask> UpdateAsync(
ValueTask UpdateAsync(
string id,
Action> builder,
- string? partitionKeyValue = null,
+ string partitionKeyValue,
+ string? etag = default,
+ 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.
+ ///
+ /// 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);
@@ -107,6 +153,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/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..8e3cb21fe 100644
--- a/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.cs
+++ b/src/Microsoft.Azure.CosmosRepository/Repositories/InMemoryRepository.cs
@@ -3,6 +3,7 @@
// ReSharper disable once CheckNamespace
+
namespace Microsoft.Azure.CosmosRepository;
///
@@ -22,4 +23,84 @@ 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 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();
+ }
}
\ 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 6adad2b61..9268a47ae 100644
--- a/src/Microsoft.Azure.CosmosRepository/Services/DefaultCosmosContainerService.cs
+++ b/src/Microsoft.Azure.CosmosRepository/Services/DefaultCosmosContainerService.cs
@@ -45,11 +45,15 @@ private async Task GetContainerAsync(Type itemType, bool forceContain
Id = _options.ContainerPerItemType
? itemConfiguration.ContainerName
: _options.ContainerId,
- PartitionKeyPath = itemConfiguration.PartitionKeyPath,
UniqueKeyPolicy = itemConfiguration.UniqueKeyPolicy ?? new(),
DefaultTimeToLive = itemConfiguration.DefaultTimeToLive
};
+ if (itemConfiguration.PartitionKeyPaths.Count() > 1)
+ containerProperties.PartitionKeyPaths = itemConfiguration.PartitionKeyPaths.ToList();
+ else
+ containerProperties.PartitionKeyPath = itemConfiguration.PartitionKeyPaths.Last();
+
Container container =
_options.IsAutoResourceCreationIfNotExistsEnabled
? await database.CreateContainerIfNotExistsAsync(
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/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);
}
diff --git a/src/Microsoft.Azure.CosmosRepository/Specification/Builder/SpecificationBuilder.cs b/src/Microsoft.Azure.CosmosRepository/Specification/Builder/SpecificationBuilder.cs
index 1579bedca..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;
@@ -64,4 +64,11 @@ 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
diff --git a/tests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests.csproj b/tests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests.csproj
index 15aada29f..f0c6b64e2 100644
--- a/tests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests.csproj
+++ b/tests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests/Microsoft.Azure.CosmosEventSourcingAcceptanceTests.csproj
@@ -28,4 +28,14 @@
+
+
+ Always
+
+
+
+
+ $(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.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/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/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);
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/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/DefaultCosmosClientProviderTests.cs b/tests/Microsoft.Azure.CosmosRepositoryTests/Providers/DefaultCosmosClientProviderTests.cs
index 353677d64..804ec71a6 100644
--- a/tests/Microsoft.Azure.CosmosRepositoryTests/Providers/DefaultCosmosClientProviderTests.cs
+++ b/tests/Microsoft.Azure.CosmosRepositoryTests/Providers/DefaultCosmosClientProviderTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) David Pine. All rights reserved.
+// Copyright (c) David Pine. All rights reserved.
// Licensed under the MIT License.
namespace Microsoft.Azure.CosmosRepositoryTests.Providers;
@@ -68,4 +68,4 @@ public async Task DefaultCosmosClientProviderCorrectlyDisposesOverloadWithTokenC
Assert.IsType(ex.GetBaseException());
}
}
-}
+}
\ No newline at end of file
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 79b21d974..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.GetPartitionKeyPath();
- 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.GetPartitionKeyPath();
- 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.GetPartitionKeyPath();
- 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());
}
}