Skip to content

feat(cosmos): support custom document IDs and partition keys - #8699

Merged
ReubenBond merged 4 commits into
dotnet:mainfrom
ReubenBond:fix/cosmos-doc-id-provider
Aug 2, 2026
Merged

ReubenBond merged 4 commits into
dotnet:mainfrom
ReubenBond:fix/cosmos-doc-id-provider

Conversation

@ReubenBond

@ReubenBond ReubenBond commented Nov 1, 2023

Copy link
Copy Markdown
Member

Currently, only custom partition keys are supported. This is a compilation-breaking change, since it changes the interface used to specify the partition key. The usage of that interface was broken in previous releases, and the fix is straightforward: change your implementation of IPartitionKeyProvider to IDocumentIdProvider. Then, if you do not wish to customize the document id, inject DefaultDocumentIdProvider and call GetId to get the document id.

Microsoft Reviewers: Open in CodeFlow

@ReubenBond
ReubenBond force-pushed the fix/cosmos-doc-id-provider branch from 774631d to 5c3c5e4 Compare November 1, 2023 22:58
@OmnipotentOwl

Copy link
Copy Markdown
Contributor

@ReubenBond, Looking ahead at the capabilities for the Microsoft.Extensions.Configuration support you implemented in #9204 should this PR include the ability to configure the type to call from DI for the IDocumentIdProvider of a given provider when configuring via IConfiguration? The wiring might not be there yet but should the configuration value be present on the CosmosGrainStorageOptions class?

@ReubenBond ReubenBond changed the title Support customization of both document id and partition key for Cosmos DB grain persistence feat(cosmos)!: support custom document IDs and partition keys May 29, 2026
Copilot AI review requested due to automatic review settings August 1, 2026 15:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Orleans Cosmos DB grain storage provider to support customizing both the document id and partition key via a new IDocumentIdProvider, replacing the prior partition-key-only abstraction (IPartitionKeyProvider) as a breaking change.

Changes:

  • Replaced IPartitionKeyProvider with IDocumentIdProvider and introduced DefaultDocumentIdProvider.
  • Updated CosmosGrainStorage to use the new provider for id/partition key generation and adjusted clear-state behavior.
  • Updated hosting extensions and Cosmos persistence tests to use the new provider.
Show a summary per file
File Description
test/Extensions/Tester.Cosmos/PersistenceProviderTests_Cosmos.cs Updates test setup to construct storage using DefaultDocumentIdProvider.
src/Azure/Orleans.Persistence.Cosmos/IPartitionKeyProvider.cs Removes the old partition-key-only provider interface and default implementation.
src/Azure/Orleans.Persistence.Cosmos/IDocumentIdProvider.cs Adds the new interface for generating document ids and partition keys.
src/Azure/Orleans.Persistence.Cosmos/HostingExtensions.cs Switches DI wiring from partition key provider to document id provider and updates extension methods.
src/Azure/Orleans.Persistence.Cosmos/DefaultDocumentIdProvider.cs Adds the default implementation for document id + partition key generation.
src/Azure/Orleans.Persistence.Cosmos/CosmosGrainStorage.cs Uses IDocumentIdProvider for read/write/clear and updates legacy /GrainType compatibility checks.

Copilot's findings

Suppressed comments (2)

src/Azure/Orleans.Persistence.Cosmos/CosmosGrainStorage.cs:369

  • The /GrainType partition key path compatibility check currently rejects any custom IDocumentIdProvider (anything other than DefaultDocumentIdProvider). That blocks custom document id support for clusters using existing containers partitioned by /GrainType, even if the custom provider returns a compatible partition key (grainType). Consider relaxing this check (or checking compatibility based on the returned partition key) so that custom document ids can be used with legacy /GrainType containers.
                if (_partitionKeyPath == GRAINTYPE_PARTITION_KEY_PATH &&
                    _documentIdProvider is not DefaultDocumentIdProvider)

src/Azure/Orleans.Persistence.Cosmos/CosmosGrainStorage.cs:370

  • This OrleansConfigurationException message still refers to a "Custom partition key provider", but the code now uses IDocumentIdProvider/DefaultDocumentIdProvider. Updating the message will make misconfiguration easier to understand.
                if (_partitionKeyPath == GRAINTYPE_PARTITION_KEY_PATH &&
                    _documentIdProvider is not DefaultDocumentIdProvider)
                    throw new OrleansConfigurationException("Custom partition key provider is not compatible with partition key path set to /GrainType");
  • Files reviewed: 6/6 changed files
  • Comments generated: 4

Comment thread src/Azure/Orleans.Persistence.Cosmos/CosmosGrainStorage.cs
Comment thread src/Azure/Orleans.Persistence.Cosmos/IDocumentIdProvider.cs Outdated
Comment thread src/Azure/Orleans.Persistence.Cosmos/DefaultDocumentIdProvider.cs Outdated
Comment thread src/Azure/Orleans.Persistence.Cosmos/HostingExtensions.cs Outdated
@ReubenBond ReubenBond changed the title feat(cosmos)!: support custom document IDs and partition keys feat(cosmos): support custom document IDs and partition keys Aug 1, 2026
ReubenBond and others added 2 commits August 1, 2026 09:06
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e82f060d-d09b-42f9-8b0f-34da38b386fa
Copilot AI review requested due to automatic review settings August 1, 2026 16:25
@ReubenBond
ReubenBond force-pushed the fix/cosmos-doc-id-provider branch from 7991079 to 7d3abdd Compare August 1, 2026 16:25
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e82f060d-d09b-42f9-8b0f-34da38b386fa

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

Suppressed comments (3)

src/Azure/Orleans.Persistence.Cosmos/CosmosGrainStorage.cs:178

  • After successfully deleting state (DeleteStateOnClear=true), the code clears ETag/RecordExists but does not reset grainState.State. This differs from ResetGrainState used elsewhere in the provider and can leave stale state in the activation.
                {
                    try

src/Azure/Orleans.Persistence.Cosmos/CosmosGrainStorage.cs:202

  • When DeleteStateOnClear is false, ClearStateAsync overwrites storage with an empty document and sets RecordExists=false, but it no longer resets grainState.State to a fresh instance. This can leave the activation with the pre-clear values even though the record is cleared.
                    await _executor.ExecuteOperation(static args =>
                    {
                        var (self, id, pk, requestOptions) = args;
                        return self._container.DeleteItemAsync<GrainStateEntity<T>>(id, pk, requestOptions);

src/Azure/Orleans.Persistence.Cosmos/CosmosGrainStorage.cs:168

  • When DeleteStateOnClear is enabled and the caller has no ETag, this method returns without clearing the in-memory state (and without normalizing ETag). That can leave the activation holding stale state after a successful ClearStateAsync call. Consider resetting the grain state (without touching storage) before returning.

This issue also appears in the following locations of the same file:

  • line 177
  • line 199
    public async Task ClearStateAsync<T>(string grainType, GrainId grainId, IGrainState<T> grainState)
    {
        var (id, partitionKey) = await _documentIdProvider.GetDocumentIdentifiers(grainType, grainId);

        LogTraceClearingState(grainType, id, grainId, grainState.ETag, _options.DeleteStateOnClear, _options.ContainerName, partitionKey);
  • Files reviewed: 7/7 changed files
  • Comments generated: 0 new

Copilot AI review requested due to automatic review settings August 1, 2026 16:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

Suppressed comments (2)

src/Azure/Orleans.Persistence.Cosmos/CosmosGrainStorage.cs:333

  • The /GrainType partition-key-path guard currently rejects any custom IDocumentIdProvider, even if it only customizes the document id while still returning partitionKey == grainType. Since Cosmos uses GrainType as the partition key when the path is /GrainType, custom document ids can still be compatible as long as the partition key remains the grain type. Consider only rejecting custom partition-key providers here (and/or validating that the returned partition key equals grainType at runtime).
                if (_partitionKeyPath == GRAINTYPE_PARTITION_KEY_PATH &&
                    (_documentIdProvider is not DefaultDocumentIdProvider defaultProvider || defaultProvider.HasCustomPartitionKeyProvider))
                    throw new OrleansConfigurationException("Custom document id or partition key providers are not compatible with partition key path set to /GrainType");

src/Azure/Orleans.Persistence.Cosmos/HostingExtensions.cs:40

  • The generic AddCosmosGrainStorage/AddCosmosGrainStorageAsDefault overloads no longer provide compile-time validation that TProvider implements IDocumentIdProvider or IPartitionKeyProvider (the constraint is now just class), so invalid types compile and fail only at runtime via AddIdentifierProvider. Consider adding a new constrained generic overload for IDocumentIdProvider and keeping legacy IPartitionKeyProvider-based overloads as [Obsolete] to preserve compile-time safety for the primary API.
    public static ISiloBuilder AddCosmosGrainStorageAsDefault<TProvider>(
        this ISiloBuilder builder,
        Action<CosmosGrainStorageOptions> configureOptions) where TProvider : class
    {
        return builder.AddCosmosGrainStorage<TProvider>(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions);
    }

    /// <summary>
    /// Configure silo to use Azure Cosmos DB storage for grain storage using a custom document id provider
    /// or a legacy partition key provider.
    /// </summary>
    /// <typeparam name="TProvider">The document id or partition key provider.</typeparam>
    /// <param name="builder">The silo builder.</param>
    /// <param name="name">The storage provider name.</param>
    /// <param name="configureOptions">The delegate used to configure the provider.</param>
    public static ISiloBuilder AddCosmosGrainStorage<TProvider>(
        this ISiloBuilder builder,
        string name,
        Action<CosmosGrainStorageOptions> configureOptions) where TProvider : class
  • Files reviewed: 7/7 changed files
  • Comments generated: 1

Comment thread src/Azure/Orleans.Persistence.Cosmos/IDocumentIdProvider.cs
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e82f060d-d09b-42f9-8b0f-34da38b386fa
Copilot AI review requested due to automatic review settings August 1, 2026 21:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

Suppressed comments (2)

src/Azure/Orleans.Persistence.Cosmos/CosmosGrainStorage.cs:333

  • This check rejects any custom IDocumentIdProvider when PartitionKeyPath is "/GrainType", even though customizing only the document id can still be compatible as long as the provider returns PartitionKey == grainType (Cosmos uses the GrainType property as the partition key, independent of document id).

Either relax this restriction for custom IDocumentIdProvider implementations, or validate/enforce that the returned partition key matches grainType when PartitionKeyPath is "/GrainType" (and update the exception message accordingly).

                if (_partitionKeyPath == GRAINTYPE_PARTITION_KEY_PATH &&
                    (_documentIdProvider is not DefaultDocumentIdProvider defaultProvider || defaultProvider.HasCustomPartitionKeyProvider))
                    throw new OrleansConfigurationException("Custom document id or partition key providers are not compatible with partition key path set to /GrainType");

src/Azure/Orleans.Persistence.Cosmos/HostingExtensions.cs:26

  • The generic overloads now accept any class (no IDocumentIdProvider/IPartitionKeyProvider constraint), so mis-typed calls compile and only fail at runtime via AddIdentifierProvider’s ArgumentException. That’s a significant loss of type-safety/IDE guidance for a public API.

Consider restoring compile-time safety by splitting into two overloads (one constrained to IDocumentIdProvider, plus an [Obsolete] overload constrained to IPartitionKeyProvider for legacy), or introducing a shared marker interface. This will require regenerating the API surface file.

    public static ISiloBuilder AddCosmosGrainStorageAsDefault<TProvider>(
        this ISiloBuilder builder,
        Action<CosmosGrainStorageOptions> configureOptions) where TProvider : class
    {
        return builder.AddCosmosGrainStorage<TProvider>(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME, configureOptions);
  • Files reviewed: 8/8 changed files
  • Comments generated: 0 new

@ReubenBond
ReubenBond merged commit 39e743a into dotnet:main Aug 2, 2026
121 of 124 checks passed
@ReubenBond
ReubenBond deleted the fix/cosmos-doc-id-provider branch August 2, 2026 15:39
@ReubenBond

Copy link
Copy Markdown
Member Author

@OmnipotentOwl Addressed in #10326. Configuration now accepts DocumentIdProviderKey, which resolves an application-registered keyed IDocumentIdProvider through standard dependency injection and aliases it to the grain storage provider name. We intentionally keep the service key/type out of CosmosGrainStorageOptions, since service selection belongs to hosting configuration rather than runtime options.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants