Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions docs/configuration/hostbuilder.md
Original file line number Diff line number Diff line change
Expand Up @@ -711,3 +711,45 @@ public class InvoicingService
```
<sup><a href='https://github.com/JasperFx/marten/blob/master/src/CoreTests/Examples/MultipleDocumentStores.cs#L73-L96' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_invoicingservice' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

### Session Configuration for Ancillary Stores <Badge type="tip" text="8.x" />

Just like the main store configured with `AddMarten()`, ancillary stores support fluent session configuration
to control the default `ISessionFactory` used for dependency-injected sessions. The session factory is registered
as a [keyed service](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#keyed-services)
keyed by the store marker interface type (e.g., `typeof(IInvoicingStore)`).

<!-- snippet: sample_bootstrapping_separate_store_with_session_config -->
<a id='snippet-sample_bootstrapping_separate_store_with_session_config'></a>
```cs
using var host = Host.CreateDefaultBuilder()
.ConfigureServices(services =>
{
services.AddMarten("some connection string");

services.AddMartenStore<IInvoicingStore>(opts =>
{
opts.Connection("different connection string");
})
// Use lightweight sessions for this ancillary store
.UseLightweightSessions();

// Or use identity map sessions
// .UseIdentitySessions();

// Or use dirty-tracked sessions
// .UseDirtyTrackedSessions();

// Or use a custom session factory
// .BuildSessionsWith<CustomSessionFactory>();
}).StartAsync();
```
<sup><a href='https://github.com/JasperFx/marten/blob/master/src/CoreTests/Examples/MultipleDocumentStores.cs#L54-L78' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_bootstrapping_separate_store_with_session_config' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

You can resolve the keyed `ISessionFactory` for an ancillary store directly from the DI container if needed:

```csharp
var factory = serviceProvider.GetRequiredKeyedService<ISessionFactory>(typeof(IInvoicingStore));
using var session = factory.OpenSession();
```
29 changes: 29 additions & 0 deletions src/CoreTests/Examples/MultipleDocumentStores.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,35 @@ public static async Task bootstrap()

#endregion
}

public static async Task bootstrap_with_session_config()
{
#region sample_bootstrapping_separate_store_with_session_config

using var host = Host.CreateDefaultBuilder()
.ConfigureServices(services =>
{
services.AddMarten("some connection string");

services.AddMartenStore<IInvoicingStore>(opts =>
{
opts.Connection("different connection string");
})
// Use lightweight sessions for this ancillary store
.UseLightweightSessions();

// Or use identity map sessions
// .UseIdentitySessions();

// Or use dirty-tracked sessions
// .UseDirtyTrackedSessions();

// Or use a custom session factory
// .BuildSessionsWith<CustomSessionFactory>();
}).StartAsync();

#endregion
}
}

public class DefaultDataSet: IInitialData
Expand Down
99 changes: 99 additions & 0 deletions src/CoreTests/bootstrapping_with_service_collection_extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using JasperFx.Core;
using JasperFx.Core.Reflection;
using Lamar;
using CoreTests.Examples;
using Marten;
using Marten.Events;
using Marten.Internal.Sessions;
Expand Down Expand Up @@ -482,6 +483,104 @@ Action GetStore(IServiceProvider c) => () =>
exc.Message.Contains("UseNpgsqlDataSource").ShouldBeTrue();
}

[Fact]
public void ancillary_store_use_lightweight_sessions()
{
var services = new ServiceCollection();
services.AddMarten(ConnectionSource.ConnectionString);
services.AddMartenStore<IInvoicingStore>(opts =>
{
opts.Connection(ConnectionSource.ConnectionString);
opts.DatabaseSchemaName = "invoicing";
}).UseLightweightSessions();

var sp = services.BuildServiceProvider();
var factory = sp.GetRequiredKeyedService<ISessionFactory>(typeof(IInvoicingStore));
factory.ShouldBeOfType<LightweightSessionFactory>();

using var session = factory.OpenSession();
session.ShouldBeOfType<LightweightSession>();
}

[Fact]
public void ancillary_store_use_identity_sessions()
{
var services = new ServiceCollection();
services.AddMarten(ConnectionSource.ConnectionString);
services.AddMartenStore<IInvoicingStore>(opts =>
{
opts.Connection(ConnectionSource.ConnectionString);
opts.DatabaseSchemaName = "invoicing";
}).UseIdentitySessions();

var sp = services.BuildServiceProvider();
var factory = sp.GetRequiredKeyedService<ISessionFactory>(typeof(IInvoicingStore));
factory.ShouldBeOfType<IdentitySessionFactory>();

using var session = factory.OpenSession();
session.ShouldBeOfType<IdentityMapDocumentSession>();
}

[Fact]
public void ancillary_store_use_dirty_tracked_sessions()
{
var services = new ServiceCollection();
services.AddMarten(ConnectionSource.ConnectionString);
services.AddMartenStore<IInvoicingStore>(opts =>
{
opts.Connection(ConnectionSource.ConnectionString);
opts.DatabaseSchemaName = "invoicing";
}).UseDirtyTrackedSessions();

var sp = services.BuildServiceProvider();
var factory = sp.GetRequiredKeyedService<ISessionFactory>(typeof(IInvoicingStore));
factory.ShouldBeOfType<DirtyTrackedSessionFactory>();

using var session = factory.OpenSession();
session.ShouldBeOfType<DirtyCheckingDocumentSession>();
}

[Fact]
public void ancillary_store_build_sessions_with_custom_factory()
{
var services = new ServiceCollection();
services.AddMarten(ConnectionSource.ConnectionString);
services.AddMartenStore<IInvoicingStore>(opts =>
{
opts.Connection(ConnectionSource.ConnectionString);
opts.DatabaseSchemaName = "invoicing";
}).BuildSessionsWith<SpecialBuilder>();

var sp = services.BuildServiceProvider();
var factory = sp.GetRequiredKeyedService<ISessionFactory>(typeof(IInvoicingStore));
var builder = factory.ShouldBeOfType<SpecialBuilder>();

builder.BuiltQuery.ShouldBeFalse();
builder.BuiltSession.ShouldBeFalse();

using var session = factory.OpenSession();
builder.BuiltSession.ShouldBeTrue();

using var query = factory.QuerySession();
builder.BuiltQuery.ShouldBeTrue();
}

[Fact]
public void ancillary_store_default_session_factory()
{
var services = new ServiceCollection();
services.AddMarten(ConnectionSource.ConnectionString);
services.AddMartenStore<IInvoicingStore>(opts =>
{
opts.Connection(ConnectionSource.ConnectionString);
opts.DatabaseSchemaName = "invoicing";
});

var sp = services.BuildServiceProvider();
var factory = sp.GetRequiredKeyedService<ISessionFactory>(typeof(IInvoicingStore));
factory.ShouldBeOfType<DefaultSessionFactory>();
}

public class SpecialBuilder: ISessionFactory
{
private readonly IDocumentStore _store;
Expand Down
63 changes: 63 additions & 0 deletions src/Marten/MartenServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,13 @@ public static MartenStoreExpression<T> AddMartenStore<T>(this IServiceCollection

services.AddSingleton<T>(s => config.Build(s));

// Default keyed session factory for the ancillary store
services.AddKeyedSingleton<ISessionFactory>(typeof(T), (sp, _) =>
{
var store = (IDocumentStore)sp.GetRequiredService<T>();
var logger = sp.GetService<ILogger<DefaultSessionFactory>>() ?? new NullLogger<DefaultSessionFactory>();
return new DefaultSessionFactory(store, logger);
});

services.AddSingleton<IMasterTableMultiTenancy>(s => (IMasterTableMultiTenancy)s.GetRequiredService<T>());

Expand Down Expand Up @@ -602,6 +609,62 @@ public MartenStoreExpression<T> AddSubscriptionWithServices<TSubscription>(Servi

return this;
}

/// <summary>
/// Use an alternative strategy / configuration for opening IDocumentSession or IQuerySession
/// objects for this ancillary store with a custom ISessionFactory type registered as a keyed singleton
/// </summary>
/// <param name="lifetime">
/// IoC service lifetime for the session factory. Default is Singleton, but use Scoped if you need
/// to reference per-scope services
/// </param>
/// <typeparam name="TFactory">The custom session factory type</typeparam>
/// <returns></returns>
public MartenStoreExpression<T> BuildSessionsWith<TFactory>(ServiceLifetime lifetime = ServiceLifetime.Singleton)
where TFactory : class, ISessionFactory
{
var descriptor = new ServiceDescriptor(
typeof(ISessionFactory),
typeof(T),
(sp, _) =>
{
var store = sp.GetRequiredService<T>();
return ActivatorUtilities.CreateInstance<TFactory>(sp, (IDocumentStore)store);
},
lifetime);
Services.Add(descriptor);
return this;
}

/// <summary>
/// Use lightweight sessions by default for this ancillary store. Equivalent to
/// IDocumentStore.LightweightSession();
/// </summary>
/// <returns></returns>
public MartenStoreExpression<T> UseLightweightSessions()
{
return BuildSessionsWith<LightweightSessionFactory>();
}

/// <summary>
/// Use identity sessions by default for this ancillary store. Equivalent to
/// IDocumentStore.IdentitySession();
/// </summary>
/// <returns></returns>
public MartenStoreExpression<T> UseIdentitySessions()
{
return BuildSessionsWith<IdentitySessionFactory>();
}

/// <summary>
/// Use dirty-tracked sessions by default for this ancillary store. Equivalent to
/// IDocumentStore.DirtyTrackedSession();
/// </summary>
/// <returns></returns>
public MartenStoreExpression<T> UseDirtyTrackedSessions()
{
return BuildSessionsWith<DirtyTrackedSessionFactory>();
}
}

public class MartenConfigurationExpression
Expand Down
Loading