DapperLib/Dapper · error · InvalidOperationException
Expression must be a property/field chain off of a(n) {typeo
Error message
Expression must be a property/field chain off of a(n) {typeof(T).Name} instance What it means
DynamicParameters.Output<T> throws InvalidOperationException("Expression must be a property/field chain off of a(n) {T} instance") when the supplied lambda body is not a chain of MemberExpressions (property/field access) rooted at a ParameterExpression of type T. The method walks the expression to derive parameter names from the member chain (e.g. Post.Author.Name → @PostAuthorName) and to wire output values back; an invalid expression cannot be deconstructed. A single-level Convert-to-object unwrap is tolerated, but anything else (method calls, indexing, constants) is rejected.
Source
Thrown at Dapper/DynamicParameters.cs:335
return default!;
}
return (T)val!;
}
/// <summary>
/// Allows you to automatically populate a target property/field from output parameters. It actually
/// creates an InputOutput parameter, so you can still pass data in.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="target">The object whose property/field you wish to populate.</param>
/// <param name="expression">A MemberExpression targeting a property/field of the target (or descendant thereof.)</param>
/// <param name="dbType"></param>
/// <param name="size">The size to set on the parameter. Defaults to 0, or DbString.DefaultLength in case of strings.</param>
/// <returns>The DynamicParameters instance</returns>
public DynamicParameters Output<T>(T target, Expression<Func<T, object?>> expression, DbType? dbType = null, int? size = null)
{
static void ThrowInvalidChain()
=> throw new InvalidOperationException($"Expression must be a property/field chain off of a(n) {typeof(T).Name} instance");
// Is it even a MemberExpression?
#pragma warning disable IDE0019 // Use pattern matching - already complex enough
var lastMemberAccess = expression.Body as MemberExpression;
#pragma warning restore IDE0019 // Use pattern matching
if (lastMemberAccess is null
|| (!(lastMemberAccess.Member is PropertyInfo)
&& !(lastMemberAccess.Member is FieldInfo)))
{
if (expression.Body.NodeType == ExpressionType.Convert
&& expression.Body.Type == typeof(object)
&& ((UnaryExpression)expression.Body).Operand is MemberExpression member)
{
// It's got to be unboxed
lastMemberAccess = member;
}
else
View on GitHub (pinned to 72a54c475f)
Solutions
- Ensure the lambda is a pure property/field chain rooted at the target (target => target.Sub.FinalField).
- For nested members, only property/field traversal is supported — flatten the target or expose an intermediate property.
- Avoid method calls, indexers, arithmetic, or captured variables inside the Output expression.
Example fix
// before p.Output(obj, x => x.Items[0].Id); // after p.Output(obj, x => x.PrimaryItemId);
Defensive patterns
Strategy: validation
Validate before calling
// ensure the lambda is a pure member chain: target.Sub.Field p.Output(target, x => x.Sub.Field);
Type guard
static bool IsMemberChain<T>(Expression<Func<T, object?>> e)
{
Expression b = e.Body;
if (b is UnaryExpression u && b.NodeType == ExpressionType.Convert) b = u.Operand;
while (b is MemberExpression) b = ((MemberExpression)b).Expression!;
return b is ParameterExpression;
} Try / catch
try { p.Output(target, expr); }
catch (InvalidOperationException ex) when (ex.Message.Contains("property/field chain"))
{ /* rewrite the expression as a pure member chain */ } Prevention
- Only pass property/field access chains to Output; no methods, indexers, or captured vars.
- Unit-test Output expressions against the helper guard above.
When it happens
Trigger: Passing a lambda that calls a method or indexes a collection (x => x.Items[0].Id); passing an expression that does not start from the target T (e.g. capturing an external variable); passing a constant or a computed expression rather than a pure member chain.
Common situations: Trying to output-bind into a nested collection element; refactoring a property into a method and forgetting to update the Output call; copy-pasting an expression that referenced a different root type.
Related errors
AI-assisted analysis of DapperLib/Dapper@72a54c475f (2026-08-13).
Data as JSON: /api/errors/4d823e408cb309d9.
Report an issue: GitHub.