**1.4.0**: Multi-schema support and table prefixing

This commit is contained in:
Tudor Stanciu 2025-07-30 01:00:35 +03:00
parent 28f1bdbd2e
commit aad1b9c39f
18 changed files with 551 additions and 55 deletions

View File

@ -1,6 +1,6 @@
namespace Netmash.Infrastructure.DatabaseMigration.Constants namespace Netmash.Infrastructure.DatabaseMigration.Constants
{ {
public enum MetadataLocation public enum MetadataStorage
{ {
XmlFile, XmlFile,
Database Database

View File

@ -1,13 +1,17 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Netmash.Infrastructure.DatabaseMigration.Entities; using Netmash.Infrastructure.DatabaseMigration.Entities;
using Netmash.Infrastructure.DatabaseMigration.Entities.Configurations; using Netmash.Infrastructure.DatabaseMigration.Entities.Configurations;
using Netmash.Infrastructure.DatabaseMigration.Services;
namespace Netmash.Infrastructure.DatabaseMigration.DbContexts namespace Netmash.Infrastructure.DatabaseMigration.DbContexts
{ {
internal class MigrationDbContext : DbContext internal class MigrationDbContext : DbContext
{ {
public MigrationDbContext(DbContextOptions<MigrationDbContext> options) : base(options) private readonly TableNamingService _tableNamingService;
public MigrationDbContext(DbContextOptions<MigrationDbContext> options, TableNamingService tableNamingService) : base(options)
{ {
_tableNamingService = tableNamingService;
} }
public DbSet<MigrationSignature> MigrationSignatures { get; set; } public DbSet<MigrationSignature> MigrationSignatures { get; set; }
@ -16,9 +20,9 @@ namespace Netmash.Infrastructure.DatabaseMigration.DbContexts
{ {
base.OnModelCreating(modelBuilder); base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new MigratedScriptConfiguration()); modelBuilder.ApplyConfiguration(new MigratedScriptConfiguration(_tableNamingService));
modelBuilder.ApplyConfiguration(new MigratedVersionConfiguration()); modelBuilder.ApplyConfiguration(new MigratedVersionConfiguration(_tableNamingService));
modelBuilder.ApplyConfiguration(new MigrationSignatureConfiguration()); modelBuilder.ApplyConfiguration(new MigrationSignatureConfiguration(_tableNamingService));
} }
} }
} }

View File

@ -9,6 +9,7 @@ using Netmash.Infrastructure.DatabaseMigration.Repositories;
using Netmash.Infrastructure.DatabaseMigration.Services; using Netmash.Infrastructure.DatabaseMigration.Services;
using Netmash.Infrastructure.DatabaseMigration.Services.Abstractions; using Netmash.Infrastructure.DatabaseMigration.Services.Abstractions;
using System; using System;
using System.Text.RegularExpressions;
namespace Netmash.Infrastructure.DatabaseMigration namespace Netmash.Infrastructure.DatabaseMigration
{ {
@ -18,13 +19,18 @@ namespace Netmash.Infrastructure.DatabaseMigration
public static void AddMigration(this IServiceCollection services, public static void AddMigration(this IServiceCollection services,
DatabaseType databaseType = DatabaseType.SQLite, DatabaseType databaseType = DatabaseType.SQLite,
MetadataLocation metadataLocation = MetadataLocation.XmlFile, MetadataStorage metadataStorage = MetadataStorage.XmlFile,
string connectionName = "DatabaseConnection", string connectionName = "DatabaseConnection",
string workspace = "Workspace", string workspace = "Workspace",
string scriptsDirectoryPath = "Scripts") string scriptsDirectoryPath = "Scripts",
string metadataSchema = "migration",
string metadataPrefix = null)
{ {
var serviceConfiguration = new ServiceConfiguration(databaseType, metadataLocation, connectionName, workspace, scriptsDirectoryPath); ValidateConfiguration(databaseType, metadataStorage, metadataSchema, metadataPrefix);
var serviceConfiguration = new ServiceConfiguration(databaseType, metadataStorage, connectionName, workspace, scriptsDirectoryPath, metadataSchema, metadataPrefix);
services.AddSingleton(serviceConfiguration); services.AddSingleton(serviceConfiguration);
services.AddSingleton<TableNamingService>();
services.AddDataAccess(serviceConfiguration); services.AddDataAccess(serviceConfiguration);
services.AddSingleton<IMetadataLocationService, MetadataLocationService>(); services.AddSingleton<IMetadataLocationService, MetadataLocationService>();
services.AddSingleton<IMigrationSignaturesService, MigrationSignaturesService>(); services.AddSingleton<IMigrationSignaturesService, MigrationSignaturesService>();
@ -72,5 +78,38 @@ namespace Netmash.Infrastructure.DatabaseMigration
var migrationService = app.ApplicationServices.GetService<IMigrationService>(); var migrationService = app.ApplicationServices.GetService<IMigrationService>();
migrationService.Execute(); migrationService.Execute();
} }
private static void ValidateConfiguration(DatabaseType databaseType, MetadataStorage metadataStorage, string metadataSchema, string metadataPrefix)
{
// Validate metadataPrefix characters
if (!string.IsNullOrEmpty(metadataPrefix))
{
if (!Regex.IsMatch(metadataPrefix, @"^[a-zA-Z0-9_]+$"))
{
throw new ArgumentException("MetadataPrefix can only contain letters, numbers, and underscores.", nameof(metadataPrefix));
}
}
// SQLite doesn't support multiple schemas
if (databaseType == DatabaseType.SQLite && metadataStorage == MetadataStorage.Database)
{
if (!string.IsNullOrEmpty(metadataSchema) && metadataSchema != "migration")
{
throw new NotSupportedException("SQLite provider does not support multiple schemas. To differentiate migration tables between multiple services, use the metadataPrefix parameter instead.");
}
}
// Schema can only be used with SQL Server and Database storage
if (metadataStorage == MetadataStorage.XmlFile && !string.IsNullOrEmpty(metadataSchema) && metadataSchema != "migration")
{
throw new NotSupportedException("MetadataSchema can only be used when metadataStorage is Database. For XML file storage, use metadataPrefix to differentiate between services.");
}
// Schema should only be used with SQL Server
if (databaseType != DatabaseType.SQLServer && !string.IsNullOrEmpty(metadataSchema) && metadataSchema != "migration")
{
throw new NotSupportedException("MetadataSchema can only be used with SQL Server database type.");
}
}
} }
} }

View File

@ -1,13 +1,24 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Netmash.Infrastructure.DatabaseMigration.Services;
namespace Netmash.Infrastructure.DatabaseMigration.Entities.Configurations namespace Netmash.Infrastructure.DatabaseMigration.Entities.Configurations
{ {
internal class MigratedScriptConfiguration : IEntityTypeConfiguration<MigratedScript> internal class MigratedScriptConfiguration : IEntityTypeConfiguration<MigratedScript>
{ {
private readonly TableNamingService _tableNamingService;
public MigratedScriptConfiguration(TableNamingService tableNamingService)
{
_tableNamingService = tableNamingService;
}
public void Configure(EntityTypeBuilder<MigratedScript> builder) public void Configure(EntityTypeBuilder<MigratedScript> builder)
{ {
builder.ToTable("MigratedScript", "migration").HasKey(z => z.Id); var tableName = _tableNamingService.GetTableName("MigratedScript");
var schemaName = _tableNamingService.GetSchemaName();
builder.ToTable(tableName, schemaName).HasKey(z => z.Id);
builder.Property(z => z.Id).ValueGeneratedOnAdd(); builder.Property(z => z.Id).ValueGeneratedOnAdd();
} }
} }

View File

@ -1,13 +1,24 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Netmash.Infrastructure.DatabaseMigration.Services;
namespace Netmash.Infrastructure.DatabaseMigration.Entities.Configurations namespace Netmash.Infrastructure.DatabaseMigration.Entities.Configurations
{ {
internal class MigratedVersionConfiguration : IEntityTypeConfiguration<MigratedVersion> internal class MigratedVersionConfiguration : IEntityTypeConfiguration<MigratedVersion>
{ {
private readonly TableNamingService _tableNamingService;
public MigratedVersionConfiguration(TableNamingService tableNamingService)
{
_tableNamingService = tableNamingService;
}
public void Configure(EntityTypeBuilder<MigratedVersion> builder) public void Configure(EntityTypeBuilder<MigratedVersion> builder)
{ {
builder.ToTable("MigratedVersion", "migration").HasKey(z => z.Id); var tableName = _tableNamingService.GetTableName("MigratedVersion");
var schemaName = _tableNamingService.GetSchemaName();
builder.ToTable(tableName, schemaName).HasKey(z => z.Id);
builder.Property(z => z.Id).ValueGeneratedOnAdd(); builder.Property(z => z.Id).ValueGeneratedOnAdd();
builder.HasMany(z => z.Scripts).WithOne().HasForeignKey(z => z.VersionId); builder.HasMany(z => z.Scripts).WithOne().HasForeignKey(z => z.VersionId);
} }

View File

@ -1,13 +1,24 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Netmash.Infrastructure.DatabaseMigration.Services;
namespace Netmash.Infrastructure.DatabaseMigration.Entities.Configurations namespace Netmash.Infrastructure.DatabaseMigration.Entities.Configurations
{ {
internal class MigrationSignatureConfiguration : IEntityTypeConfiguration<MigrationSignature> internal class MigrationSignatureConfiguration : IEntityTypeConfiguration<MigrationSignature>
{ {
private readonly TableNamingService _tableNamingService;
public MigrationSignatureConfiguration(TableNamingService tableNamingService)
{
_tableNamingService = tableNamingService;
}
public void Configure(EntityTypeBuilder<MigrationSignature> builder) public void Configure(EntityTypeBuilder<MigrationSignature> builder)
{ {
builder.ToTable("MigrationSignature", "migration").HasKey(z => z.Id); var tableName = _tableNamingService.GetTableName("MigrationSignature");
var schemaName = _tableNamingService.GetSchemaName();
builder.ToTable(tableName, schemaName).HasKey(z => z.Id);
builder.Property(z => z.Id).ValueGeneratedOnAdd(); builder.Property(z => z.Id).ValueGeneratedOnAdd();
builder.HasMany(z => z.MigratedVersions).WithOne().HasForeignKey(z => z.SignatureId); builder.HasMany(z => z.MigratedVersions).WithOne().HasForeignKey(z => z.SignatureId);
} }

View File

@ -5,18 +5,22 @@ namespace Netmash.Infrastructure.DatabaseMigration.Models
internal class ServiceConfiguration internal class ServiceConfiguration
{ {
public DatabaseType DatabaseType { get; } public DatabaseType DatabaseType { get; }
public MetadataLocation MetadataLocation { get; } public MetadataStorage MetadataStorage { get; }
public string ConnectionName { get; } public string ConnectionName { get; }
public string Workspace { get; } public string Workspace { get; }
public string ScriptsDirectory { get; } public string ScriptsDirectory { get; }
public string MetadataSchema { get; }
public string MetadataPrefix { get; }
public ServiceConfiguration(DatabaseType databaseType, MetadataLocation metadataLocation, string connectionName, string workspace, string scriptsDirectory) public ServiceConfiguration(DatabaseType databaseType, MetadataStorage metadataStorage, string connectionName, string workspace, string scriptsDirectory, string metadataSchema, string metadataPrefix)
{ {
DatabaseType = databaseType; DatabaseType = databaseType;
MetadataLocation = metadataLocation; MetadataStorage = metadataStorage;
ConnectionName = connectionName; ConnectionName = connectionName;
Workspace = workspace; Workspace = workspace;
ScriptsDirectory = scriptsDirectory; ScriptsDirectory = scriptsDirectory;
MetadataSchema = metadataSchema;
MetadataPrefix = metadataPrefix;
} }
} }
} }

View File

@ -7,10 +7,12 @@
<RepositoryUrl>https://lab.code-rove.com/gitea/bricks/netmash</RepositoryUrl> <RepositoryUrl>https://lab.code-rove.com/gitea/bricks/netmash</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<PackageTags>Netmash database migration</PackageTags> <PackageTags>Netmash database migration</PackageTags>
<Version>1.3.0</Version> <Version>1.4.0</Version>
<PackageReleaseNotes>1.1.0: From this version the migration service can keep its metadata in the database (SqlServer or Sqlite) <PackageReleaseNotes>1.1.0: From this version the migration service can keep its metadata in the database (SqlServer or Sqlite)
1.2.0: .NET 6 upgrade 1.2.0: .NET 6 upgrade
1.3.0: .NET 8 upgrade</PackageReleaseNotes> 1.3.0: .NET 8 upgrade
1.4.0: Multi-schema support, table prefixing</PackageReleaseNotes>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -38,4 +40,11 @@
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Update="README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,356 @@
# Netmash.Infrastructure.DatabaseMigration
A comprehensive database migration library for .NET applications that supports multiple database providers and flexible metadata storage options.
## Features
- **Multi-Database Support**: SQL Server and SQLite
- **Flexible Metadata Storage**: XML files or database tables
- **Multi-Schema Support**: Separate migration metadata for multiple services
- **Version-Based Migrations**: Organized script execution based on semantic versioning
- **Table Prefixing**: Support for prefixed table names to avoid conflicts
- **Automatic Schema Creation**: Creates migration tables and schemas automatically
- **Logging Integration**: Comprehensive logging of migration activities
- **ASP.NET Core Integration**: Easy dependency injection setup
## Installation
Add the package to your project:
```bash
dotnet add package Netmash.Infrastructure.DatabaseMigration
```
## Quick Start
### 1. Basic Setup
```csharp
// In Startup.cs or Program.cs
services.AddMigration();
// In Configure method or app startup
app.UseMigration();
```
### 2. Configuration
Add connection string to `appsettings.json`:
```json
{
"ConnectionStrings": {
"DatabaseConnection": "Server=localhost;Database=MyApp;Trusted_Connection=true;"
}
}
```
### 3. Organize Migration Scripts
Create directory structure:
```
Scripts/
├── 1.0.0/
│ ├── 01.CreateUserTable.sql
│ └── 02.CreateIndexes.sql
├── 1.0.1/
│ ├── 01.AddUserRoles.sql
│ └── 02.UpdateUserData.sql
└── 1.1.0/
└── 01.AddAuditFields.sql
```
## Configuration Options
### AddMigration Parameters
| Parameter | Type | Default | Description |
| ---------------------- | ----------------- | ---------------------- | ------------------------------------------------------ |
| `databaseType` | `DatabaseType` | `SQLite` | Database provider (SQLite, SQLServer) |
| `metadataStorage` | `MetadataStorage` | `XmlFile` | Where to store migration metadata |
| `connectionName` | `string` | `"DatabaseConnection"` | Connection string name in configuration |
| `workspace` | `string` | `"Workspace"` | Working directory for XML files |
| `scriptsDirectoryPath` | `string` | `"Scripts"` | Directory containing migration scripts |
| `metadataSchema` | `string` | `"migration"` | Database schema for migration tables (SQL Server only) |
| `metadataPrefix` | `string` | `null` | Prefix for table names or XML file names |
## Usage Examples
### SQL Server with Database Metadata Storage
```csharp
services.AddMigration(
databaseType: DatabaseType.SQLServer,
metadataStorage: MetadataStorage.Database,
connectionName: "DefaultConnection",
scriptsDirectoryPath: "Database/Migrations"
);
```
### Multi-Service Setup with Schema Separation
```csharp
// Service 1
services.AddMigration(
databaseType: DatabaseType.SQLServer,
metadataStorage: MetadataStorage.Database,
metadataSchema: "service1_migrations"
);
// Service 2
services.AddMigration(
databaseType: DatabaseType.SQLServer,
metadataStorage: MetadataStorage.Database,
metadataSchema: "service2_migrations"
);
```
### SQLite with Table Prefixing
```csharp
services.AddMigration(
databaseType: DatabaseType.SQLite,
metadataStorage: MetadataStorage.Database,
metadataPrefix: "myservice_"
);
// Creates tables: myservice_MigrationSignature, myservice_MigratedVersion, myservice_MigratedScript
```
### XML File Storage with Prefixing
```csharp
services.AddMigration(
metadataStorage: MetadataStorage.XmlFile,
metadataPrefix: "userservice_",
workspace: "C:\\MyApp\\Data"
);
// Creates file: C:\MyApp\Data\userservice_MigrationSignatures.xml
```
## Database Schema Creation
### SQL Server
The library automatically creates:
- Schema (if specified): `CREATE SCHEMA [your_schema]`
- Migration tables in the specified schema
- Proper foreign key relationships
### SQLite
The library automatically creates:
- Migration tables (with optional prefix)
- Proper foreign key relationships
## Migration Script Organization
Scripts are organized by version directories and executed in alphabetical order:
```
Scripts/
├── 1.0.0/ # Version 1.0.0 scripts
│ ├── 01.Users.sql
│ └── 02.Roles.sql
├── 1.0.1/ # Version 1.0.1 scripts
│ └── 01.Permissions.sql
└── 2.0.0/ # Version 2.0.0 scripts
├── 01.RefactorTables.sql
└── 02.MigrateData.sql
```
## Connection String Workspace Placeholder
Use `{Workspace}` placeholder in connection strings for dynamic paths:
```json
{
"ConnectionStrings": {
"DatabaseConnection": "Data Source={Workspace}\\myapp.db"
}
}
```
## Metadata Storage Options
### XML File Storage
- **Pros**: Simple, file-based, easy to backup
- **Cons**: Not suitable for distributed deployments
- **Use Case**: Single-instance applications, development environments
### Database Storage
- **Pros**: Centralized, supports distributed deployments, transactional
- **Cons**: Requires database access, slightly more complex
- **Use Case**: Production environments, multiple service instances
## Validation Rules
The library enforces several validation rules:
1. **Prefix Validation**: `metadataPrefix` can only contain letters, numbers, and underscores
2. **SQLite Schema Limitation**: SQLite doesn't support custom schemas - use `metadataPrefix` instead
3. **XML File Schema Limitation**: Custom schemas only work with database storage
4. **Provider Compatibility**: Custom schemas only supported with SQL Server
## Migration Tables Schema
### MigrationSignature
- `Id` (int, primary key)
- `MigrationDate` (datetime)
- `MachineName` (varchar)
- `LastVersion` (varchar)
### MigratedVersion
- `Id` (int, primary key)
- `SignatureId` (int, foreign key)
- `Version` (varchar)
### MigratedScript
- `Id` (int, primary key)
- `VersionId` (int, foreign key)
- `Script` (varchar)
## Logging
The library provides comprehensive logging:
```csharp
// Configuration logging
[INFO] Database migration configured with DatabaseType: SQLServer, MetadataStorage: Database
[INFO] Using database schema: microservice1
[INFO] Using metadata prefix: ms1_
// Migration execution logging
[INFO] Starting migration...
[INFO] Running script pack '1.0.1' with 3 scripts:
[INFO] Running sql script: 'Scripts\1.0.1\01.CreateTable.sql'
```
## Error Handling
Common exceptions and solutions:
### Invalid Prefix
```
ArgumentException: MetadataPrefix can only contain letters, numbers, and underscores.
```
**Solution**: Use only alphanumeric characters and underscores in prefix.
### SQLite Schema Error
```
NotSupportedException: SQLite provider does not support multiple schemas.
```
**Solution**: Use `metadataPrefix` instead of `metadataSchema` with SQLite.
### XML Schema Error
```
NotSupportedException: MetadataSchema can only be used when metadataStorage is Database.
```
**Solution**: Use database storage or remove custom schema parameter.
## Advanced Configuration
### Custom Workspace with Dynamic Connection String
```csharp
// appsettings.json
{
"ConnectionStrings": {
"DatabaseConnection": "Data Source={Workspace}\\myapp.db"
}
}
// Startup
services.AddMigration(
databaseType: DatabaseType.SQLite,
workspace: "C:\\MyAppData"
);
// Results in: Data Source=C:\MyAppData\myapp.db
```
### Multiple Services Sharing Database
```csharp
// User Service
services.AddMigration(
databaseType: DatabaseType.SQLServer,
metadataStorage: MetadataStorage.Database,
metadataSchema: "user_migrations",
scriptsDirectoryPath: "UserService/Migrations"
);
// Order Service
services.AddMigration(
databaseType: DatabaseType.SQLServer,
metadataStorage: MetadataStorage.Database,
metadataSchema: "order_migrations",
scriptsDirectoryPath: "OrderService/Migrations"
);
```
## Best Practices
1. **Version Naming**: Use semantic versioning (1.0.0, 1.0.1, 1.1.0)
2. **Script Naming**: Use numbered prefixes (01.CreateTable.sql, 02.AddIndex.sql)
3. **Idempotent Scripts**: Write scripts that can be run multiple times safely
4. **Backup Strategy**: Always backup databases before running migrations
5. **Testing**: Test migrations on development/staging environments first
6. **Rollback Planning**: Plan rollback strategies for complex migrations
## Migration Process Flow
1. **Initialization**: Check and create migration metadata storage
2. **Version Detection**: Determine last applied migration version
3. **Script Discovery**: Find unapplied script versions
4. **Execution**: Run scripts in version and alphabetical order
5. **Tracking**: Record successful migrations in metadata
6. **Logging**: Log all activities and results
## Troubleshooting
### Migration Not Running
- Verify connection string is correct
- Check that Scripts directory exists and contains version folders
- Ensure proper permissions on database/file system
### Metadata Table Missing
- Verify database connection
- Check that the application has database creation permissions
- Review logs for schema creation errors
### Version Skipping
- Ensure version folders follow semantic versioning
- Check for missing or incorrectly named version directories
- Verify script execution order and dependencies
## License
This project is part of the Netmash framework. See the main repository for license information.
## Contributing
Contributions are welcome! Please follow the established patterns and include appropriate tests.
## Version History
- **1.4.0**: Multi-schema support, table prefixing
- **1.3.0**: .NET 8 upgrade
- **1.2.0**: .NET 6 upgrade
- **1.1.0**: Database metadata storage support
- **1.0.0**: Initial release with XML file storage

View File

@ -2,6 +2,7 @@
using Netmash.Infrastructure.DatabaseMigration.Constants; using Netmash.Infrastructure.DatabaseMigration.Constants;
using Netmash.Infrastructure.DatabaseMigration.DbContexts; using Netmash.Infrastructure.DatabaseMigration.DbContexts;
using Netmash.Infrastructure.DatabaseMigration.Entities; using Netmash.Infrastructure.DatabaseMigration.Entities;
using Netmash.Infrastructure.DatabaseMigration.Services;
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -11,10 +12,12 @@ namespace Netmash.Infrastructure.DatabaseMigration.Repositories
internal class MigrationRepository : IMigrationRepository internal class MigrationRepository : IMigrationRepository
{ {
private readonly MigrationDbContext _dbContext; private readonly MigrationDbContext _dbContext;
private readonly TableNamingService _tableNamingService;
public MigrationRepository(MigrationDbContext dbContext) public MigrationRepository(MigrationDbContext dbContext, TableNamingService tableNamingService)
{ {
_dbContext = dbContext; _dbContext = dbContext;
_tableNamingService = tableNamingService;
} }
public async Task ExecuteSqlRaw(string sqlRaw) public async Task ExecuteSqlRaw(string sqlRaw)
@ -24,10 +27,13 @@ namespace Netmash.Infrastructure.DatabaseMigration.Repositories
public async Task<bool> MigrationTablesAreSet(DatabaseType databaseType) public async Task<bool> MigrationTablesAreSet(DatabaseType databaseType)
{ {
var tableName = _tableNamingService.GetTableName("MigrationSignature");
var schemaName = _tableNamingService.GetSchemaName();
var query = databaseType switch var query = databaseType switch
{ {
DatabaseType.SQLServer => "select count(1) from sys.objects where name = 'MigrationSignature' and type = 'U' and SCHEMA_NAME(schema_id)='migration'", DatabaseType.SQLServer => $"select count(1) from sys.objects where name = '{tableName}' and type = 'U' and SCHEMA_NAME(schema_id)='{schemaName}'",
DatabaseType.SQLite => "select count(1) from sqlite_master where type='table' and name='MigrationSignature';", DatabaseType.SQLite => $"select count(1) from sqlite_master where type='table' and name='{tableName}';",
_ => throw new NotImplementedException($"DatabaseMigration type {databaseType} is not implemented"), _ => throw new NotImplementedException($"DatabaseMigration type {databaseType} is not implemented"),
}; };

View File

@ -1,4 +1,4 @@
if not exists (select top 1 1 from sys.schemas where name = 'migration') if not exists (select top 1 1 from sys.schemas where name = '{MetadataSchema}')
begin begin
EXEC ('CREATE SCHEMA [migration] AUTHORIZATION [dbo]') EXEC ('CREATE SCHEMA [{MetadataSchema}] AUTHORIZATION [dbo]')
end end

View File

@ -1,30 +1,30 @@
if not exists (select top 1 1 from sys.objects where name = 'MigrationSignature' and type = 'U' and SCHEMA_NAME(schema_id) = 'migration') if not exists (select top 1 1 from sys.objects where name = '{TablePrefix}MigrationSignature' and type = 'U' and SCHEMA_NAME(schema_id) = '{MetadataSchema}')
begin begin
create table migration.MigrationSignature create table {MetadataSchema}.{TablePrefix}MigrationSignature
( (
Id int identity(1, 1) constraint PK_MigrationSignature primary key, Id int identity(1, 1) constraint PK_{TablePrefix}MigrationSignature primary key,
MigrationDate Datetime not null, MigrationDate Datetime not null,
MachineName varchar(30) not null, MachineName varchar(30) not null,
LastVersion varchar(20) not null LastVersion varchar(20) not null
) )
end end
if not exists (select top 1 1 from sys.objects where name = 'MigratedVersion' and type = 'U' and SCHEMA_NAME(schema_id) = 'migration') if not exists (select top 1 1 from sys.objects where name = '{TablePrefix}MigratedVersion' and type = 'U' and SCHEMA_NAME(schema_id) = '{MetadataSchema}')
begin begin
create table migration.MigratedVersion create table {MetadataSchema}.{TablePrefix}MigratedVersion
( (
Id int identity(1, 1) constraint PK_MigratedVersion primary key, Id int identity(1, 1) constraint PK_{TablePrefix}MigratedVersion primary key,
SignatureId int constraint FK_MigratedVersion_MigrationSignature foreign key references migration.MigrationSignature(Id), SignatureId int constraint FK_{TablePrefix}MigratedVersion_{TablePrefix}MigrationSignature foreign key references {MetadataSchema}.{TablePrefix}MigrationSignature(Id),
[Version] varchar(20) not null [Version] varchar(20) not null
) )
end end
if not exists (select top 1 1 from sys.objects where name = 'MigratedScript' and type = 'U' and SCHEMA_NAME(schema_id) = 'migration') if not exists (select top 1 1 from sys.objects where name = '{TablePrefix}MigratedScript' and type = 'U' and SCHEMA_NAME(schema_id) = '{MetadataSchema}')
begin begin
create table migration.MigratedScript create table {MetadataSchema}.{TablePrefix}MigratedScript
( (
Id int identity(1, 1) constraint PK_MigratedScript primary key, Id int identity(1, 1) constraint PK_{TablePrefix}MigratedScript primary key,
VersionId int constraint FK_MigratedScript_MigratedVersion foreign key references migration.MigratedVersion(Id), VersionId int constraint FK_{TablePrefix}MigratedScript_{TablePrefix}MigratedVersion foreign key references {MetadataSchema}.{TablePrefix}MigratedVersion(Id),
Script varchar(250) not null Script varchar(250) not null
) )
end end

View File

@ -1,7 +1,7 @@
CREATE TABLE "MigrationSignature" ( CREATE TABLE "{TablePrefix}MigrationSignature" (
"Id" INTEGER NOT NULL, "Id" INTEGER NOT NULL,
"MigrationDate" TEXT NOT NULL, "MigrationDate" TEXT NOT NULL,
"MachineName" TEXT NOT NULL, "MachineName" TEXT NOT NULL,
"LastVersion" TEXT NOT NULL, "LastVersion" TEXT NOT NULL,
CONSTRAINT "PK_MigrationSignature" PRIMARY KEY("Id" AUTOINCREMENT) CONSTRAINT "PK_{TablePrefix}MigrationSignature" PRIMARY KEY("Id" AUTOINCREMENT)
); );

View File

@ -1,7 +1,7 @@
CREATE TABLE "MigratedVersion" ( CREATE TABLE "{TablePrefix}MigratedVersion" (
"Id" INTEGER NOT NULL, "Id" INTEGER NOT NULL,
"SignatureId" INTEGER, "SignatureId" INTEGER,
"Version" TEXT NOT NULL, "Version" TEXT NOT NULL,
CONSTRAINT "PK_MigratedVersion" PRIMARY KEY("Id" AUTOINCREMENT), CONSTRAINT "PK_{TablePrefix}MigratedVersion" PRIMARY KEY("Id" AUTOINCREMENT),
CONSTRAINT "FK_MigratedVersion_MigrationSignature" FOREIGN KEY("SignatureId") REFERENCES "MigrationSignature"("Id") CONSTRAINT "FK_{TablePrefix}MigratedVersion_{TablePrefix}MigrationSignature" FOREIGN KEY("SignatureId") REFERENCES "{TablePrefix}MigrationSignature"("Id")
); );

View File

@ -1,7 +1,7 @@
CREATE TABLE "MigratedScript" ( CREATE TABLE "{TablePrefix}MigratedScript" (
"Id" INTEGER NOT NULL, "Id" INTEGER NOT NULL,
"VersionId" INTEGER, "VersionId" INTEGER,
"Script" TEXT NOT NULL, "Script" TEXT NOT NULL,
CONSTRAINT "PK_MigratedScript" PRIMARY KEY("Id" AUTOINCREMENT), CONSTRAINT "PK_{TablePrefix}MigratedScript" PRIMARY KEY("Id" AUTOINCREMENT),
CONSTRAINT "FK_MigratedScript_MigratedVersion" FOREIGN KEY("VersionId") REFERENCES "MigratedVersion"("Id") CONSTRAINT "FK_{TablePrefix}MigratedScript_{TablePrefix}MigratedVersion" FOREIGN KEY("VersionId") REFERENCES "{TablePrefix}MigratedVersion"("Id")
); );

View File

@ -17,26 +17,28 @@ namespace Netmash.Infrastructure.DatabaseMigration.Services
private readonly ServiceConfiguration _configuration; private readonly ServiceConfiguration _configuration;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly ILogger<MetadataLocationService> _logger; private readonly ILogger<MetadataLocationService> _logger;
private readonly TableNamingService _tableNamingService;
public MetadataLocationService(ServiceConfiguration configuration, IServiceProvider serviceProvider, ILogger<MetadataLocationService> logger) public MetadataLocationService(ServiceConfiguration configuration, IServiceProvider serviceProvider, ILogger<MetadataLocationService> logger, TableNamingService tableNamingService)
{ {
_configuration=configuration; _configuration=configuration;
_serviceProvider=serviceProvider; _serviceProvider=serviceProvider;
_logger=logger; _logger=logger;
_tableNamingService = tableNamingService;
} }
public async Task Check() public async Task Check()
{ {
switch (_configuration.MetadataLocation) switch (_configuration.MetadataStorage)
{ {
case MetadataLocation.XmlFile: case MetadataStorage.XmlFile:
CheckWorkspace(); CheckWorkspace();
break; break;
case MetadataLocation.Database: case MetadataStorage.Database:
await CheckMigrationTables(); await CheckMigrationTables();
break; break;
default: default:
throw new NotImplementedException($"Metadata location {_configuration.MetadataLocation} is not implemented."); throw new NotImplementedException($"Metadata storage {_configuration.MetadataStorage} is not implemented.");
} }
} }
@ -74,7 +76,8 @@ namespace Netmash.Infrastructure.DatabaseMigration.Services
} }
var resourceContent = GetManifestResourceContent(assembly, resource); var resourceContent = GetManifestResourceContent(assembly, resource);
await _repository.ExecuteSqlRaw(resourceContent); var processedContent = _tableNamingService.ProcessSqlScript(resourceContent);
await _repository.ExecuteSqlRaw(processedContent);
} }
} }
} }

View File

@ -25,36 +25,39 @@ namespace Netmash.Infrastructure.DatabaseMigration.Services
public MigrationSignaturesService(ServiceConfiguration configuration, IServiceProvider serviceProvider) public MigrationSignaturesService(ServiceConfiguration configuration, IServiceProvider serviceProvider)
{ {
_migrationSignaturesFilePath = Path.Combine(configuration.Workspace, _migrationSignaturesFileName); var fileName = !string.IsNullOrEmpty(configuration.MetadataPrefix)
? $"{configuration.MetadataPrefix}{_migrationSignaturesFileName}"
: _migrationSignaturesFileName;
_migrationSignaturesFilePath = Path.Combine(configuration.Workspace, fileName);
_configuration = configuration; _configuration = configuration;
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
} }
public async Task<MigrationSignature> GetLastMigrationSignature() public async Task<MigrationSignature> GetLastMigrationSignature()
{ {
switch (_configuration.MetadataLocation) switch (_configuration.MetadataStorage)
{ {
case MetadataLocation.XmlFile: case MetadataStorage.XmlFile:
return GetLastMigrationSignatureFromFile(); return GetLastMigrationSignatureFromFile();
case MetadataLocation.Database: case MetadataStorage.Database:
return await GetLastMigrationSignatureFromDatabase(); return await GetLastMigrationSignatureFromDatabase();
default: default:
throw new NotImplementedException($"Metadata location {_configuration.MetadataLocation} is not implemented."); throw new NotImplementedException($"Metadata storage {_configuration.MetadataStorage} is not implemented.");
} }
} }
public async Task SaveMigrationSignature(MigrationSignature migrationSignature) public async Task SaveMigrationSignature(MigrationSignature migrationSignature)
{ {
switch (_configuration.MetadataLocation) switch (_configuration.MetadataStorage)
{ {
case MetadataLocation.XmlFile: case MetadataStorage.XmlFile:
SaveMigrationSignatureToFile(migrationSignature); SaveMigrationSignatureToFile(migrationSignature);
break; break;
case MetadataLocation.Database: case MetadataStorage.Database:
await SaveMigrationSignatureToDatabase(migrationSignature); await SaveMigrationSignatureToDatabase(migrationSignature);
break; break;
default: default:
throw new NotImplementedException($"Metadata location {_configuration.MetadataLocation} is not implemented."); throw new NotImplementedException($"Metadata storage {_configuration.MetadataStorage} is not implemented.");
} }
} }

View File

@ -0,0 +1,39 @@
using Netmash.Infrastructure.DatabaseMigration.Constants;
using Netmash.Infrastructure.DatabaseMigration.Models;
namespace Netmash.Infrastructure.DatabaseMigration.Services
{
internal class TableNamingService
{
private readonly ServiceConfiguration _configuration;
public TableNamingService(ServiceConfiguration configuration)
{
_configuration = configuration;
}
public string GetTableName(string baseTableName)
{
return !string.IsNullOrEmpty(_configuration.MetadataPrefix)
? $"{_configuration.MetadataPrefix}{baseTableName}"
: baseTableName;
}
public string GetSchemaName()
{
return _configuration.DatabaseType == DatabaseType.SQLServer
? _configuration.MetadataSchema
: null;
}
public string ProcessSqlScript(string sqlContent)
{
var tablePrefix = _configuration.MetadataPrefix ?? string.Empty;
var metadataSchema = _configuration.MetadataSchema ?? "migration";
return sqlContent
.Replace("{TablePrefix}", tablePrefix)
.Replace("{MetadataSchema}", metadataSchema);
}
}
}