DI019

Scoped Service Resolved From Root Provider

scoped services, known scoped framework services such as `IOptionsSnapshot<T>`, EF Core contexts from `AddDbContext(...)`, `AddDbContextFactory(...)`, `AddDbContextPool(...)`, and `AddPooledDbContextFactory(...)` including service/implementation overload self-registrations, or services whose activation graph reaches a scoped service, resolved from a root `IServiceProvider` such as ASP.NET Core `app.Services`, ASP.NET test-host `factory.Services` / `server.Services`, Generic Host `host.Services`, nullable root-provider surfaces such as `app.Services!`, or a provider returned by `BuildServiceProvider()`. Root-provider aliases also stay classified through `?? throw` guards and conditional expressions whose two result arms are proven root through path-stable declarations or straight-line writes. Provider declarations and assignments are collected in source order, path stability propagates through copied aliases, later unclassified, `??=`, deconstruction, and `ref`/`out` writes invalidate older provider facts. Write facts become visible only after right-hand-side, initializer, or argument evaluation, and nested mutation events are processed before their enclosing write, so resolutions and alias copies observe the provider state at that runtime point. Merely binding or retargeting a ref local preserves the referents' facts; source-positioned mappings ensure later writes follow every possible storage active at that point across conditional or unconditional retargeting and ref-conditional local, by-reference argument, or lvalue targets, while reads use only the mapping active at their position and classify the alias only when every possible storage agrees. Writes through aliases with multiple possible referents invalidate every candidate storage rather than claiming each one definitely received the new value. Forward or backward `goto` edges cannot make path-dependent facts stable. Field/property facts never qualify because source position cannot prove cross-method execution; deferred lambda, LINQ-query, and local-function hazards remain conservative for captured outer storage, while locals and parameters owned by the deferred boundary retain ordinary path stability for declarations and straight-line writes. Control flow outside that owning boundary does not alter the path executed inside it. Other control-flow-dependent, mixed root/scoped, and unknown arms stay conservative. Both ordinary extension syntax and direct static calls through the exact framework `ServiceProviderServiceExtensions` and `ServiceProviderKeyedServiceExtensions` types are analyzed, including reordered named arguments; same-named user extensions stay silent.

Default severity: Warning · Code fix: Yes

Why it matters

the default container's scope validation is designed to prevent scoped services from being resolved directly or indirectly from the root provider. Resolving them from root can fail at runtime or accidentally stretch scoped state to application lifetime.

A classroom pass only works for one lesson. Taking it home for the whole year breaks the rules.

README problem example

var app = builder.Build();
var db = app.Services.GetRequiredService<MyDbContext>();

README better pattern

var app = builder.Build();
using var scope = app.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<MyDbContext>();

Yes. Offers to wrap ordinary extension-form resolutions in a `using` declaration or block with a new scope. Direct static-call syntax reports without a code fix because rewriting the declaring type as a provider receiver would not compile.

Repo sample extraction

Examples pulled from the sample app

Open full sample file

Sample app root-provider warning

    public static void BuildRootScopedResolutionExample()
    {
        var services = new ServiceCollection();
        services.AddScoped<IScopedService, ScopedService>();
        services.AddTransient<BadRootResolvedRunner>();

        using var provider = services.BuildServiceProvider();

        // DI019: Resolves a scoped service directly from the root provider.
        provider.GetRequiredService<IScopedService>();

        // DI019: Resolves a transient graph that reaches a scoped service from the root provider.
        provider.GetRequiredService<BadRootResolvedRunner>();
    }

Sample app scoped-provider pattern

    public static void GoodScopedResolutionExample()
    {
        var services = new ServiceCollection();
        services.AddScoped<IScopedService, ScopedService>();

        using var provider = services.BuildServiceProvider();
        using var scope = provider.CreateScope();

        var service = scope.ServiceProvider.GetRequiredService<IScopedService>();
        service.DoWork();
    }

Related guides

  • No problem-guide pages point here yet.