A captive dependency occurs when a longer-lived service holds a shorter-lived dependency. The dependency becomes trapped in the longer-lived service and survives longer than intended. section 9.4.4
A singleton captures a scoped service
Registering DataContext as scoped and Repository as singleton creates a captive dependency. Repository lives for the application lifetime, so it keeps the DataContext created for its first scope instead of receiving a new instance for each request. This can produce subtle, difficult-to-find bugs. section 9.4.4
Match dependency lifetimes using this rule:
- A singleton may depend only on singleton services. section 9.4.4
- A scoped service may depend on scoped or singleton services. section 9.4.4
- A transient service may depend on services with any lifetime. section 9.4.4
Figure 9.10 DataContext is registered as a scoped dependency, but Repository is a singleton. Even though you expect a new DataContext for every request, Repository captures the injected DataContext and causes it to be reused for the lifetime of the app.
Trace how a scoped DataContext is captured by a singleton Repository and reused across application requests. section 9.4.4
ASP.NET Core can validate scopes and throw an exception when it creates a service with a captive dependency. Scope validation is enabled by default in development; ValidateScopes and ValidateOnBuild can be enabled explicitly for broader checking. section 9.4.4
The same restriction applies when OptionsBuilder configuration retrieves services from dependency injection: Configure<TDeps> should use singleton services, not scoped services such as DbContext. section 31.2
