Skip to content
Introducing AI Conversations: Natural Language Interaction for Your Apps! Learn More

Limitations

This page collects what the library does not do, so you can rule it out (or in) before adopting it. Where a limitation is provider-specific, see Provider Reference for the full matrix.

DocumentDB is a document store, not a relational ORM. A few things follow from that and are not going to change:

  • No JOINs across document types. Store related data on the same document (embed child collections) or denormalize. Cross-document lookups happen with two Get calls.
  • No referential integrity / foreign keys. Nothing in the schema enforces that a CustomerId on an Order points to an existing Customer. Validate in application code.
  • No schema, no migrations. The flip side: removing a property from your C# type does not delete it from stored JSON. Old fields just stop being deserialized. Use RemoveProperty if you need to physically strip them.
  • One document type per table is optional, not enforced. By default all types share the documents table and are discriminated by TypeName. Mixing types on the same table prevents the database engine from using a per-type clustered key.
  • Every document type must expose a public Id property of type Guid, int, long, or string. Alternative names are supported via MapTypeToTable<T>("tbl", x => x.MyKey) but only when a custom table mapping is also set.
  • string Ids must be supplied before Insert — there is no auto-generation strategy for them.
  • The library is async-only. There are no synchronous overloads.
  • One default table per store. All unmapped types share DocumentStoreOptions.TableName.
  • Two types cannot map to the same custom table. Throws InvalidOperationException at registration.
  • SetProperty is scalar-only. Supported value types: string, int, long, double, float, decimal, bool, null. To replace a nested object or collection, use Update (full replacement) or Upsert (merge — see provider note below).
  • UnitOfWork is not thread-safe. Use one instance per logical operation / request scope.
  • Streaming methods on shared-connection providers hold the internal semaphore for the duration of enumeration. Do not interleave other store operations inside the same await foreach against SQLite, SQLCipher, or DuckDB stores. Server SQL providers (Postgres / MySQL / SQL Server) open the streaming connection from the driver pool and do not block other callers.
  • CreateIndexAsync only exists on the concrete SQL DocumentStore types (SQLite, SQLCipher, PostgreSQL, SQL Server, MySQL, DuckDB). It is not on IDocumentStore.
  • No INCLUDE columns, no filtered indexes beyond the automatic WHERE TypeName = '...' filter applied by the library.
  • CosmosDB, MongoDB, LiteDB, IndexedDB do not expose CreateIndexAsync. CosmosDB indexes everything automatically (tune via container indexing policy). MongoDB indexes are managed with native Mongo tooling. LiteDB does not configure indexes. IndexedDB indexes are fixed at object-store creation time (when you bump Version).

The expression API translates a subset of C#. Anything outside this list throws NotSupportedException at query time:

Supported in .Where() / .Select()Not supported
==, !=, >, >=, <, <=, &&, ||, !string.ToLower(), ToUpper()
null / != nullstring.IsNullOrEmpty(), IsNullOrWhiteSpace()
string.Contains, StartsWith, EndsWith (translated to LIKE)string.Equals(other, StringComparison.*)
Nested property access (o.Address.City)Math.*, DateTime.AddDays, TimeSpan arithmetic
collection.Any(), Any(predicate), Count(), Count(predicate)Explicit casts beyond boxing ((int)x.Age)
Captured closure variablesstring.Format, interpolation
Enum equality (boxed via implicit Convert)Select projecting into anonymous types or tuples
DateTime equality / comparison (ISO-8601 ordering)Custom method calls on your own types
Sum/Min/Max/Average on child collections in .SelectGroupBy on multiple keys
Single-key GroupByJoin, SelectMany, Distinct, Union, Except, Zip

If your predicate needs something on the right column of this table, fall back to raw SQL via Query<T>("...", parameters) — but note that raw SQL is not supported on LiteDB or IndexedDB.

Full matrix on Provider Reference. The most consequential:

ProviderWhat’s missing or different
PostgreSQLUpsert is shallow merge only, not RFC 7396 deep merge. No spatial. No Backup().
SQL ServerSame shallow Upsert. Requires SQL Server 2025+ / Azure SQL with native JSON type. No spatial. No Backup().
MySQLNo spatial. No Backup().
CosmosDBNo Backup(). No CreateIndexAsync (tune indexing policy on the container). Cost model is RU-per-operation — full scans over unindexed paths get expensive fast.
MongoDBNo raw SQL. No spatial. No CreateIndexAsync (manage indexes with native Mongo tooling). Transactions are compensating on single-node deployments — only inserts are tracked and rolled back; updates and removes are not. Use a replica set + custom IClientSessionHandle for true ACID.
DuckDBSingle-process / writer-one (like SQLite). No spatial. No Backup() (copy the file directly or use DuckDB EXPORT DATABASE). SetProperty/RemoveProperty go through json_merge_patch rather than json_set/json_remove.
LiteDBAll predicates evaluated in C# after a full load of every document of that type. No raw SQL. No spatial. Fine for small datasets, painful for large ones.
IndexedDBSame client-side predicate evaluation as LiteDB. No raw SQL. No spatial. No CreateIndexAsync. Browser quota typically ~50 MB before prompting. Single-tab writer.
  • SQLite / DuckDB stores keep a single long-lived connection and serialize every operation through a SemaphoreSlim — embedded engines lock the whole database on writes, so multi-flighting buys nothing. Two simultaneous callers queue.
  • Postgres / MySQL / SQL Server stores open a fresh DbConnection per operation and let the ADO.NET driver pool multiplex callers. A single store instance is safe to share across threads and operations execute concurrently up to the pool size.
  • LiteDB is single-process: opening the same file from two processes fails.
  • IndexedDB is effectively single-tab — multi-tab writes can interleave non-deterministically.
  • CosmosDB / MongoDB use their respective documented thread-safe clients (CosmosClient, IMongoClient) and pool internally.
  • Nested transactions are not supported. Calling RunInTransaction inside a transaction reuses the outer transaction (no SAVEPOINT).
  • Optimistic concurrency (MapVersionProperty) checks the version on Update and Upsert-as-update only. It does not protect SetProperty, RemoveProperty, ExecuteUpdate, or ExecuteDelete — those are unchecked bulk operations.

Backup() is only on the concrete store types: SqliteDocumentStore, SqlCipherDocumentStore, LiteDbDocumentStore. Not on IDocumentStore. Not on the server-database providers (use their native tooling: pg_dump, BACKUP DATABASE, mysqldump, etc.).

There is no built-in cap on document size, but each provider has practical limits:

ProviderPractical max single-document size
SQLite~1 GB (SQLITE_MAX_LENGTH, configurable but rarely worth it)
PostgreSQL JSONB255 MB (toasted)
SQL Server JSON2 GB (max size)
MySQL JSON1 GB
DuckDB JSONPractically bounded by available memory during reads (vectorized engine)
CosmosDB2 MB per document — the hard one to plan around
MongoDB BSON16 MB per document — hard server limit
LiteDB1 MB per document, 256 MB per page chain
IndexedDBBrowser-dependent, typically tens of MB total quota

If you have documents approaching these, split them. The library does not page large blobs for you.