Summary
When LINQ translation doesn't cover a query shape, users need an escape hatch to write PartiQL directly. This story adds FromPartiQL on DbSet<T> for raw SELECT queries that return entity results, and ExecutePartiQLAsync on DatabaseFacade for raw DML (INSERT/UPDATE/DELETE). This follows the pattern established by EF Core's FromSqlRaw / ExecuteSqlRawAsync in relational providers and FromSqlRaw in the Cosmos provider.
API Surface
Entity SELECT (composable)
// Raw PartiQL SELECT — returns IQueryable<T>, supports LINQ composition
var orders = await context.Orders
.FromPartiQL("SELECT * FROM Orders WHERE pk = ?", new AttributeValue { S = "user-1" })
.Where(o => o.Status == "active") // client-side composition
.ToListAsync();
FromPartiQL is defined as an extension method on DbSet<TEntity>. It returns IQueryable<TEntity> so further LINQ operators (.Where, .Select, .Skip, .Take) can be appended as client-side evaluation.
DML / non-query execution
await context.Database.ExecutePartiQLAsync(
"UPDATE Orders SET status = ? WHERE pk = ?",
[new AttributeValue { S = "closed" }, new AttributeValue { S = "order-1" }]);
Signatures
// Extensions/DynamoDbSetExtensions.cs (or DynamoQueryableExtensions.cs)
public static IQueryable<TEntity> FromPartiQL<TEntity>(
this DbSet<TEntity> source,
[NotParameterized] string partiql,
params AttributeValue[] parameters)
where TEntity : class;
// Extensions/DynamoDatabaseFacadeExtensions.cs
public static Task ExecutePartiQLAsync(
this DatabaseFacade database,
string partiql,
IEnumerable<AttributeValue> parameters,
CancellationToken cancellationToken = default);
Parameters are AttributeValue (AWS SDK type) rather than object? — DynamoDB has no generic CLR-to-AttributeValue inference, so requiring the provider-native type is more honest and avoids a lossy conversion layer.
Implementation sketch
FromPartiQL (SELECT path)
Follows the same pipeline as the Cosmos FromSqlRaw implementation:
- New expression node —
FromPartiQLQueryRootExpression : EntityQueryRootExpression carrying the raw PartiQL string and AttributeValue[] parameters
DynamoQueryRootProcessor — detect the new root, pass through to translator
DynamoQueryableMethodTranslatingExpressionVisitor — handle FromPartiQLQueryRootExpression, build a SelectExpression that wraps a raw PartiQL source node
DynamoQuerySqlGenerator — emit the literal PartiQL string unchanged; bind parameters into the ExecuteStatementRequest
- Execution — routes through the existing
DynamoClientWrapper.ExecutePartiQl() path
The existing ExecutePartiQl() already accepts an ExecuteStatementRequest with a Parameters list of AttributeValue — the translation pipeline just needs to thread the user-supplied parameters through correctly.
ExecutePartiQLAsync (DML path)
Thin wrapper over the existing DynamoClientWrapper.ExecuteWriteAsync(string statement, List<AttributeValue> parameters, ...). Just expose it through DatabaseFacade so users don't need to resolve the internal service.
Design constraints
- Async only — no synchronous
FromPartiQL or ExecutePartiQL overloads; the provider is async-only
- No server-side composition — LINQ operators appended after
FromPartiQL are client-evaluated; document this clearly. Unlike relational providers there is no way to wrap raw PartiQL in an outer query
- Parameters are positional — PartiQL uses
? placeholders; parameters bind left-to-right in the supplied array
- No interpolated string overload —
AttributeValue parameters can't be naturally embedded in a FormattableString; raw string + explicit parameter list is the only form
What is NOT in scope
Acceptance criteria
Summary
When LINQ translation doesn't cover a query shape, users need an escape hatch to write PartiQL directly. This story adds
FromPartiQLonDbSet<T>for raw SELECT queries that return entity results, andExecutePartiQLAsynconDatabaseFacadefor raw DML (INSERT/UPDATE/DELETE). This follows the pattern established by EF Core'sFromSqlRaw/ExecuteSqlRawAsyncin relational providers andFromSqlRawin the Cosmos provider.API Surface
Entity SELECT (composable)
FromPartiQLis defined as an extension method onDbSet<TEntity>. It returnsIQueryable<TEntity>so further LINQ operators (.Where,.Select,.Skip,.Take) can be appended as client-side evaluation.DML / non-query execution
Signatures
Parameters are
AttributeValue(AWS SDK type) rather thanobject?— DynamoDB has no generic CLR-to-AttributeValueinference, so requiring the provider-native type is more honest and avoids a lossy conversion layer.Implementation sketch
FromPartiQL(SELECT path)Follows the same pipeline as the Cosmos
FromSqlRawimplementation:FromPartiQLQueryRootExpression : EntityQueryRootExpressioncarrying the raw PartiQL string andAttributeValue[]parametersDynamoQueryRootProcessor— detect the new root, pass through to translatorDynamoQueryableMethodTranslatingExpressionVisitor— handleFromPartiQLQueryRootExpression, build aSelectExpressionthat wraps a raw PartiQL source nodeDynamoQuerySqlGenerator— emit the literal PartiQL string unchanged; bind parameters into theExecuteStatementRequestDynamoClientWrapper.ExecutePartiQl()pathThe existing
ExecutePartiQl()already accepts anExecuteStatementRequestwith aParameterslist ofAttributeValue— the translation pipeline just needs to thread the user-supplied parameters through correctly.ExecutePartiQLAsync(DML path)Thin wrapper over the existing
DynamoClientWrapper.ExecuteWriteAsync(string statement, List<AttributeValue> parameters, ...). Just expose it throughDatabaseFacadeso users don't need to resolve the internal service.Design constraints
FromPartiQLorExecutePartiQLoverloads; the provider is async-onlyFromPartiQLare client-evaluated; document this clearly. Unlike relational providers there is no way to wrap raw PartiQL in an outer query?placeholders; parameters bind left-to-right in the supplied arrayAttributeValueparameters can't be naturally embedded in aFormattableString; raw string + explicit parameter list is the only formWhat is NOT in scope
FromPartiQLfor non-entity result shapes (e.g. scalar projections) — follow-up storyGetItem,Query,Scanaccess — separate exploratory story (feat: FromPartiQL escape hatch for raw PartiQL SELECT queries #176)Acceptance criteria
FromPartiQLreturns entities for a valid PartiQL SELECT with positional parametersFromPartiQLevaluate client-side (not sent to DynamoDB as additional predicates)ExecutePartiQLAsyncexecutes DML statements and does not throw for valid PartiQLDynamoException(not a null ref or silent failure)