Resource Migrations
Resource-based migrations load and execute embedded resource files instead of inline C# code.
Overview
- Resource migrations use embedded files (SQL, JSON, N1QL, AQL) instead of inline C# code
- Changes are declared in resource files, making them reviewable and diffable
- Each provider has a
ResourceRunner<TMigration>that loads and executes resources - Resource files are embedded in the assembly at build time
How It Works
- Create resource files in a
Resources/{version}-{ClassName}/folder - Add them as
EmbeddedResourcein the .csproj - Inject the provider’s resource runner into your migration
- Call
StatementsFromAsyncorDocumentsFromAsync(orSqlFromAsync/AllSqlFromAsyncfor Postgres)
Folder Convention
The resource runner resolves files relative to Resources/{version}-{ClassName}/:
MyProject/
Migrations/
1000-CreateInitialSchema.cs
2000-AddSecondaryIndexes.cs
Resources/
1000-CreateInitialSchema/
statements.json <-- statements to execute
sample/users/ <-- documents to seed
user1.json
user2.json
2000-AddSecondaryIndexes/
statements.json
Embedding Resources in .csproj
<ItemGroup>
<None Remove="Resources\1000-CreateInitialSchema\statements.json" />
<None Remove="Resources\1000-CreateInitialSchema\sample\users\user1.json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\1000-CreateInitialSchema\statements.json" />
<EmbeddedResource Include="Resources\1000-CreateInitialSchema\sample\users\user1.json" />
</ItemGroup>
Statements (StatementsFromAsync)
Execute database-specific statements from a JSON file.
JSON Format:
{
"statements": [
{ "statement": "CREATE INDEX IF NOT EXISTS idx_users_email ON sample.users (email)" },
{ "statement": "CREATE INDEX IF NOT EXISTS idx_users_active ON sample.users (active)" }
]
}
The statement syntax is provider-specific:
- Aerospike: AQL syntax –
CREATE INDEX WAIT idx_users_email ON test.users (email) STRING - Couchbase: N1QL syntax –
CREATE INDEX idx_users_email ON sample(email) USING GSI - MongoDB: JavaScript syntax –
db.getSiblingDB('sample').users.createIndex({ email: 1 }, { name: 'idx_users_email' }) - PostgreSQL: Uses
SqlFromAsync/AllSqlFromAsyncwith plain.sqlfiles instead of JSON
Documents (DocumentsFromAsync)
Seed data by loading JSON documents into the database. The folder path beneath the migration resource folder maps to the target location in the database:
Resources/<migration>/<database>/<container>/<document>.json
Each provider interprets the path segments according to its own terminology:
| Segment | Aerospike | Couchbase | MongoDB |
|---|---|---|---|
<database> | namespace | bucket | database |
<container> | set | scope | collection |
<document> | record key | document key | document |
Aerospike:
Resources/1000-CreateInitialSchema/test/users/user1.json
| | |
| | +-- record key
| +-- set
+-- namespace
Couchbase:
Resources/1000-CreateInitialSchema/sample/_default/ccuser001.json
| | |
| | +-- document key
| +-- scope
+-- bucket
MongoDB:
Resources/1000-CreateInitialSchema/sample/users/user1.json
| | |
| | +-- document
| +-- collection
+-- database
PostgreSQL Resource Migrations
PostgreSQL uses plain SQL files instead of JSON statements:
[Migration(1000)]
public class CreateInitialSchema(PostgresResourceRunner<CreateInitialSchema> resourceRunner) : Migration
{
public override async Task UpAsync(CancellationToken cancellationToken = default)
{
await resourceRunner.AllSqlFromAsync(cancellationToken);
}
}
Methods:
AllSqlFromAsync()– executes ALL .sql files in the migration’s resource folderSqlFromAsync(["CreateSchema.sql"])– executes specific named SQL files
Example: Complete Resource Migration
[Migration(1000)]
public class CreateInitialSchema(AerospikeResourceRunner<CreateInitialSchema> resourceRunner) : Migration
{
public override async Task UpAsync(CancellationToken cancellationToken = default)
{
// execute AQL statements (create indexes)
await resourceRunner.StatementsFromAsync(["statements.json"], cancellationToken);
// seed documents into test.users set
await resourceRunner.DocumentsFromAsync(["test/users"], cancellationToken);
}
}
When to Use Resource vs Code Migrations
| Consideration | Resource | Code |
|---|---|---|
| Schema/DDL changes | Preferred – declarative, reviewable | Works but verbose |
| Data seeding | Good for static seed data | Better for computed/dynamic data |
| Complex logic | Not suitable | Required |
| Database client access | Not available | Full DI access |
| Reviewability | SQL/JSON files are easy to diff | C# code requires more context |