PhoenixmlDb.Core
IContainer
Primary interaction surface for storing, retrieving, querying, and managing documents within a PhoenixmlDb container.
#IContainer
Namespace: PhoenixmlDb.Core
Primary interaction surface for storing, retrieving, querying, and managing documents within a PhoenixmlDb container.
IContainer is the interface most application code works with day-to-day. While IDocumentDatabase manages containers and transactions at the database level, IContainer is where you put documents, run XQuery queries, and manage metadata.
A container holds a collection of documents that share the same index configuration, namespace bindings, and validation rules. Documents can be either XML or JSON β both formats are stored natively, parsed into the XQuery Data Model (XDM), and are fully queryable via XQuery.
Document naming: Each document is identified by a name string within its container. Names are URI-like identifiers. Path-style names (e.g., "customers/acme/profile.xml") are recommended because they enable prefix-based listing via IContainer.ListDocumentsAsync and create a natural organizational hierarchy. Names are case-sensitive and must be unique within the container.
Convenience methods vs. explicit transactions: The methods on IContainer (e.g., IContainer.PutDocumentAsync, IContainer.GetDocumentAsync) each run within an implicit transaction β a write transaction for mutations, a read transaction for queries. This is convenient for single operations, but if you need to perform multiple reads or writes atomically, use IDocumentDatabase.BeginWriteAsync or IDocumentDatabase.BeginRead to create an explicit transaction instead.
Basic usage:
// Store a document
await container.PutDocumentAsync("orders/2024/order-001.xml", orderXml);
// Retrieve it
var doc = await container.GetDocumentAsync("orders/2024/order-001.xml");
var content = await doc!.GetContentAsync();
// Query with XQuery
await foreach (var result in container.QueryAsync("//order[total > 100]"))
{
Console.WriteLine(result);
}
// List documents by prefix
await foreach (var info in container.ListDocumentsAsync("orders/2024/"))
{
Console.WriteLine($"{info.Name} ({info.SizeBytes} bytes)");
}
#Properties
| Name | Description |
|---|---|
Id
|
Gets the unique identifier for this container within the database. |
Name
|
Gets the human-readable name of this container. |
Options
|
Gets the configuration options for this container, including indexes, namespace bindings, and validation settings. |
#Methods
#DeleteDocumentAsync(String,Threading.CancellationToken)
Permanently deletes a document from the container.
Parameters:
-
nameβ The name of the document to delete. -
cancellationTokenβ Cancellation token.
Returns: true if the document existed and was deleted; false if no document with the given name was found.
Deletion removes the document content, its XDM node tree, all associated index entries, and all metadata. This operation is irreversible.
This method runs within an implicit write transaction. The return value lets you distinguish between "deleted successfully" and "nothing to delete" without needing a separate IContainer.DocumentExistsAsync call.
Example:
bool wasDeleted = await container.DeleteDocumentAsync("obsolete/old-report.xml");
if (!wasDeleted)
Console.WriteLine("Document was already gone.");
#DocumentExistsAsync(String,Threading.CancellationToken)
Checks whether a document with the specified name exists in this container.
Parameters:
-
nameβ The document name to check (case-sensitive). -
cancellationTokenβ Cancellation token.
Returns: true if the document exists; false otherwise.
This is a lightweight existence check that does not load the document content. Use it for guard checks before operations where you want to handle the exists/not-exists cases differently.
Note that in concurrent scenarios, the document could be created or deleted between this check and a subsequent operation. For atomic "create if not exists" behavior, use IContainer.PutDocumentAsync with Overwrite = false and catch DocumentExistsException.
#GetAllMetadataAsync(String,Threading.CancellationToken)
All metadata for a document, keyed by qualified name.
Parameters:
-
documentNameβ The document to read from. -
cancellationTokenβ Cancels the operation.
Returns: The document's metadata; empty if it has none.
Example:
var all = await container.GetAllMetadataAsync("reports/q1.xml");
var creator = all.Get(DcTerms.Creator);
#GetDocumentAsync(String,Threading.CancellationToken)
Retrieves a document by its name.
Parameters:
-
nameβ The exact document name to look up (case-sensitive). -
cancellationTokenβ Cancellation token.
Returns: The IDocument if found, or null if no document with the given name exists in this container.
The returned IDocument provides access to the document's content (via IDocument.GetContentAsync or IDocument.GetContentStreamAsync), its parsed XDM tree (via IDocument.GetRootNodeAsync), and its metadata.
This method runs within an implicit read transaction. If you need to read multiple documents with a consistent snapshot, use IDocumentDatabase.BeginRead to create an explicit read transaction.
Example:
var doc = await container.GetDocumentAsync("orders/order-001.xml");
if (doc is not null)
{
var xml = await doc.GetContentAsync();
Console.WriteLine($"Document {doc.Name}, {doc.SizeBytes} bytes, type: {doc.ContentType}");
}
#GetMetadataAsync(String,PhoenixmlDb.Xdm.XdmQName,Threading.CancellationToken)
Reads a metadata value by explicit qualified name.
Parameters:
-
documentNameβ The document to read from. -
nameβ The fully qualified metadata name. -
cancellationTokenβ Cancels the operation.
Returns: The value, or null if the document has no such metadata.
#GetMetadataAsync(String,String,Threading.CancellationToken)
Reads a metadata value from the container's default namespace.
Parameters:
-
documentNameβ The document to read from. -
nameβ The local metadata name, resolved against the container's default namespace. -
cancellationTokenβ Cancels the operation.
Returns: The value, or null if the document has no such metadata.
#GetMetadataAsync``1(String,PhoenixmlDb.Core.Metadata.MetadataProperty<``0>,Threading.CancellationToken)
Reads a typed metadata value.
Type parameters:
-
Tβ The property's CLR value type.
Parameters:
-
documentNameβ The document to read from. -
descriptorβ The metadata property descriptor. -
cancellationTokenβ Cancels the operation.
Returns: The value, or default if the document has no such metadata.
#GetMetadataByNamespaceAsync(String,PhoenixmlDb.Core.NamespaceId,Threading.CancellationToken)
All metadata for a document within one namespace.
Parameters:
-
documentNameβ The document to read from. -
namespaceIdβ The namespace to restrict to. -
cancellationTokenβ Cancels the operation.
Returns: The matching metadata; empty if the document has none in that namespace.
Served by a cursor range over the namespace key prefix, not by filtering.
#ListDocumentsAsync(String,Threading.CancellationToken)
Lists documents whose names start with the specified prefix.
Parameters:
-
prefixβ The name prefix to filter by. For example,"orders/2024/"returns all documents whose names begin with that string. The match is case-sensitive. -
cancellationTokenβ Cancellation token.
Returns: An IAsyncEnumerable`1 of DocumentInfo records for documents matching the prefix.
This is why path-style document names are recommended β they enable efficient hierarchical browsing. For example, with documents named "orders/2024/q1/inv-001.xml", "orders/2024/q2/inv-042.xml", etc., you can list all 2024 orders with prefix "orders/2024/" or just Q1 with "orders/2024/q1/".
The prefix match is performed on the stored name index and is efficient even for containers with many documents.
Example:
// List all configuration documents
await foreach (var info in container.ListDocumentsAsync("config/"))
{
Console.WriteLine(info.Name);
}
#ListDocumentsAsync(Threading.CancellationToken)
Lists all documents in the container.
Parameters:
-
cancellationTokenβ Cancellation token.
Returns: An IAsyncEnumerable`1 of DocumentInfo records, one per document. The enumeration order is implementation-defined.
Each DocumentInfo contains lightweight metadata (name, size, timestamps, content type) without loading the full document content. This is efficient for building document inventories, dashboards, or migration scripts.
For large containers, consider using the prefix-based overload IContainer.ListDocumentsAsync to narrow results.
Example:
await foreach (var info in container.ListDocumentsAsync())
{
Console.WriteLine($"{info.Name} β {info.ContentType}, {info.SizeBytes} bytes");
}
#PutDocumentAsync(String,IO.Stream,PhoenixmlDb.Core.DocumentOptions,Threading.CancellationToken)
Stores a document in the container from a
.
Parameters:
-
nameβ Document name (URI-like identifier). SeeIContainer.PutDocumentAsyncfor naming conventions. -
contentβ A readable stream containing XML or JSON content. The stream is read to completion but is not disposed by this method β the caller retains ownership. -
optionsβ Optional settings controlling content type detection, overwrite behavior, and initial metadata. SeeDocumentOptionsfor details. -
cancellationTokenβ Cancellation token.
Returns: A ValueTask that completes when the document is stored and indexed.
Exceptions:
-
DocumentExistsExceptionβ Thrown when a document with the samenamealready exists andDocumentOptions.Overwriteis set tofalse.
Use this overload when loading documents from files, HTTP responses, or other stream-based sources to avoid buffering the entire content in memory as a string. This is especially beneficial for large documents.
Example:
await using var fileStream = File.OpenRead("/data/catalog.xml");
await container.PutDocumentAsync("catalog.xml", fileStream);
#PutDocumentAsync(String,String,PhoenixmlDb.Core.DocumentOptions,Threading.CancellationToken)
Stores a document in the container from a string.
Parameters:
-
nameβ Document name (URI-like identifier). Path-style names are recommended (e.g.,"invoices/2024/inv-1042.xml") to enable prefix-based listing. Names are case-sensitive and must be unique within the container. -
contentβ The document content as XML or JSON. The content type is auto-detected from the content itself unless explicitly specified viaoptions. The content must be well-formed XML or valid JSON. -
optionsβ Optional settings controlling content type detection, overwrite behavior, and initial metadata. SeeDocumentOptionsfor details. -
cancellationTokenβ Cancellation token.
Returns: A ValueTask that completes when the document is stored and indexed.
Exceptions:
-
DocumentExistsExceptionβ Thrown when a document with the samenamealready exists andDocumentOptions.Overwriteis set tofalse.
This method runs within an implicit write transaction. The document is parsed, validated (if ContainerOptions.ValidationMode is set), stored, and indexed atomically.
By default, DocumentOptions.Overwrite is true, so calling this method with an existing document name silently replaces the previous content. Set Overwrite = false when you want insert-only semantics (e.g., to prevent accidental data loss).
Example:
Store an XML document with default options (auto-detect, overwrite enabled):
await container.PutDocumentAsync("products/widget.xml",
"<product><name>Widget</name><price>9.99</price></product>");
Store a JSON document with metadata and insert-only semantics:
await container.PutDocumentAsync("events/evt-42.json",
"""{"type": "click", "timestamp": "2024-03-15T10:30:00Z"}""",
new DocumentOptions
{
ContentType = ContentType.Json,
Overwrite = false,
Metadata = new Dictionary<XdmQName, XdmValue>
{
[new XdmQName(NamespaceId.PhoenixmlMeta, "source")] = XdmValue.From("web"),
[new XdmQName(NamespaceId.PhoenixmlMeta, "priority")] = XdmValue.From(1L)
}
});
#PutDocumentsAsync(Collections.Generic.IEnumerable<PhoenixmlDb.Core.DocumentInput>,Threading.CancellationToken)
Writes multiple documents. Engine implementations batch them into a single write transaction (one commit) for bulk-load throughput; this default falls back to sequential single-document writes so other implementations keep working. Returns the number of documents written.
#QueryAsync(String,Collections.Generic.IReadOnlyDictionary<String,Object>,Predicate<String>,Threading.CancellationToken)
Executes
against documents whose name passes
. Use this to hide system / sidecar documents from user-facing query surfaces. Defaults to delegating to the unfiltered overload so existing implementations stay source-compatible.
Parameters:
-
queryβ XQuery 4.0 expression. -
variablesβ External variable bindings. -
documentNameFilterβ Predicate over document name. Documents for which it returnsfalseare excluded before query iteration. Passnullfor legacy unfiltered behavior. -
cancellationTokenβ Cancellation token.
#QueryAsync(String,Collections.Generic.IReadOnlyDictionary<String,Object>,Threading.CancellationToken)
Executes an XQuery expression against the documents in this container.
Parameters:
-
queryβ An XQuery expression. The expression runs in the context of this container's document collection. Usecollection()to access all documents, ordoc("name")to access a specific document by name. -
variablesβ Optional external variable bindings. Keys are variable names (without the$prefix); values are the variable values. These can be referenced in the query as$variableName. -
cancellationTokenβ Cancellation token.
Returns: An IAsyncEnumerable`1 yielding each item in the XQuery result sequence. Results may be XDM nodes, atomic values, or other XQuery items depending on the query.
Queries benefit from the indexes configured in ContainerOptions.Indexes. For example, a path index on "//product/price" accelerates queries like //product[price > 50]. Without appropriate indexes, queries perform full document scans.
The default namespace bindings from ContainerOptions.DefaultNamespaces are automatically available in query expressions, so you don't need to redeclare them.
External variables are useful for parameterizing queries safely, avoiding string concatenation that could lead to injection issues.
Example:
Simple query across all documents:
await foreach (var result in container.QueryAsync("collection()//product[price > 100]"))
{
Console.WriteLine(result);
}
Parameterized query with external variables:
var variables = new Dictionary<string, object>
{
["minPrice"] = 50.0m,
["category"] = "electronics"
};
await foreach (var result in container.QueryAsync(
"""
for $p in collection()//product
where $p/price > $minPrice and $p/category = $category
order by $p/price descending
return $p/name
""",
variables))
{
Console.WriteLine(result);
}
#QueryMetadataAsync(PhoenixmlDb.Xdm.XdmQName,PhoenixmlDb.Xdm.XdmValue,Threading.CancellationToken)
Streams documents whose metadata equals a value, by explicit qualified name.
Parameters:
-
nameβ The fully qualified metadata name to match on. -
valueβ The value to match. -
cancellationTokenβ Cancels the enumeration.
#QueryMetadataAsync``1(PhoenixmlDb.Core.Metadata.MetadataProperty<``0>,``0,Threading.CancellationToken)
Streams documents whose metadata property equals a value.
Type parameters:
-
Tβ The property's CLR value type.
Parameters:
-
descriptorβ The metadata property to match on. -
valueβ The value to match. -
cancellationTokenβ Cancels the enumeration.
Most efficient when a metadata index is configured for the property via
. Without one the query scans the container's documents; both paths use the same value encoding and return the same documents.
Example:
await foreach (var info in container.QueryMetadataAsync(Routing.Status, "approved"))
Console.WriteLine($"Approved: {info.Name}");
#QueryMetadataRangeAsync(PhoenixmlDb.Xdm.XdmQName,Nullable<PhoenixmlDb.Xdm.XdmValue>,Nullable<PhoenixmlDb.Xdm.XdmValue>,Boolean,Boolean,Threading.CancellationToken)
Streams documents whose metadata falls within a range, by explicit qualified name.
Parameters:
-
nameβ The fully qualified metadata name to range over. -
lowerBoundβ Lower bound, or null for unbounded below. -
upperBoundβ Upper bound, or null for unbounded above. -
lowerInclusiveβ WhetherlowerBounditself matches. -
upperInclusiveβ WhetherupperBounditself matches. -
cancellationTokenβ Cancels the enumeration.
The general form. Use it for reference-typed properties such as
MetadataProperty<string>
, which the typed overload cannot express: an unconstrained
T?
cannot represent "no bound" for a value type, and using
default(T)
for it would make an unbounded range indistinguishable from one bounded at
DateTimeOffset.MinValue
or
0
.
#QueryMetadataRangeAsync``1(PhoenixmlDb.Core.Metadata.MetadataProperty<``0>,Nullable<``0>,Nullable<``0>,Boolean,Boolean,Threading.CancellationToken)
Streams documents whose metadata property falls within a range of values.
Type parameters:
-
Tβ The property's CLR value type.
Parameters:
-
descriptorβ The metadata property to range over. -
lowerBoundβ Lower bound, or null for unbounded below. -
upperBoundβ Upper bound, or null for unbounded above. -
lowerInclusiveβ WhetherlowerBounditself matches. Defaults to true. -
upperInclusiveβ WhetherupperBounditself matches. Defaults to true. -
cancellationTokenβ Cancels the enumeration.
Either bound may be null for an open-ended range: from: cutoff, to: null reads "at or after cutoff". Both null matches every document that carries the property at all.
A metadata index declared for this property with a matching value type serves the range directly; otherwise the container is scanned. As with equality, both paths return the same documents β the index changes how the answer is found, not what it is.
Ordering follows the property's XDM type, not its .NET string form: dates order chronologically and numbers numerically.
Example:
// everything received on or after the cutoff
await foreach (var d in container.QueryMetadataRangeAsync(Received, lowerBound: cutoff, upperBound: null))
Console.WriteLine(d.Name);
#SetMetadataAsync(String,PhoenixmlDb.Xdm.XdmQName,PhoenixmlDb.Xdm.XdmValue,Threading.CancellationToken)
Sets a metadata value by explicit qualified name.
Parameters:
-
documentNameβ The document to annotate. -
nameβ The fully qualified metadata name. -
valueβ The value to store. -
cancellationTokenβ Cancels the operation.
Exceptions:
-
DocumentNotFoundExceptionβ No such document in this container.
Opens and commits its own write transaction; see the string overload.
#SetMetadataAsync(String,String,String,Threading.CancellationToken)
Sets a metadata value in the container's default namespace.
Parameters:
-
documentNameβ The document to annotate. -
nameβ The local metadata name, resolved against the container's default namespace. -
valueβ The value to store. -
cancellationTokenβ Cancels the operation.
Exceptions:
-
DocumentNotFoundExceptionβ No such document in this container.
This overload opens and commits its own write transaction. To make a metadata write part of a larger atomic unit, use IWriteTransaction.SetMetadataAsync``1.
name is a local name, resolved against the container's ContainerOptions.DefaultMetadataNamespace. Two applications sharing a database can therefore both use a common name such as status without colliding.
To make metadata queryable with IContainer.QueryMetadataAsync``1, add a metadata index to the container's IndexConfiguration via IndexConfiguration.AddMetadataIndex.
Example:
await container.SetMetadataAsync("reports/q1.xml", "status", "approved");
#SetMetadataAsync``1(String,PhoenixmlDb.Core.Metadata.MetadataProperty<``0>,``0,Threading.CancellationToken)
Sets a typed metadata value. The namespace and value type come from the property.
Type parameters:
-
Tβ The property's CLR value type.
Parameters:
-
documentNameβ The document to annotate. -
descriptorβ The metadata property descriptor. -
valueβ The value to store. -
cancellationTokenβ Cancels the operation.
Exceptions:
-
DocumentNotFoundExceptionβ No such document in this container.
Opens and commits its own write transaction; see the string overload.
Example:
await container.SetMetadataAsync("reports/q1.xml", DcTerms.Creator, "Jane Smith");