{"slug": "the-scoped-singleton-di-bug-your-ai-just-suggested", "title": "The Scoped Singleton DI Bug Your AI Just Suggested", "summary": "Common software bug where a Scoped service (like `OrderService`) caches an entity from a Scoped `DbContext` into a Singleton service (like `IMemoryCache`). This causes data corruption and crashes in production because the cached entity holds a reference to a `DbContext` that has been disposed after the initial request ends. The author explains that AI assistants frequently suggest this flawed pattern because their training data often omits dependency injection lifetime registrations, and provides rules to prevent the bug, such as ensuring cached data is detached from its original context.", "body_md": "The Scoped→Singleton DI bug your AI just suggested (and how to catch it)\nOf all the bugs that ship to production silently, the captured-dependency lifetime bug is one of the most expensive. It compiles. It passes your tests. It runs fine in dev. Then in production, under load, it starts corrupting data across requests. And AI assistants suggest it constantly. Here's why — and the one Cursor rule that catches it before merge.\nThe bug, in 30 lines\n``\nYou ask Cursor to add caching to OrderService. It gives you this:\n`plaintext\n`\n// OrderService.cs\npublic class OrderService : IOrderService\n{\nprivate readonly IMemoryCache _cache;\nprivate readonly OrderDbContext _db;\npublic OrderService(IMemoryCache cache, OrderDbContext db)\n{\n_cache = cache;\n_db = db;\n}\npublic async Task<Order?> GetAsync(int id, CancellationToken ct)\n{\nif (_cache.TryGetValue(id, out Order? cached)) return cached;\nvar order = await _db.Orders.FindAsync(new object[] { id }, ct);\nif (order is not null) _cache.Set(id, order, TimeSpan.FromMinutes(5));\nreturn order;\n}\n}\n// Program.cs\nbuilder.Services.AddDbContext(...);\nbuilder.Services.AddScoped();\nbuilder.Services.AddMemoryCache(); // ← registers IMemoryCache as Singleton\nLooks correct. Compiles. Tests pass. Shipped.\nWhat actually happens at runtime\n****\n****\nIMemoryCache is registered as Singleton — one instance for the entire app's lifetime. OrderService is registered as Scoped — one instance per HTTP request.\nplaintext\nOn its own, that's fine. The problem is what you cached: an Order entity, which is in turn attached to OrderDbContext — also Scoped. The cache, alive for the lifetime of the application, now holds a reference to an entity attached to a DbContext that was disposed when the original request ended.\n``\nNow request #2 comes in. It hits the cache, gets the order, mutates a property. Then request #3 hits the cache, sees the mutation, and decides to write something else based on it. Then request #4 wakes up the entity's dispose-tracking and explodes with ObjectDisposedException — but only sometimes, depending on the GC pressure that day.\nWelcome to the longest debugging session of your year.\nWhy AI assistants suggest this constantly\nThe patterns the AI has seen most often in its training data — short examples, blog tutorials, StackOverflow answers — almost always omit DI registration. A typical \"caching with IMemoryCache\" snippet looks like ten lines, with no reference to where the service is registered or with what lifetime.\nThe AI learned the surface pattern (\"inject IMemoryCache, call .Set\") without the surrounding constraint (\"…unless the consumer is Scoped and the cached value graph reaches into Scoped infrastructure\"). When you ask it to add caching to your codebase, it pattern-matches against the surface form. The constraint is invisible to it.\nThis isn't a \"the AI is dumb\" critique. Most senior developers ship this exact bug at least once. The patterns in the wild teach the wrong lesson.\nThe five lifetime traps to teach the AI\nIf you're going to enforce one set of rules on AI-suggested .NET code, make it these:\n``\nThe classic. A Singleton constructor takes IRepository (Scoped). The Singleton captures it forever. Requests share state. Data corrupts.\nThe rule: when adding a constructor parameter, check the parameter type's registered lifetime. If the consumer is Singleton and the parameter is Scoped/Transient, refuse and surface the issue.\n****\nSpecial case of #1 but worth its own callout. DbContext is always Scoped — it has to be, it tracks per-request state. Any Singleton that captures a DbContext is a bug. If you need DB access from a Singleton, inject IServiceScopeFactory and create a scope per operation.\nThe bug from the example. The cache outlives the DbContext, but holds a graph that depends on it.\n****``\nThe rule: what goes into long-lived caches must be either (a) AsNoTracking()'d, (b) projected to a DTO, or (c) detached explicitly.\n4. HttpClient instantiated with new\nA long-running app that does new HttpClient() on every call leaks sockets — eventually exhausting the connection pool. Even worse: a Singleton that captures a single HttpClient reuses DNS forever.\nThe rule: always inject IHttpClientFactory and call CreateClient(name). Never new HttpClient() outside of one-shot scripts.\n###\n5. Hosted services touching Scoped dependencies directly\n``\nIHostedService is Singleton-by-construction. Inject a Scoped repo into one and it'll be alive for the lifetime of the process — every \"scoped\" operation will share state. Worse, the DbContext will leak.\n****``````\nThe rule: in any BackgroundService or IHostedService, never inject Scoped dependencies directly. Inject IServiceScopeFactory and create a scope per unit of work.\n##\nThe Cursor rule that catches all five\n``[](https://agenticstandardcontact-byte.github.io/agentic-architect/)``\n````\nThe dotnet-di.mdc rule in Agentic Architect codifies the above. When Cursor is editing a file where DI is happening — Program.cs, Startup.cs, ServiceCollectionExtensions.cs, any class constructor — the rule activates and audits suggestions for:\n- Lifetime mismatches between consumer and constructor parameters\n- Captured Scoped dependencies inside hosted services or background workers\n- ``\nDirect HttpClient instantiation\n- Captured tracked entities in long-lived caches\n- Static helpers reaching into scoped infrastructure\n**\nThe trick is the scoping: it loads only on files where DI is actually happening — not on every prompt. Your token budget stays sane. The AI stays sharp on the file you're actually in.\n##\nThe bigger pattern: enforce, don't suggest\n********\nThe reframe that took me a year of using AI assistants to internalize is this: generic prompts ask the AI to suggest good patterns. Scoped rules force it to enforce them.\n\"Be careful with DI lifetimes\" is a suggestion. The AI will agree, nod sagely, then ship the captured-Scoped bug an hour later when you're tired.\n\"Before suggesting any constructor change, audit the lifetime contract\" is a rule. The AI now has a checklist. It pauses, runs the check, and either suggests a boundary-respecting alternative or asks you a targeted question — instead of confidently shipping the bug.\n**\nThe first time the AI catches a Scoped-into-Singleton in code you wrote, the kit pays for itself.\n---\n*Originally published at [https://agenticstandardcontact-byte.github.io/agentic-architect/blog/02-scoped-singleton-di-bug.html](https://agenticstandardcontact-byte.github.io/agentic-architect/blog/02-scoped-singleton-di-bug.html). Part of the [Agentic Architect](https://agenticstandardcontact-byte.github.io/agentic-architect/) persistence kit for Cursor + .NET.*", "url": "https://wpnews.pro/news/the-scoped-singleton-di-bug-your-ai-just-suggested", "canonical_source": "https://dev.to/agentic_standard/the-scoped-singleton-di-bug-your-ai-just-suggested-jo0", "published_at": "2026-05-21 22:23:27+00:00", "updated_at": "2026-05-21 22:32:04.076628+00:00", "lang": "en", "topics": ["developer-tools", "large-language-models", "artificial-intelligence"], "entities": ["Cursor", "OrderService", "IMemoryCache", "OrderDbContext"], "alternates": {"html": "https://wpnews.pro/news/the-scoped-singleton-di-bug-your-ai-just-suggested", "markdown": "https://wpnews.pro/news/the-scoped-singleton-di-bug-your-ai-just-suggested.md", "text": "https://wpnews.pro/news/the-scoped-singleton-di-bug-your-ai-just-suggested.txt", "jsonld": "https://wpnews.pro/news/the-scoped-singleton-di-bug-your-ai-just-suggested.jsonld"}}