Marten Metadata
A major goal of the Marten V4 release was to enable much richer document and event metadata collection based on user requests. To that end, Marten still supports the same basic metadata columns as Marten V2/V3, but adds other opt in columns.
The available columns for document storage are:
Column Name | Description | Enabled by Default |
---|---|---|
mt_last_modified | Timestamp of the last time the document record was modified | Yes |
mt_version | Guid value that marks the current version of the document. This supports optimistic concurrency | Yes |
mt_dotnet_type | Assembly qualified name of the .Net type persisted to this row | Yes |
correlation_id | User-supplied correlation identifier (string ) | No, opt in |
causation_id | User-supplied causation identifier (string ) | No, opt in |
headers | User-supplied key/value pairs for extensible metadata | No, opt in |
mt_deleted | Boolean flag noting whether the document is soft-deleted | Only if the document type is configured as soft-deleted |
mt_deleted_at | Timestamp marking when a document was soft-deleted | Only if the document type is configured as soft-deleted |
Correlation Id, Causation Id, and Headers
TIP
At this point, the Marten team thinks that using a custom ISessionFactory
to set correlation and causation data is the most likely usage for this feature. The Marten team plans to build a sample application showing Marten being used with Open Telemetry tracing soon.
The first step is to enable these columns on the document types in your system:
var store = DocumentStore.For(opts =>
{
opts.Connection("some connection string");
// Optionally turn on metadata columns by document type
opts.Schema.For<User>().Metadata(x =>
{
x.CorrelationId.Enabled = true;
x.CausationId.Enabled = true;
x.Headers.Enabled = true;
});
// Or just globally turn on columns for all document
// types in one fell swoop
opts.Policies.ForAllDocuments(x =>
{
x.Metadata.CausationId.Enabled = true;
x.Metadata.CorrelationId.Enabled = true;
x.Metadata.Headers.Enabled = true;
});
});
Next, you relay the actual values for these fields at the document session level as shown below:
public void SettingMetadata(IDocumentSession session, string correlationId, string causationId)
{
// These values will be persisted to any document changed
// by the session when SaveChanges() is called
session.CorrelationId = correlationId;
session.CausationId = causationId;
}
Headers are a little bit different, with the ability to set individual header key/value pairs as shown below:
public void SetHeader(IDocumentSession session, string sagaId)
{
session.SetHeader("saga-id", sagaId);
}
Tracking Metadata on Documents
Marten can now set metadata values directly on the documents persisted by Marten, but this is an opt in behavior. You can explicitly map a public member of your document type to a metadata value individually. Let's say that you have a document type like this where you want to track metadata:
public class DocWithMetadata
{
public Guid Id { get; set; }
// other members
public Guid Version { get; set; }
public string Causation { get; set; }
public bool IsDeleted { get; set; }
}
To enable the Marten mapping to metadata values, use this syntax:
var store = DocumentStore.For(opts =>
{
opts.Connection("some connection string");
// Explicitly map the members on this document type
// to metadata columns.
opts.Schema.For<DocWithMetadata>().Metadata(m =>
{
m.Version.MapTo(x => x.Version);
m.CausationId.MapTo(x => x.Causation);
m.IsSoftDeleted.MapTo(x => x.IsDeleted);
});
});
TIP
Note that mapping a document member to a metadata column will implicitly enable that metadata column collection.
For correlation, causation, and last modified tracking, an easy way to do this is to just implement the Marten ITracked
interface as shown below:
public class MyTrackedDoc: ITracked
{
public Guid Id { get; set; }
public string CorrelationId { get; set; }
public string CausationId { get; set; }
public string LastModifiedBy { get; set; }
}
If your document type implements this interface, Marten will automatically enable the correlation and causation tracking, and set values for correlation, causation, and the last modified data on documents anytime they are loaded or persisted by Marten.
Likewise, version tracking directly on the document is probably easiest with the IVersioned
interface as shown below:
public class MyVersionedDoc: IVersioned
{
public Guid Id { get; set; }
public Guid Version { get; set; }
}
Implementing IVersioned
will automatically opt your document type into optimistic concurrency checking with mapping of the current version to the IVersioned.Version
property.
Disabling All Metadata
If you want Marten to run lean, you can omit all metadata fields from Marten with this configuration:
var store = DocumentStore.For(opts =>
{
opts.Connection("some connection string");
// This will direct Marten to omit all informational
// metadata fields
opts.Policies.DisableInformationalFields();
});
Querying by Last Modified
Documents can be queried by the last modified time using these custom extension methods in Marten:
ModifiedSince(DateTimeOffset)
- Return only documents modified since specific date (not inclusive)ModifiedBefore(DateTimeOffset)
- Return only documents modified before specific date (not inclusive)
Here is a sample usage:
public async Task sample_usage(IQuerySession session)
{
var fiveMinutesAgo = DateTime.UtcNow.AddMinutes(-5);
var tenMinutesAgo = DateTime.UtcNow.AddMinutes(-10);
// Query for documents modified between 5 and 10 minutes ago
var recents = await session.Query<Target>()
.Where(x => x.ModifiedSince(tenMinutesAgo))
.Where(x => x.ModifiedBefore(fiveMinutesAgo))
.ToListAsync();
}
Indexing
See metadata index for information on how to enable predefined indexing