HangfireIO/Hangfire · error · ArgumentException

Expression body should be of type `MethodCallExpression`

Error message

Expression body should be of type `MethodCallExpression`

What it means

ArgumentException thrown by Job.FromExpression when the supplied lambda's Body is not a MethodCallExpression. Hangfire builds a Job by extracting the target method, type, and arguments from a method-call expression tree; other body types (e.g., a property access MemberExpression, a bare constant, an assignment, or a lambda invoking a delegate) do not represent an invokable method and cannot be serialized. This fires synchronously at enqueue time before any storage interaction.

Source

Thrown at src/Hangfire.Core/Common/Job.cs:427

        /// </remarks>
        public static Job FromExpression<TType>([NotNull, InstantHandle] Expression<Func<TType, Task>> methodCall)
        {
            return FromExpression(methodCall, null);
        }

        public static Job FromExpression<TType>([NotNull, InstantHandle] Expression<Func<TType, Task>> methodCall, [CanBeNull] string queue)
        {
            return FromExpression(methodCall, typeof(TType), queue);
        }

        private static Job FromExpression([NotNull] LambdaExpression methodCall, [CanBeNull] Type explicitType, [CanBeNull] string queue)
        {
            if (methodCall == null) throw new ArgumentNullException(nameof(methodCall));

            var callExpression = methodCall.Body as MethodCallExpression;
            if (callExpression == null)
            {
                throw new ArgumentException("Expression body should be of type `MethodCallExpression`", nameof(methodCall));
            }

            var type = explicitType ?? callExpression.Method.DeclaringType;
            var method = callExpression.Method;

            if (explicitType == null && callExpression.Object != null)
            {
                // Creating a job that is based on a scope variable. We should infer its
                // type and method based on its value, and not from the expression tree.

                // TODO: BREAKING: Consider removing this special case entirely.
                // People consider that the whole object is serialized, this is not true.

                var objectValue = GetExpressionValue(callExpression.Object);
                if (objectValue == null)
                {
                    throw new InvalidOperationException("Expression object should be not null.");
                }

View on GitHub (pinned to c236dd0f93)

Solutions

  1. Ensure the lambda body is a direct method call, e.g. () => service.DoWork(arg).
  2. If targeting an instance method, use the typed overload Enqueue<T>(x => x.DoWork()).
  3. When building expressions dynamically, construct a MethodCallExpression via Expression.Call before wrapping in a lambda.

Example fix

// before — body is not a method call
BackgroundJob.Enqueue(() => _counter);
BackgroundJob.Enqueue(() => func.Invoke());

// after
BackgroundJob.Enqueue(() => service.ReadCounter());
BackgroundJob.Enqueue<MyService>(x => x.DoWork());
Defensive patterns

Strategy: type-guard

Validate before calling

if (methodCall.Body is not MethodCallExpression)
    throw new ArgumentException("Lambda body must be a method call, e.g. () => service.Method(args).", nameof(methodCall));

Type guard

public static bool IsMethodCallExpression<T>(Expression<T> expr)
    => expr?.Body is MethodCallExpression;

Try / catch

try { var job = Job.FromExpression(() => svc.Work()); }
catch (ArgumentException ex) when (ex.Message.Contains("MethodCallExpression")) { /* rewrite the lambda to a real method call */ }

Prevention

When it happens

Trigger: Passing () => someProperty, () => 42, () => field, or () => func.Invoke() to BackgroundJob.Enqueue/FromExpression; wrapping the method call in an extra lambda; using a ternary or null-coalescing expression as the body; passing an expression that constructs an object rather than calling a method.

Common situations: Refactoring a method call into a property accessor or field read; mistakenly enqueuing a delegate invocation; building expressions dynamically with Expression.Lambda where the body is not a MethodCall; copy-paste errors where the lambda body is a value not a call.

Related errors


AI-assisted analysis of HangfireIO/Hangfire@c236dd0f93 (2026-08-13). Data as JSON: /api/errors/0c36b86d346a7433. Report an issue: GitHub.