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:
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;
}
}
Now, to actually apply this feature to your Marten applications, use this syntax:
var store = DocumentStore.For(_ =>
{
// Creates a new instance of FakeStorage and
// passes along the current StoreOptions
_.Storage.Add<FakeStorage>();
// or
_.Storage.Add(new FakeStorage(_));
});
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:
- single schema configuration,
- multi-tenancy per database with tenants known upfront.
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:
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();
Function
Postgresql functions can be managed by creating a function using Weasel.Postgresql.Functions.Function
as below:
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();
Sequence
Postgresql sequences can be created using Weasel.Postgresql.Sequence
as below:
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();
Extension
Postgresql extensions can be enabled using Weasel.Postgresql.Extension
as below:
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();