Migrating v10 → v11
v11 is a breaking release. Its centrepiece is a redesign we call store-as-connection: the document
store is now a two-level model — a long-lived IDocumentStore and a short-lived IDocumentSession
(the unit of work, the EF-DbContext analogue). This removes the ambient thread-safety / scoping traps the old
singleton-store-as-the-operation-API forced, and it’s the reason for most of the changes below.
There are no [Obsolete] shims — v11 is a clean break. This guide lists every change you need to make, with
before/after code, then the optional new features worth adopting.
At a glance
Section titled “At a glance”| What changed | v10 | v11 |
|---|---|---|
| Packages | Shiny.DocumentDb.Extensions.DependencyInjection + Shiny.DocumentDb.Diagnostics | folded into Shiny.DocumentDb — remove both refs |
| Unit of work | store.CreateUnitOfWork() → UnitOfWork | store.OpenSession() → IDocumentSession |
| Discard buffer | uow.Clear() | session.ClearPending() |
| Named stores | IDocumentStoreProvider | IDocumentSessionFactory |
| Typed context | DocumentContext over a store (stateless) | DocumentContext over a session (disposable) |
| Telemetry | AddDocumentStoreInstrumentation() / o.Instrumentation = true | always-on, embedded — just subscribe your OTel pipeline |
1. Packages — remove two references
Section titled “1. Packages — remove two references”DI registration and diagnostics now ship inside the core Shiny.DocumentDb package. Delete the two now-empty
package references; the namespaces are unchanged, so nothing else moves.
<!-- remove these two --><PackageReference Include="Shiny.DocumentDb.Extensions.DependencyInjection" Version="10.*" /><PackageReference Include="Shiny.DocumentDb.Diagnostics" Version="10.*" />using Shiny.DocumentDb;, AddDocumentStore(...), AddDocumentContext(...), seeding, and multi-tenancy all
keep working from the core package.
2. Unit of work → IDocumentSession
Section titled “2. Unit of work → IDocumentSession”CreateUnitOfWork() and the public UnitOfWork type are removed. Open a session instead — the buffered
write verbs (Add/AddRange/Update/Upsert/Remove) and SaveChanges are identical. A session is
IAsyncDisposable, so await using it.
// v10var uow = store.CreateUnitOfWork();uow.Add(order).Update(customer).Remove<Cart>(cartId);await uow.SaveChanges();
// v11await using var session = store.OpenSession();session.Add(order).Update(customer).Remove<Cart>(cartId);await session.SaveChanges();uow.Clear()→session.ClearPending().SaveChanges(suppressInterceptors: true)is unchanged.- Contiguous same-type runs still coalesce into the batch fast path.
Where to get a session
Section titled “Where to get a session”| Host | How | Register |
|---|---|---|
| Any (one-off) | store.OpenSession() | AddDocumentStore(...) |
| ASP.NET Core | inject scoped IDocumentSession | add .AddScopedDocumentSession() |
| MAUI / desktop / background / Orleans | inject singleton IDocumentSessionFactory, factory.OpenSession() per unit | AddDocumentStore(...) (factory always registered) |
IDocumentSession is the DbContext analogue: short-lived, single-flow, not thread-safe. In ASP.NET register it
scoped; elsewhere open one per unit of work from the factory (mirrors EF’s IDbContextFactory).
3. Named / multi-store: IDocumentStoreProvider → IDocumentSessionFactory
Section titled “3. Named / multi-store: IDocumentStoreProvider → IDocumentSessionFactory”// v10public class MyService(IDocumentStoreProvider stores){ void Work() => stores.GetStore("orders").Insert(...);}
// v11public class MyService(IDocumentSessionFactory stores){ void Work() => stores.GetStore("orders").Insert(...); // GetStore unchanged // …or a unit of work on a named store: Task Unit() { using var s = stores.OpenSession("orders"); s.Add(...); return s.SaveChanges(); }}IDocumentStoreProvider is removed; IDocumentSessionFactory absorbs GetStore(name) and adds OpenSession(name).
4. Typed DocumentContext is now session-backed
Section titled “4. Typed DocumentContext is now session-backed”The generated DocumentContext now wraps an IDocumentSession (it is a unit of work) and is
IAsyncDisposable/IDisposable — like EF’s DbContext. The DI registrations are unchanged in name:
AddAppContext(...)— scoped context (ASP.NET). InjectAppContextin a request handler.AddAppContextFactory(...)— singletonIDocumentContextFactory<AppContext>(MAUI/desktop/background). Callfactory.Create()per unit of work andawait usingthe result.
What you must change:
// v10 — manual construction took a storevar db = new AppContext(store);
// v11 — the generated constructor takes a sessionawait using var db = new AppContext(store.OpenSession());context.CreateUnitOfWork()is gone — the context is the unit of work:context.Add(x)+await context.SaveChanges(), or reach the session viacontext.Session.context.BeginTransaction()is available too.- Typed set writes (
db.Users.Insert(...),Update,Upsert,Remove) are unchanged (still immediate). - Dispose the context (
await using) — a factory-created context owns a DI scope + session. In ASP.NET the container disposes the scoped context for you. - If you call the low-level
AddDocumentContext<T>/AddDocumentContextFactory<T>directly (rare), theactivatordelegate is nowFunc<IDocumentSession, TContext>instead ofFunc<IDocumentStore, TContext>.
5. Telemetry is embedded — remove the opt-in
Section titled “5. Telemetry is embedded — remove the opt-in”The instrumentation decorator, AddDocumentStoreInstrumentation(), and DocumentStoreOptions.Instrumentation are
removed. Every store now emits OpenTelemetry metrics + spans directly, on every provider and every construction
path, zero-cost when unobserved.
// v10services.AddDocumentStore(o => { o.Instrumentation = true; o.DatabaseProvider = …; });services.AddDocumentStoreInstrumentation();
// v11 — just register the store; subscribe your OTel pipelineservices.AddDocumentStore(o => o.DatabaseProvider = …);
builder.Services.AddOpenTelemetry() .WithTracing(t => t.AddSource("Shiny.DocumentDb")) .WithMetrics(m => m.AddMeter("Shiny.DocumentDb"));Operation names and db.* tags are unchanged, so existing dashboards keep working.
6. Write interceptors (mostly additive)
Section titled “6. Write interceptors (mostly additive)”Interceptors did not break — v11 adds capabilities. Your existing IDocumentInterceptor keeps working, and now:
ctx.Servicesgives you the write’s DI scope (non-nullable) — resolve scoped services inside the hook.- Scoped interceptors are supported:
services.AddScoped<IDocumentInterceptor, X>(). ctx.Session(and the existingctx.Store) is bound to the write’s transaction for atomic re-entrant side effects —ctx.Session.Add(outbox); await ctx.Session.SaveChanges();.- Bug fix: DI-registered interceptors now fire on every provider (they previously never ran on MongoDB, Cosmos, LiteDB, IndexedDB, DynamoDB, Azure Table, or Orleans grain storage).
See Write Interceptors.
New in v11 — worth adopting
Section titled “New in v11 — worth adopting”Optional, non-breaking, and enabled by the session model:
- Explicit transactions + consistent reads —
await using var tx = await session.BeginTransaction();for pessimistic locking reads (session.Get(id, LockMode.Update)) and grouping set-basedExecuteUpdate/ExecuteDelete. Pass an isolation level (BeginTransaction(IsolationLevel.Snapshot)) for a consistent-read session. Relational providers. - Scope-aware multi-tenancy — register
ITenantResolverscoped and it resolves the request’s own tenant through a scoped session; no ambientIHttpContextAccessorneeded. See Multi-tenancy. - Scope-aware temporal actor —
o.MapTemporal<Order>(t => t.ResolveActor = sp => sp.GetService<ICurrentUser>()?.Id)captures “who” from the request scope. See Temporal. - Unit-of-work telemetry — a session emits a
unit_of_workparent span (db.session.id) so its operations correlate into one trace, plus adb.client.unit_of_work.operationshistogram. See Telemetry.
Checklist
Section titled “Checklist”- Remove the
Extensions.DependencyInjectionandDiagnosticspackage references. - Delete
AddDocumentStoreInstrumentation()ando.Instrumentation = true; add OTel.AddSource/.AddMeter("Shiny.DocumentDb"). store.CreateUnitOfWork()→await using store.OpenSession();uow.Clear()→session.ClearPending().IDocumentStoreProvider→IDocumentSessionFactory.- Add
.AddScopedDocumentSession()(ASP.NET) or injectIDocumentSessionFactorywhere there’s no scope. new AppContext(store)→new AppContext(store.OpenSession());context.CreateUnitOfWork()→context.Add/SaveChanges;await usingfactory-created contexts.- Build, then run your tests.