Skip to content
Document DB v12 - Improved Interceptors with Soft Delete Integration, AI protections, & Admin UI with Aspire Integration!How!?

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.

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 them
await 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-nothing
await using var uow = store.OpenSession();
uow.Add(order)
.Add(orderLine1)
.Add(orderLine2)
.Remove<Cart>(cartId);
await uow.SaveChanges(); // one transaction; rolls back entirely on failure

Why 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.

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.