Skip to content
On this page

Schema Feature Extensions

New in Marten 5.4.0 is the ability to add additional features with custom database schema objects that simply plug into Marten's schema management facilities. The key abstraction is the IFeatureSchema interface from the Weasel.Core library.

Not to worry though, Marten comes with a base class that makes it a bit simpler to build out new features. Here's a very simple example that defines a custom table with one column:

cs
public class FakeStorage: FeatureSchemaBase
{
    private readonly StoreOptions _options;

    public FakeStorage(StoreOptions options): base("fake", options.Advanced.Migrator)
    {
        _options = options;
    }

    protected override IEnumerable<ISchemaObject> schemaObjects()
    {
        var table = new Table(new PostgresqlObjectName(_options.DatabaseSchemaName, "mt_fake_table"));
        table.AddColumn("name", "varchar");

        yield return table;
    }
}

snippet source | anchor

Now, to actually apply this feature to your Marten applications, use this syntax:

cs
var store = DocumentStore.For(_ =>
{
    // Creates a new instance of FakeStorage and
    // passes along the current StoreOptions
    _.Storage.Add<FakeStorage>();

    // or

    _.Storage.Add(new FakeStorage(_));
});

snippet source | anchor

Do note that when you use the Add<T>() syntax, Marten will pass along the current StoreOptions to the constructor function if there is a constructor with that signature. Otherwise, it uses the no-arg constructor.

While you can directly implement the ISchemaObject interface for something Marten doesn't already support. Marten provides an even easier extensibility mechanism to add custom database objects such as Postgres tables, functions and sequences using StorageFeatures.ExtendedSchemaObjects using Weasel.

WARNING

Marten will apply **Schema Feature Extensions ** automatically when you call ApplyAllConfiguredChangesToDatabaseAsync for:

But it won't apply them for multi-tenancy per database with **unknown ** tenants. If you cannot predict them, read the guidance on dynamically applying changes to tenants databases.

Table

Postgresql tables can be modeled with the Table class from Weasel.Postgresql.Tables as shown in this example below:

cs
StoreOptions(opts =>
{
    opts.RegisterDocumentType<Target>();

    var table = new Table("adding_custom_schema_objects.names");
    table.AddColumn<string>("name").AsPrimaryKey();

    opts.Storage.ExtendedSchemaObjects.Add(table);
});

await theStore.Storage.ApplyAllConfiguredChangesToDatabaseAsync();

snippet source | anchor

Function

Postgresql functions can be managed by creating a function using Weasel.Postgresql.Functions.Function as below:

cs
StoreOptions(opts =>
{
    opts.RegisterDocumentType<Target>();

    // Create a user defined function to act as a ternary operator similar to SQL Server
    var function = new Function(new PostgresqlObjectName("public", "iif"), @"
create or replace function iif(
condition boolean,       -- if condition
true_result anyelement,  -- then
false_result anyelement  -- else
) returns anyelement as $f$
select case when condition then true_result else false_result end
$f$  language sql immutable;
");

    opts.Storage.ExtendedSchemaObjects.Add(function);
});

await theStore.Storage.ApplyAllConfiguredChangesToDatabaseAsync();

snippet source | anchor

Sequence

Postgresql sequences can be created using Weasel.Postgresql.Sequence as below:

cs
StoreOptions(opts =>
{
    opts.RegisterDocumentType<Target>();

    // Create a sequence to generate unique ids for documents
    var sequence = new Sequence("banana_seq");

    opts.Storage.ExtendedSchemaObjects.Add(sequence);
});

await theStore.Storage.ApplyAllConfiguredChangesToDatabaseAsync();

snippet source | anchor

Extension

Postgresql extensions can be enabled using Weasel.Postgresql.Extension as below:

cs
StoreOptions(opts =>
{
    opts.RegisterDocumentType<Target>();

    // Unaccent is an extension ships with postgresql
    // and removes accents (diacritic signs) from strings
    var extension = new Extension("unaccent");

    opts.Storage.ExtendedSchemaObjects.Add(extension);
});

await theStore.Storage.ApplyAllConfiguredChangesToDatabaseAsync();

snippet source | anchor

Released under the MIT License.