dotnet/efcore · error · InvalidOperationException
'WithPartitionKey' only accepts simple constant or…
Error message
'WithPartitionKey' only accepts simple constant or parameter arguments. See https://aka.ms/efdocs-cosmos-partition-keys for more information.
What it means
After translating each WithPartitionKey argument with applyDefaultTypeMapping:false, the visitor requires the result to be a SqlConstantExpression or SqlParameterExpression. Any other shape (a column reference, a computed expression, a client value that cannot be mapped) triggers InvalidOperationException.
Solutions
- Pass a simple constant or a parameter captured in a local variable (string, int, etc.) to WithPartitionKey.
- Resolve any computed partition key to a concrete value before calling WithPartitionKey.
- Ensure the partition key CLR type has a Cosmos type mapping registered.
Example fix
// before var q = db.Users.WithPartitionKey(otherUser.TenantId); // after var tenantId = otherUser.TenantId; var q = db.Users.WithPartitionKey(tenantId);
Defensive patterns
Strategy: validation
Validate before calling
static IQueryable<T> SafePartition<T>(IQueryable<T> q, object key)
where T : class
{
if (key is null) throw new ArgumentNullException(nameof(key));
if (key is string s && s.Length == 0) throw new ArgumentException("empty partition key", nameof(key));
if (!key.GetType().IsPrimitive && key is not string and not Guid and not DateTime and not DateTimeOffset)
throw new ArgumentException("partition key must be a simple constant or parameter", nameof(key));
return q.WithPartitionKey(key);
} Type guard
static bool IsSimplePartitionValue(object v)
=> v is string or Guid or DateTime or DateTimeOffset || v?.GetType().IsPrimitive == true; Prevention
- Resolve partition keys to concrete locals before calling WithPartitionKey.
- Avoid passing property accessors or method results as partition key arguments.
- Add an analyzer rule that flags non-constant/parameter arguments to WithPartitionKey.
When it happens
Trigger: Passing a non-constant/non-parameter value to WithPartitionKey — e.g. a property of another entity, the result of a method call, or an expression that requires translation rather than binding.
Common situations: Computing the partition key at runtime from another query result and passing it in, or capturing a non-simple value (struct without type mapping, dynamic) as the partition key.
Related errors
- A FromSqlExpression has an invalid arguments expression type
- 'WithPartitionKey' can only be called on an entity query…
- 'WithPartitionKey' can only be called once in a query. See…
- A partition key is defined on entity type
- Bind property on structural type coming out of scalar…
AI-assisted analysis of dotnet/efcore@3a2006ef56 (2026-08-11).
Data as JSON: /api/errors/464a038635598622.
Report an issue: GitHub.
Appendix: source
Thrown at src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs:185
{
if (_queryCompilationContext.PartitionKeyPropertyValues.Count > 0)
{
throw new InvalidOperationException(CosmosStrings.WithPartitionKeyAlreadyCalled);
}
if (methodCallExpression.Arguments[0] is not EntityQueryRootExpression)
{
throw new InvalidOperationException(CosmosStrings.WithPartitionKeyBadNode);
}
var innerQueryable = Visit(methodCallExpression.Arguments[0]);
for (var i = 1; i < methodCallExpression.Arguments.Count; i++)
{
var value = _sqlTranslator.Translate(methodCallExpression.Arguments[i], applyDefaultTypeMapping: false);
if (value is not SqlConstantExpression and not SqlParameterExpression)
{
throw new InvalidOperationException(CosmosStrings.WithPartitionKeyNotConstantOrParameter);
}
_queryCompilationContext.PartitionKeyPropertyValues.Add(value);
}
return innerQueryable;
}
if (method.DeclaringType == typeof(Queryable) && method.IsGenericMethod)
{
switch (methodCallExpression.Method.Name)
{
// The following is a bad hack to account for https://github.com/dotnet/efcore/issues/32957#issuecomment-2165864086.
// Basically for the query form Where(b => b.Posts.GetElementAt(0).Id == 1), nav expansion moves the property access
// forward, generating Where(b => b.Posts.Select(p => p.Id).GetElementAt(0)); unfortunately that means that GetElementAt()
// over a bare array in Cosmos doesn't get translated to a simple indexer as it should (b["Posts"][0].Id), since the
// reordering messes things up.
case nameof(Queryable.ElementAt) or nameof(Queryable.ElementAtOrDefault)View on GitHub (pinned to 3a2006ef56)