dotnet/orleans · error · ArgumentException

Journal metadata property '{propertyName}' cannot be both se

Error message

Journal metadata property '{propertyName}' cannot be both set and removed.

What it means

A metadata update passed the same property name in both the set dictionary and the remove set. CopyRemove (AzureTableJournalStorage.cs:1115) validates each removed key against the set dictionary and rejects the contradiction because the net effect would be ambiguous. The argument blamed is `remove`.

Source

Thrown at src/Azure/Orleans.Journaling.AzureStorage/AzureTableJournalStorage.cs:1128

        }

        return result;
    }

    private static IReadOnlySet<string> CopyRemove(IEnumerable<string>? remove, IReadOnlyDictionary<string, string> set)
    {
        if (remove is null)
        {
            return new HashSet<string>(StringComparer.Ordinal);
        }

        var result = new HashSet<string>(StringComparer.Ordinal);
        foreach (var propertyName in remove)
        {
            ValidateCallerMetadataPropertyName(propertyName);
            if (set.ContainsKey(propertyName))
            {
                throw new ArgumentException($"Journal metadata property '{propertyName}' cannot be both set and removed.", nameof(remove));
            }

            result.Add(propertyName);
        }

        return result;
    }

    private static bool ApplyCallerMetadataUpdate(
        Dictionary<string, string> metadata,
        IReadOnlyDictionary<string, string> set,
        IReadOnlySet<string> remove)
    {
        var changed = false;
        foreach (var propertyName in remove)
        {
            changed |= metadata.Remove(propertyName);
        }

View on GitHub (pinned to fca799fa70)

Solutions

  1. Disjoint the keys: remove a name from set before passing it in remove, or vice versa.
  2. Decide per-key intent (set wins vs remove wins) and filter the other collection before the call.
  3. Add a precondition check in calling code: `set.Keys.Intersect(remove).Any()` should be false.

Example fix

// before
storage.UpdateMetadata(set: new() { ["k"] = "v" }, remove: new[] { "k" });

// after
var set = new Dictionary<string, string> { ["k"] = "v" };
var remove = new[] { "other" };
storage.UpdateMetadata(set: set, remove: remove);
Defensive patterns

Strategy: validation

Validate before calling

var overlap = set.Keys.Intersect(remove, StringComparer.Ordinal).ToList();
if (overlap.Count > 0) throw new ArgumentException($"Keys both set and removed: {string.Join(", ", overlap)}");

Type guard

static bool IsMetadataUpdateDisjoint(
    IReadOnlyDictionary<string,string> set, IEnumerable<string> remove)
    => !remove.Any(r => set.ContainsKey(r));

Prevention

When it happens

Trigger: Calling a journal metadata-update API (e.g., the method that calls CopyRemove) with overlapping keys in its set and remove parameters; thrown at AzureTableJournalStorage.cs:1128.

Common situations: Caller builds set and remove from the same source list; a UI applies a diff that marks a key as both changed and deleted; copying a key collection into both arguments by mistake.

Related errors


AI-assisted analysis of dotnet/orleans@fca799fa70 (2026-08-13). Data as JSON: /api/errors/cbbfcc2ed1263e56. Report an issue: GitHub.