FAQ & Decision Trees
Common questions about how DocumentDb is designed and which API to reach for. If you’re coming from Entity Framework, start with the write-model questions — the mental model is deliberately a little different.
Which write API should I use?
Section titled “Which write API should I use?”DocumentDb gives you three ways to write, in increasing order of grouping. Pick the smallest one that fits.
Are you writing more than one document?│├─ No ── one document, write it now│ └─► Insert / Update / Upsert / Remove (auto-commit, one round trip)│└─ Yes │ ├─ All the same type, and you just want them in fast? │ └─► BatchInsert(documents) (single transaction, command reuse) │ └─ Mixed operations / mixed types that must all succeed or all fail together? └─► store.OpenSession() → Add/Update/Remove → SaveChanges()| You want to… | Use | Atomic? | Notes |
|---|---|---|---|
| Write a single document | Insert / Update / Upsert / Remove |
n/a (one op) | Auto-commits. No setup. |
| Insert many documents of one type, fast | BatchInsert |
Yes — the whole batch | Auto-generates IDs; rolls the batch back on any failure. |
| Group several writes (any mix) atomically | store.OpenSession() (IDocumentSession) + SaveChanges |
Yes — the whole unit | Buffers Add/Update/Upsert/Remove, commits once. |
// Single writes — just do themawait store.Insert(user);await store.Update(order);
// Bulk insert of one type — fastest path for "load these in"var count = await store.BatchInsert(users);
// Several writes, all-or-nothingawait using var uow = store.OpenSession();uow.Add(order) .Add(orderLine1) .Add(orderLine2) .Remove<Cart>(cartId);await uow.SaveChanges(); // one transaction; rolls back entirely on failureWhy is the unit of work a separate IDocumentSession and not IDocumentStore itself?
Section titled “Why is the unit of work a separate IDocumentSession and not IDocumentStore itself?”This is exactly EF’s split, and v11 mirrors it. IDocumentStore is long-lived, shared
infrastructure (a singleton): it owns the connection(s), the change-notification broadcaster
and its subscribers, and the type/ID caches. IDocumentSession is the short-lived, single-flow
unit of work — the EF-DbContext analogue — that carries the mutable “pending writes” buffer,
its own DI scope, and (optionally) an explicit transaction.
Putting the pending-writes buffer on the shared store would mean:
- Concurrency hazards. Two callers buffering writes would land in the same buffer; whoever calls save first would flush the other’s half-built work.
- Forgotten-flush footguns. A long-lived object accumulates pending writes; a later, unrelated save could flush stale ones.
So the buffer lives on a per-operation session whose lifetime is bounded by your use of it.
Can I read my own uncommitted writes from a session?
Section titled “Can I read my own uncommitted writes from a session?”No. A session is a write buffer, not a tracking context with an identity map.
Reads (Get, Query, Count) always go live against the store and won’t see writes that
are still buffered in a session. Call SaveChanges first if a later read needs to see them.
This is a deliberate difference from EF’s DbContext, which serves buffered changes back
from its change tracker.
How do I run several operations in one transaction?
Section titled “How do I run several operations in one transaction?”Open a session (store.OpenSession()). Everything you Add/Update/Upsert/Remove is
applied inside a single transaction when you call SaveChanges, and rolled back as a whole
if anything fails. For finer control — locking reads, multiple set-based ExecuteUpdate/
ExecuteDelete, or a chosen isolation level — open an explicit transaction on the session:
await using var tx = await session.BeginTransaction(); … await tx.Commit(); (relational
providers). SaveChanges joins the active transaction when one is open.
Do I need to dispose a session?
Section titled “Do I need to dispose a session?”Yes — a session is IAsyncDisposable (like an EF DbContext), so await using it. An idle
session holds no connection between operations, but disposing it releases any open explicit
transaction (rolling it back if uncommitted) and, for a factory-created session, its child DI
scope. In ASP.NET the container disposes the scoped session for you at the end of the request.


