Bootstrapping Marten
As briefly shown in the getting started page, Marten comes with the AddMarten()
extension method for the .NET IServiceCollection
to quickly add Marten to any ASP.NET Core or Worker Service application:
// This is the absolute, simplest way to integrate Marten into your
// .NET application with Marten's default configuration
builder.Services.AddMarten(options =>
{
// Establish the connection string to your Marten database
options.Connection(builder.Configuration.GetConnectionString("Marten")!);
// Specify that we want to use STJ as our serializer
options.UseSystemTextJsonForSerialization();
// If we're running in development mode, let Marten just take care
// of all necessary schema building and patching behind the scenes
if (builder.Environment.IsDevelopment())
{
options.AutoCreateSchemaObjects = AutoCreate.All;
}
});
The AddMarten()
method will add these service registrations to your application:
IDocumentStore
with a Singleton lifetime. The document store can be used to create sessions, query the configuration of Marten, generate schema migrations, and do bulk inserts.IDocumentSession
with a Scoped lifetime for all read and write operations. By default, this is done with theIDocumentStore.OpenSession()
method and the session created will have the identity map behaviorIQuerySession
with a Scoped lifetime for all read operations against the document store.
For more information, see:
- Dependency injection in ASP.NET Core for more explanation about the service lifetime behavior.
- Check identity map mechanics for an explanation of Marten session behavior
- Check storing documents and unit of work for session basics
At runtime, when your application needs to resolve IDocumentStore
for the first time, Marten will:
- Resolve a
StoreOptions
object from the initialAddMarten()
configuration - Apply all registered
IConfigureMarten
services to alter thatStoreOptions
object - Apply all registered
IAsyncConfigureMarten
services to alter thatStoreOptions
object - Reads the
IHostEnvironment
for the application if it exists to try to determine the main application assembly and paths for generated code output - Attaches any
IInitialData
services that were registered in the IoC container to theStoreOptions
object - Finally, Marten builds a new
DocumentStore
object using the now configuredStoreOptions
object
This model is comparable to the .Net IOptions
model.
Register DocumentStore with AddMarten()
INFO
All the examples in this page are assuming the usage of the default IoC container Microsoft.Extensions.DependencyInjection
, but Marten can be used with any IoC container or with no IoC container whatsoever.
First, if you are using Marten completely out of the box with no customizations (besides attributes on your documents), you can just supply a connection string to the underlying Postgresql database like this:
var connectionString = Configuration.GetConnectionString("postgres");
// By only the connection string
services.AddMarten(connectionString);
The second option is to supply a nested closure to configure Marten inline like so:
var connectionString = Configuration.GetConnectionString("postgres");
services.AddMarten(opts =>
{
opts.Connection(connectionString);
})
// Using the "Optimized artifact workflow" for Marten >= V5
// sets up your Marten configuration based on your environment
// See https://martendb.io/configuration/optimized_artifact_workflow.html
.OptimizeArtifactWorkflow();
Lastly, if you prefer, you can pass a Marten StoreOptions
object to AddMarten()
like this example:
var connectionString = Configuration.GetConnectionString("postgres");
// Build a StoreOptions object yourself
var options = new StoreOptions();
options.Connection(connectionString);
services.AddMarten(options)
// Using the "Optimized artifact workflow" for Marten >= V5
// sets up your Marten configuration based on your environment
// See https://martendb.io/configuration/optimized_artifact_workflow.html
.OptimizeArtifactWorkflow();
Using NpgsqlDataSource 7.0
TIP
You will have to use the NpgsqlDataSource
registration if you want to opt into Npgsql logging. See the Npgsql documentation on logging for more information.
You can also use the NpgsqlDataSource to configure Marten connection settings. From Npgsql docs:
The data source represents your PostgreSQL database and can hand out connections to it or support direct execution of SQL against it. The data source encapsulates the various Npgsql configuration needed to connect to PostgreSQL, as well the connection pooling which makes Npgsql efficient.
You can use the AddNpgsqlDataSource
method from Npgsql.DependencyInjection package to perform a setup by calling the UseNpgsqlDataSourceMethod
:
services.AddNpgsqlDataSource(ConnectionSource.ConnectionString);
services.AddMarten()
.UseLightweightSessions()
.UseNpgsqlDataSource();
If you're on .NET 8 (and above), you can also use a dedicated keyed registration. This can be useful for scenarios where you need more than one data source registered:
const string dataSourceKey = "marten_data_source";
services.AddNpgsqlDataSource(ConnectionSource.ConnectionString, serviceKey: dataSourceKey);
services.AddMarten()
.UseLightweightSessions()
.UseNpgsqlDataSource(dataSourceKey);
Using a Multi-Host Data Source 7.11
Marten includes support for NpgsqlMultiHostDataSource
, allowing you to spread queries over your read replicas, potentially improving throughput in read-heavy applications. To get started, your connection string should specify your primary host along a list of replicas, per Npgsql documentation.
Configuring NpgsqlMultiHostDataSource
is very similar to a normal data source, simply swapping it for AddMultiHostNpgsqlDataSource
. Marten will always use the primary node for queries with a NpgsqlMultiHostDataSource
unless you explicitly opt to use the standby nodes. You can adjust what type of node Marten uses for querying via the MultiHostSettings
store options:
services.AddMultiHostNpgsqlDataSource(ConnectionSource.ConnectionString);
services.AddMarten(x =>
{
// Will prefer standby nodes for querying.
x.Advanced.MultiHostSettings.ReadSessionPreference = TargetSessionAttributes.PreferStandby;
})
.UseLightweightSessions()
.UseNpgsqlDataSource();
WARNING
Marten will only use your read node preference with user queries (using IQuerySession) that are using a Marten-managed lifetime.
Internal queries, including the async daemon, will always use your primary node for reliability.
Ensure your replication delay is acceptable as you risk returning outdated queries.
Composite Configuration with ConfigureMarten()
The AddMarten()
mechanism assumes that you are expressing all of the Marten configuration in one place and "know" what that configuration is upfront. Consider these possibilities where that isn't necessarily possible or desirable:
- You want to override Marten configuration in integration testing scenarios (I do this quite commonly)
- Many users have expressed the desire to keep parts of Marten configuration in potentially separate assemblies or subsystems in such a way that they could later break up the current service into smaller services
Fear not, Marten V5.0 introduced a new way to add or modify the Marten configuration from AddMarten()
. Let's assume that we're building a system that has a subsystem related to users and want to segregate all the service registrations and Marten configuration related to users into a single place like this extension method:
public static IServiceCollection AddUserModule(this IServiceCollection services)
{
// This applies additional configuration to the main Marten DocumentStore
// that is configured elsewhere
services.ConfigureMarten(opts =>
{
opts.RegisterDocumentType<User>();
});
// Other service registrations specific to the User submodule
// within the bigger system
return services;
}
And next, let's put that into context with its usage inside your application's bootstrapping:
using var host = await Host.CreateDefaultBuilder()
.ConfigureServices(services =>
{
// The initial Marten configuration
services.AddMarten("some connection string");
// Other core service registrations
services.AddLogging();
// Add the User module
services.AddUserModule();
}).StartAsync();
The ConfigureMarten()
method is the interesting part of the code samples above. That is registering a small service that implements the IConfigureMarten
interface into the underlying IoC container:
/// <summary>
/// Mechanism to register additional Marten configuration that is applied after AddMarten()
/// configuration, but before DocumentStore is initialized
/// </summary>
public interface IConfigureMarten
{
void Configure(IServiceProvider services, StoreOptions options);
}
You could alternatively implement a custom IConfigureMarten
(or IConfigureMarten<T> where T : IDocumentStore
if you're working with multiple databases) class like so:
internal class UserMartenConfiguration: IConfigureMarten
{
public void Configure(IServiceProvider services, StoreOptions options)
{
options.RegisterDocumentType<User>();
// and any other additional Marten configuration
}
}
and registering it in your IoC container something like this:
public static IServiceCollection AddUserModule2(this IServiceCollection services)
{
// This applies additional configuration to the main Marten DocumentStore
// that is configured elsewhere
services.AddSingleton<IConfigureMarten, UserMartenConfiguration>();
// If you're using multiple databases per Host, register `IConfigureMarten<T>`, like this:
services.AddSingleton<IConfigureMarten<IInvoicingStore>, InvoicingStoreConfiguration>();
// Other service registrations specific to the User submodule
// within the bigger system
return services;
}
Using IoC Services for Configuring Marten 7.7
There is also a newer mechanism called IAsyncConfigureMarten
that was originally built to enable services like the Feature Management library from Microsoft to be used to selectively configure Marten using potentially asynchronous methods and IoC resolved services.
That interface signature is:
/// <summary>
/// Mechanism to register additional Marten configuration that is applied after AddMarten()
/// configuration, but before DocumentStore is initialized when you need to utilize some
/// kind of asynchronous services like Microsoft's FeatureManagement feature to configure Marten
/// </summary>
public interface IAsyncConfigureMarten
{
ValueTask Configure(StoreOptions options, CancellationToken cancellationToken);
}
As an example from the tests, here's a custom version that uses the Feature Management service:
public class FeatureManagementUsingExtension: IAsyncConfigureMarten
{
private readonly IFeatureManager _manager;
public FeatureManagementUsingExtension(IFeatureManager manager)
{
_manager = manager;
}
public async ValueTask Configure(StoreOptions options, CancellationToken cancellationToken)
{
if (await _manager.IsEnabledAsync("Module1"))
{
options.Events.MapEventType<Module1Event>("module1:event");
}
}
}
And lastly, these extensions can be registered directly against IServiceCollection
like so:
services.ConfigureMartenWithServices<FeatureManagementUsingExtension>();
Using Lightweight Sessions
TIP
Most usages of Marten should default to the lightweight sessions for better performance
The default registration for IDocumentSession
added by AddMarten()
is a session with identity map mechanics. That might be unnecessary overhead in most cases where the sessions are short-lived, but we keep this behavior for backward compatibility with early Marten and RavenDb behavior before that. To opt into using lightweight sessions without the identity map behavior, use this syntax:
var connectionString = Configuration.GetConnectionString("postgres");
services.AddMarten(opts =>
{
opts.Connection(connectionString);
})
// Chained helper to replace the built in
// session factory behavior
.UseLightweightSessions();
Customizing Session Creation Globally
By default, Marten will create a document session with the basic identity map enabled and a ReadCommitted transaction isolation level. If you want to use a different configuration for sessions globally in your application, you can use a custom implementation of the ISessionFactory
class as shown in this example:
public class CustomSessionFactory: ISessionFactory
{
private readonly IDocumentStore _store;
// This is important! You will need to use the
// IDocumentStore to open sessions
public CustomSessionFactory(IDocumentStore store)
{
_store = store;
}
public IQuerySession QuerySession()
{
return _store.QuerySession();
}
public IDocumentSession OpenSession()
{
// Opting for the "lightweight" session
// option with no identity map tracking
// and choosing to use Serializable transactions
// just to be different
return _store.LightweightSession(IsolationLevel.Serializable);
}
}
To register the custom session factory, use the BuildSessionsWith()
method as shown in this example:
var connectionString = Configuration.GetConnectionString("postgres");
services.AddMarten(opts =>
{
opts.Connection(connectionString);
})
// Using the "Optimized artifact workflow" for Marten >= V5
// sets up your Marten configuration based on your environment
// See https://martendb.io/configuration/optimized_artifact_workflow.html
.OptimizeArtifactWorkflow()
// Chained helper to replace the built in
// session factory behavior
.BuildSessionsWith<CustomSessionFactory>();
The session factories can also be used to build out and attach custom IDocumentSessionListener
objects or replace the logging as we'll see in the next section.
See diagnostics and instrumentation for more information.
Customizing Session Creation by Scope
From a recent user request to Marten, what if you want to log the database statement activity in Marten with some kind of correlation to the active HTTP request or service bus message or some other logical session identification in your application? That's now possible by using a custom ISessionFactory
.
Taking the example of an ASP.NET Core application, let's say that you have a small service scoped to an HTTP request that tracks a correlation identifier for the request like this:
public interface ISession
{
Guid CorrelationId { get; set; }
}
And a custom Marten session logger to add the correlation identifier to the log output like this:
public class CorrelatedMartenLogger: IMartenSessionLogger
{
private readonly ILogger<IDocumentSession> _logger;
private readonly ISession _session;
public CorrelatedMartenLogger(ILogger<IDocumentSession> logger, ISession session)
{
_logger = logger;
_session = session;
}
public void LogSuccess(NpgsqlCommand command)
{
// Do some kind of logging using the correlation id of the ISession
}
public void LogFailure(NpgsqlCommand command, Exception ex)
{
// Do some kind of logging using the correlation id of the ISession
}
public void LogSuccess(NpgsqlBatch batch)
{
// Do some kind of logging using the correlation id of the ISession
}
public void LogFailure(NpgsqlBatch batch, Exception ex)
{
// Do some kind of logging using the correlation id of the ISession
}
public void RecordSavedChanges(IDocumentSession session, IChangeSet commit)
{
// Do some kind of logging using the correlation id of the ISession
}
public void OnBeforeExecute(NpgsqlCommand command)
{
}
public void LogFailure(Exception ex, string message)
{
}
public void OnBeforeExecute(NpgsqlBatch batch)
{
}
}
Now, let's move on to building out a custom session factory that will attach our correlated marten logger to sessions being resolved from the IoC container:
public class ScopedSessionFactory: ISessionFactory
{
private readonly IDocumentStore _store;
private readonly ILogger<IDocumentSession> _logger;
private readonly ISession _session;
// This is important! You will need to use the
// IDocumentStore to open sessions
public ScopedSessionFactory(IDocumentStore store, ILogger<IDocumentSession> logger, ISession session)
{
_store = store;
_logger = logger;
_session = session;
}
public IQuerySession QuerySession()
{
return _store.QuerySession();
}
public IDocumentSession OpenSession()
{
var session = _store.LightweightSession();
// Replace the Marten session logger with our new
// correlated marten logger
session.Logger = new CorrelatedMartenLogger(_logger, _session);
return session;
}
}
Lastly, let's register our new session factory, but this time we need to take care to register the session factory as Scoped
in the underlying container so we're using the correct ISession
at runtime:
var connectionString = Configuration.GetConnectionString("postgres");
services.AddMarten(opts =>
{
opts.Connection(connectionString);
})
// Using the "Optimized artifact workflow" for Marten >= V5
// sets up your Marten configuration based on your environment
// See https://martendb.io/configuration/optimized_artifact_workflow.html
.OptimizeArtifactWorkflow()
// Chained helper to replace the CustomSessionFactory
.BuildSessionsWith<ScopedSessionFactory>(ServiceLifetime.Scoped);
TIP
This correlation tracking might be better with structural logging with something like Serilog, but we'll leave that to users.
Eager Initialization of the DocumentStore
Lastly, if desirable, you can force Marten to initialize the applications document store as part of bootstrapping instead of waiting for it to be initialized on the first usage with this syntax:
var connectionString = Configuration.GetConnectionString("postgres");
// By only the connection string
services.AddMarten(connectionString)
// Using the "Optimized artifact workflow" for Marten >= V5
// sets up your Marten configuration based on your environment
// See https://martendb.io/configuration/optimized_artifact_workflow.html
.OptimizeArtifactWorkflow()
// Spin up the DocumentStore right this second!
.InitializeWith();
Working with Multiple Marten Databases
TIP
This feature is not meant for multi-tenancy with separate databases. This is specifically meant for use cases where a single system needs to work with two or more semantically different Marten databases.
TIP
The database management tools in Marten.CommandLine are able to work with the separately registered document stores along with the default store from AddMarten()
.
Marten V5.0 introduces a new feature to register additional Marten databases into a .Net system. AddMarten()
continues to work as it has, but we can now register and resolve additional store services. To utilize the type system and your application's underlying IoC container, the first step is to create a custom marker interface for your separate document store like this one below targeting a separate "invoicing" database:
// These marker interfaces *must* be public
public interface IInvoicingStore : IDocumentStore
{
}
A couple notes on the interface:
- The custom interface has to be public and implement the
IDocumentStore
interface - Marten is quietly building a dynamic type for your additional store interface internally
And now to bootstrap that separate store in our system:
using var host = Host.CreateDefaultBuilder()
.ConfigureServices(services =>
{
// You can still use AddMarten() for the main document store
// of this application
services.AddMarten("some connection string");
services.AddMartenStore<IInvoicingStore>(opts =>
{
// All the normal options are available here
opts.Connection("different connection string");
// more configuration
})
// Optionally apply all database schema
// changes on startup
.ApplyAllDatabaseChangesOnStartup()
// Run the async daemon for this database
.AddAsyncDaemon(DaemonMode.HotCold)
// Use IInitialData
.InitializeWith(new DefaultDataSet())
// Use the V5 optimized artifact workflow
// with the separate store as well
.OptimizeArtifactWorkflow();
}).StartAsync();
At runtime we can inject an instance of our new IInvoicingStore
and work with it like any other Marten IDocumentStore
as shown below in an internal InvoicingService
:
public class InvoicingService
{
private readonly IInvoicingStore _store;
// IInvoicingStore can be injected like any other
// service in your IoC container
public InvoicingService(IInvoicingStore store)
{
_store = store;
}
public async Task DoSomethingWithInvoices()
{
// Important to dispose the session when you're done
// with it
await using var session = _store.LightweightSession();
// do stuff with the session you just opened
}
}