DI022

Service Instance Reused Across Handler Invocations

The same capture shape as DI021, but on a sink whose concurrency is controlled by a configuration knob that cannot be proven at compile time — canonically `ServiceBusProcessor` where `MaxConcurrentCalls` comes from configuration or is left at its default.

Default severity: Info · Code fix: Yes

Why it matters

If the knob is ever raised above 1 this becomes the DI021 concurrency crash. Even with sequential dispatch, one instance accumulates state across all messages: an EF Core change tracker grows without bound, and a failed `SaveChanges` poisons every subsequent message. DI022 reports at **Info** severity because the concurrency claim is conditional; teams that want it louder can raise it with one line:

README problem example

README better pattern

Yes. Same scope-per-invocation rewrite as DI021.

Repo sample extraction

Examples pulled from the sample app

Open full sample file

Sample app config-gated capture info

    public class Bad_ProcessorCaptureWithUnprovenConcurrency
    {
        private readonly DbConnection _connection;

        public Bad_ProcessorCaptureWithUnprovenConcurrency(DbConnection connection)
        {
            _connection = connection;
        }

        public void Start(ServiceBusProcessor processor)
        {
            processor.ProcessMessageAsync += args =>
            {
                // [DI022] '_connection' is captured once and reused across all invocations
                var command = _connection.CreateCommand();
                command.CommandText = "SELECT 1";
                command.ExecuteNonQuery();
                return Task.CompletedTask;
            };
        }
    }

Sample app per-message pattern

    public class Good_ConnectionPerMessage
    {
        private readonly Func<DbConnection> _connectionFactory;

        public Good_ConnectionPerMessage(Func<DbConnection> connectionFactory)
        {
            _connectionFactory = connectionFactory;
        }

        public void Start(ServiceBusProcessor processor)
        {
            processor.ProcessMessageAsync += args =>
            {
                // Create the non-thread-safe resource per message instead of sharing one instance.
                using var connection = _connectionFactory();
                using var command = connection.CreateCommand();
                command.CommandText = "SELECT 1";
                command.ExecuteNonQuery();
                return Task.CompletedTask;
            };
        }
    }

Related guides

  • No problem-guide pages point here yet.