Expand description
DI integration — AddDbContext on rust-dix 0.10.
Registers DbContext as Scoped and exposes the rust-ef-specific
registration/erasure helpers. rust-ef keeps only this module as its DI
integration surface: lifetimes, scope caching, owned resolution, disposal and
MEDI registration semantics are provided by rust-dix.
§Resolution models
- Owned (
get_owned/#[inject(owned)]): caller-ownedDbContextwith&mut selfaccess; allowed from the root provider and never cached. - Shared (
scope.get()):Arc<DbContext>with&selfaccess, one instance per scope.
use rust_dix::*;
use rust_ef::di::*;
let provider = ServiceCollection::new()
.add_dbcontext(|options| {
options.use_sqlite("data source=app.db");
})
.build()
.unwrap();
// One scope per request/job (`rust-webx` creates it automatically per request).
let scope = provider.scope();
let mut ctx: DbContext = scope.get_owned().expect("DbContext");
ctx.add::<Blog>(blog);
ctx.save_changes().await?;§Registration variants
| API | Lifetime of the provider initialization | Use when |
|---|---|---|
DbContextServiceCollectionExt::add_dbcontext | eager, at registration (panics on failure) | simplest startup path; fail fast |
DbContextServiceCollectionExt::add_dbcontext_async | during build_async().await (typed error) | async startup; provider failures become RdiError |
DbContextServiceCollectionExt::add_dbcontext_keyed | eager, at registration | static read/write or module keys |
DbContextServiceCollectionExt::add_dbcontext_keyed_async | during build_async().await | async startup with keyed contexts |
DbContextServiceCollectionExt::add_dbcontext_anykey | lazily, per requested key (cached) | dynamic tenant/sharded contexts |
§Typed context keys
DbContextKey generates IDbContextKey
(the stable string identity used by #[context("...")]) and rust-dix 0.10’s
official extension contract From<Key> for ServiceKey:
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DbContextKey)]
enum Db { Read, Write }
let provider = ServiceCollection::new()
.add_dbcontext_keyed(Db::Read, |o| { o.use_default_entity_model(); o.use_sqlite("read.db"); })
.add_dbcontext_keyed(Db::Write, |o| { o.use_default_entity_model(); o.use_sqlite("write.db"); })
.build()?;
let mut ctx = provider.get_owned_context(Db::Write)?;
// rust-dix 0.10 macro key expressions use the generated From impl:
#[inject(scoped)]
struct WriteHandler {
#[inject(owned, key = Db::Write)]
ctx: DbContext,
}Typed keys are consistent across registration, DbContextResolver and
handler injection. &str / String keys remain source-compatible and are
registered as String keys; typed enum keys and string keys are not
interchangeable. The entity model still matches IDbContextKey::name().
§Dynamic keys (AnyKey fallback)
DbContextServiceCollectionExt::add_dbcontext_anykey registers one
fallback for every key without an exact registration. The configure closure
receives the requested ServiceKey and builds per-key options; options and
the connection pool are cached per requested key in a bounded LRU
(DEFAULT_ANYKEY_CAPACITY entries, override with
DbContextServiceCollectionExt::add_dbcontext_anykey_with_capacity), so
repeated scopes reuse the same pool and tenant cardinality cannot grow memory
without bound:
let provider = ServiceCollection::new()
.add_dbcontext_anykey(|key, options| {
let tenant = key.to_string(); // map to a *known* tenant configuration
options.use_default_entity_model();
options.use_postgres(tenant_connection_string(&tenant));
})
.build()?;
let scope = provider.scope();
let mut ctx = scope.get_keyed_owned::<DbContext>("tenant-a")?;Security: never interpolate an untrusted requested key into SQL or a
connection string directly — resolve it through an allow-list/tenant table.
AnyKey contexts always use use_default_entity_model() (a dynamic key has no
#[context("...")] string); use global query filters for tenant isolation.
§Async provider initialization
DbContextServiceCollectionExt::add_dbcontext_async defers provider
creation (connection pool) to build_async().await. A failure becomes a typed
RdiError instead of a registration-time panic. The DbContext itself stays
a synchronous Scoped service, so handlers keep using #[inject(owned)] /
get_owned() unchanged:
let provider = ServiceCollection::new()
.add_dbcontext_async(|options| {
options.use_postgres("postgres://app/db");
// Optional: async validation after the pool is created.
options.async_preflight(|options| {
Box::pin(async move {
let provider = options.create_provider()?;
let mut conn = provider.get_connection().await?;
conn.query("SELECT 1", &[]).await?;
Ok(())
})
});
})
.build_async()
.await?;
let mut ctx: DbContext = provider.get_owned().expect("DbContext");Calling build() (sync) with async registrations returns
RdiError::AsyncServiceRequiresBuildAsync; use build_async().await.
§rust-dix 0.10 notes
- Macro key expressions:
#[inject(key = <expr>)]/#[inject(owned, key = <expr>)]accept anyimpl Into<ServiceKey>.DbContextKeyimplements the officialFrom<Key> for ServiceKeycontract; do not copy rust-dix’s injection macro. - Typed vs string keys: typed enum keys use
ServiceKey::new(key);&str/Stringregistration keys stayStringkeys. They are intentionally not interchangeable (ServiceKey::new("x")≠"x".into()). - MSRV 1.95; rust-ef 2.1 declares
rust-version = "1.95". - Async diagnostics: a sync singleton depending on an async service reports
SyncSingletonDependsOnAsyncService; resolving an uninitialized async service synchronously reportsAsyncServiceRequiresGetAsync. - Async cycle detection is chain-based; if an async provider factory ever
spawns detached tasks that resolve services, capture
provider.async_context()and resolve through it. - Async AnyKey is still not provided by rust-dix 0.10; dynamic keys keep the synchronous per-key initialization described above.
§Scoped lifetime & strict scope validation
rust-dix 0.8+ enables MEDI ValidateScopes semantics by default: resolving a
Scoped service from the root provider returns
RdiError::ScopedFromRootProvider — the root provider is not a scope.
get_owned / get_keyed_owned are the exception (caller-owned, never cached);
their constructor’s Scoped dependencies still obey validation.
Migrating from rust-dix 0.7 / rust-ef ≤ 2.0. Root resolutions that relied
on root-scope caching must move to provider.scope() (shared) or owned
resolution. A temporary escape hatch is
ServiceCollection::build_with(ServiceProviderOptions::lenient()).
§Disposal
Scope::drop / Scope::dispose() release the Scoped DbContext instances
created in that scope in reverse creation order; ServiceProvider::dispose()
releases singletons (including async-initialized providers). After disposal,
resolution returns ScopeDisposed / ProviderDisposed.
§Registration semantics
add_dbcontext* uses normal scoped / keyed_scoped registrations, so
rust-dix’s last-registration-wins rule applies: registering the same key
twice keeps the last registration. try_add_* behaves as MEDI TryAdd.
§Recommended: owned resolution for handlers
DbContext methods (set::<T>(), save_changes(), detect_changes())
require &mut self; own the context via get_owned(), avoiding
Arc<Mutex> and interior mutability:
#[derive(Inject)]
pub struct CreateBlogHandler {
#[inject(owned)]
ctx: DbContext,
}§Shared resolution (within a scope)
Resolve Arc<DbContext> from a scope when several consumers in the same scope
must share one instance. Arc<DbContext> only provides &self access; prefer
owned resolution when mutation is needed.
§Multiple databases (keyed)
let provider = ServiceCollection::new()
.add_dbcontext_keyed("primary", |options| {
options.use_postgres("host=primary/db");
})
.add_dbcontext_keyed("logs", |options| {
options.use_sqlite("logs.db");
})
.build()?;
let mut primary: DbContext = provider.get_keyed_owned("primary")?;
let scope = provider.scope();
let logs: Arc<DbContext> = scope.get_keyed("logs")?;Structs§
- Service
Collection - Service collection — register services, then call
.build(). - Service
Key - Key used by keyed registrations, including the
AnyKeyfallback sentinel. Key of a keyed service registration / resolution. - Service
Provider - Built service provider — the root DI container (read-only after build).
- Service
Provider Options - Options for
ServiceCollection::build_with/ServiceCollection::build_async_with.
Constants§
- DEFAULT_
ANYKEY_ CAPACITY - Default number of requested keys whose
AnyKeyoptions/provider are cached.
Traits§
- DbContext
Resolver - Typed-context resolution helpers.
- DbContext
Service Collection Ext - Adds the
DbContextregistration helpers torust_dix::ServiceCollection. - IDbContext
Key - Identity of a keyed
DbContext.