Skip to main content

Module di

Module di 

Source
Expand description

DI integration — AddDbContext on rust-dix.

Supports single-context (default) and multi-context (keyed) registration. DbContext is registered as Scoped and can be resolved either as Arc<DbContext> (shared within a scope) or as owned DbContext (fresh instance, idiomatic &mut self access).

DbContext methods (set::<T>(), save_changes(), detect_changes()) require &mut self. The idiomatic pattern is to own the context via get_owned(), avoiding Arc<Mutex> and interior mutability entirely.

use rust_dix::*;
use rust_ef::di::*;
use rust_ef::db_context::DbContext;
use rust_ef_sqlite::DbContextOptionsBuilderExt as _;

// rust-dix 0.6+: `build()` returns `Arc<ServiceProvider>` directly,
// and `get_owned()` returns `Result<T, RdiError>`.
let provider = ServiceCollection::new()
    .add_dbcontext(|options| {
        options.use_sqlite("data source=app.db");
    })
    .build()
    .unwrap();

// Owned: fresh instance, direct &mut self access — no locks needed.
let mut ctx: DbContext = provider.get_owned().expect("DbContext");
ctx.set::<Blog>().add(blog);
ctx.save_changes().await?;

Handlers declare a bare ctx: DbContext field marked with #[inject(owned)]#[derive(Inject)] resolves it via get_owned(). Unmarked fields fall back to Default::default(). Use #[inject(scoped)] (not bare #[inject], which defaults to Singleton) to avoid captive dependency errors with the Scoped DbContext:

#[derive(Inject)]
pub struct CreateBlogHandler {
    #[inject(owned)]
    ctx: DbContext,            // bare T + #[inject(owned)] → get_owned()
}

#[inject(scoped)]
#[async_trait]
impl IRequestHandler<CreateBlogRequest, BlogModel> for CreateBlogHandler {
    async fn handle(&mut self, req: CreateBlogRequest) -> Result<BlogModel> {
        self.ctx.set::<Blog>().add(blog);
        self.ctx.save_changes().await?;
        // ...
    }
}

§Shared resolution (within a scope)

When multiple consumers in the same scope must share a single instance (e.g. an IHostedService that seeds data before handlers run), resolve as Arc<DbContext>:

use rust_dix::scope::ScopeFactory;  // for create_scope()

let scope = provider.create_scope();
let ctx: Arc<DbContext> = scope.get().expect("DbContext");
// Additional get() calls within this scope return the same instance.

Note: Arc<DbContext> only provides &self access. Mutation requires Arc::get_mut (refcount == 1) or restructuring to owned resolution. Prefer get_owned() for any scope that needs &mut self.

§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")
            .add_interceptor(AuditInterceptor);
    })
    .build()
    .unwrap();

// Owned keyed resolution (recommended for handlers):
let mut primary: DbContext = provider.get_keyed_owned("primary").expect("primary ctx");
let mut logs: DbContext = provider.get_keyed_owned("logs").expect("logs ctx");

// Shared keyed resolution (within a scope):
// let primary: Arc<DbContext> = scope.get_keyed("primary");

§Scoped Lifetime

add_dbcontext registers the context as Scoped. Resolving via get() shares the instance within a scope; resolving via get_owned() bypasses the cache and returns a fresh instance each call (both are isolated across scopes). Resolving directly from the root ServiceProvider (without creating a scope) degrades to a fresh instance per call (transient).

rust-webapp: the HTTP pipeline automatically creates a scope per request. Handlers are resolved via get_owned::<Handler>(), which owns a fresh DbContext — no manual scope management needed.

Structs§

ServiceCollection
ServiceProvider

Traits§

DbContextServiceCollectionExt
Adds add_dbcontext and add_dbcontext_keyed to rust_dix::ServiceCollection.