Fork me on GitHub

Multi-Tenancy with Basic Store Operations Edit on GitHub


Once configured for multi-tenancy, Marten exposes it via sessions (IQuerySession, IDocumentSession) scoped to specific tenants, as well as various overloads to saving operations that accept a tenant identifier.

Scoping Sessions to Tenancy

The following sample demonstrates scoping a document session to tenancy idenfitied as tenant1. With multi-tenancy enabled, the persisted User objects are then associated with the tenancy of the session.


// Write some User documents to tenant "tenant1"
using (var session = store.OpenSession("tenant1"))
{
    session.Store(new User { UserName = "Bill" });
    session.Store(new User { UserName = "Lindsey" });
    session.SaveChanges();
}

As with storing, the load operations respect tenancy of the session.


// When you query for data from the "tenant1" tenant,
// you only get data for that tenant
using (var query = store.QuerySession("tenant1"))
{
    query.Query<User>()
        .Select(x => x.UserName)
        .ToList()
        .ShouldHaveTheSameElementsAs("Bill", "Lindsey");
}

Lastly, unlike reading operations, IDocumentSession.Store offers an overload to explicitly pass in a tenant identifier, bypassing any tenancy associated with the session. Similar overload for tenancy exists for IDocumentStore.BulkInsert.

Default Tenancy

With multi-tenancy enabled, Marten associates each record with a tenancy. If no explicit tenancy is specified, either via policies, mappings, scoped sessions or overloads, Marten will default to Tenancy.DefaultTenantId with a constant value of *DEFAULT*.

The following sample demonstrates persisting documents as non-tenanted, under default tenant and other named tenants then querying them back in a session scoped to a specific named tenant and default tenant.


StoreOptions(_ =>
{
    _.Schema.For<Target>().MultiTenanted(); // tenanted
    _.Schema.For<User>(); // non-tenanted
    _.Schema.For<Issue>().MultiTenanted(); // tenanted
});

// Add documents to tenant Green
var greens = Target.GenerateRandomData(10).ToArray();
theStore.BulkInsert("Green", greens);

// Add documents to tenant Red
var reds = Target.GenerateRandomData(11).ToArray();
theStore.BulkInsert("Red", reds);

// Add non-tenanted documents
// User is non-tenanted in schema
var user1 = new User {UserName = "Frank"};
var user2 = new User {UserName = "Bill"};
theStore.BulkInsert(new User[]{user1, user2});

// Add documents to default tenant
// Note that schema for Issue is multi-tenanted hence documents will get added
// to default tenant if tenant is not passed in the bulk insert operation
var issue1 = new Issue { Title = "Test issue1" };
var issue2 = new Issue { Title = "Test issue2" };
theStore.BulkInsert(new Issue[] { issue1, issue2 });

// Create a session with tenant Green
using (var session = theStore.QuerySession("Green"))
{
    // Query tenanted document as the tenant passed in session
    session.Query<Target>().Count().ShouldBe(10);

    // Query non-tenanted documents
    session.Query<User>().Count().ShouldBe(2);

    // Query documents in default tenant from a session using tenant Green
    session.Query<Issue>().Count(x => x.TenantIsOneOf(Tenancy.DefaultTenantId)).ShouldBe(2);

    // Query documents from tenant Red from a session using tenant Green
    session.Query<Target>().Count(x => x.TenantIsOneOf("Red")).ShouldBe(11);
}

// create a session without passing any tenant, session will use default tenant
using (var session = theStore.QuerySession())
{
    // Query non-tenanted documents
    session.Query<User>().Count().ShouldBe(2);

    // Query documents in default tenant
    // Note that session is using default tenant
    session.Query<Issue>().Count().ShouldBe(2);

    // Query documents on tenant Green
    session.Query<Target>().Count(x => x.TenantIsOneOf("Green")).ShouldBe(10);

    // Query documents on tenant Red
    session.Query<Target>().Count(x => x.TenantIsOneOf("Red")).ShouldBe(11);
}