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

  1. Ensure the lambda is a pure property/field chain rooted at the target (target => target.Sub.FinalField).
  2. For nested members, only property/field traversal is supported — flatten the target or expose an intermediate property.
  3. 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

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.