From aad1b9c39f4e03fdc1f58f03305474ded0f47931 Mon Sep 17 00:00:00 2001 From: Tudor Stanciu Date: Wed, 30 Jul 2025 01:00:35 +0300 Subject: [PATCH] **1.4.0**: Multi-schema support and table prefixing --- .../Constants/MetadataLocation.cs | 2 +- .../DbContexts/MigrationDbContext.cs | 12 +- .../DependencyInjectionExtensions.cs | 45 ++- .../MigratedScriptConfiguration.cs | 13 +- .../MigratedVersionConfiguration.cs | 13 +- .../MigrationSignatureConfiguration.cs | 13 +- .../Models/ServiceConfiguration.cs | 10 +- ...sh.Infrastructure.DatabaseMigration.csproj | 13 +- .../README.md | 356 ++++++++++++++++++ .../Repositories/MigrationRepository.cs | 12 +- .../SqlServer/01.CreateMigrationSchema.sql | 4 +- .../Scripts/SqlServer/02.MigrationTables.sql | 22 +- .../Sqlite/01.MigrationSignatureTable.sql | 4 +- .../Sqlite/02.MigratedVersionTable.sql | 6 +- .../Scripts/Sqlite/03.MigratedScriptTable.sql | 6 +- .../Services/MetadataLocationService.cs | 15 +- .../Services/MigrationSignaturesService.cs | 21 +- .../Services/TableNamingService.cs | 39 ++ 18 files changed, 551 insertions(+), 55 deletions(-) create mode 100644 src/infrastructure/Netmash.Infrastructure.DatabaseMigration/README.md create mode 100644 src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Services/TableNamingService.cs diff --git a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Constants/MetadataLocation.cs b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Constants/MetadataLocation.cs index 23f6607..bf02a60 100644 --- a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Constants/MetadataLocation.cs +++ b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Constants/MetadataLocation.cs @@ -1,6 +1,6 @@ namespace Netmash.Infrastructure.DatabaseMigration.Constants { - public enum MetadataLocation + public enum MetadataStorage { XmlFile, Database diff --git a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/DbContexts/MigrationDbContext.cs b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/DbContexts/MigrationDbContext.cs index b5ce6ee..7966f10 100644 --- a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/DbContexts/MigrationDbContext.cs +++ b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/DbContexts/MigrationDbContext.cs @@ -1,13 +1,17 @@ using Microsoft.EntityFrameworkCore; using Netmash.Infrastructure.DatabaseMigration.Entities; using Netmash.Infrastructure.DatabaseMigration.Entities.Configurations; +using Netmash.Infrastructure.DatabaseMigration.Services; namespace Netmash.Infrastructure.DatabaseMigration.DbContexts { internal class MigrationDbContext : DbContext { - public MigrationDbContext(DbContextOptions options) : base(options) + private readonly TableNamingService _tableNamingService; + + public MigrationDbContext(DbContextOptions options, TableNamingService tableNamingService) : base(options) { + _tableNamingService = tableNamingService; } public DbSet MigrationSignatures { get; set; } @@ -16,9 +20,9 @@ namespace Netmash.Infrastructure.DatabaseMigration.DbContexts { base.OnModelCreating(modelBuilder); - modelBuilder.ApplyConfiguration(new MigratedScriptConfiguration()); - modelBuilder.ApplyConfiguration(new MigratedVersionConfiguration()); - modelBuilder.ApplyConfiguration(new MigrationSignatureConfiguration()); + modelBuilder.ApplyConfiguration(new MigratedScriptConfiguration(_tableNamingService)); + modelBuilder.ApplyConfiguration(new MigratedVersionConfiguration(_tableNamingService)); + modelBuilder.ApplyConfiguration(new MigrationSignatureConfiguration(_tableNamingService)); } } } diff --git a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/DependencyInjectionExtensions.cs b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/DependencyInjectionExtensions.cs index 87972bc..f52d233 100644 --- a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/DependencyInjectionExtensions.cs +++ b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/DependencyInjectionExtensions.cs @@ -9,6 +9,7 @@ using Netmash.Infrastructure.DatabaseMigration.Repositories; using Netmash.Infrastructure.DatabaseMigration.Services; using Netmash.Infrastructure.DatabaseMigration.Services.Abstractions; using System; +using System.Text.RegularExpressions; namespace Netmash.Infrastructure.DatabaseMigration { @@ -18,13 +19,18 @@ namespace Netmash.Infrastructure.DatabaseMigration public static void AddMigration(this IServiceCollection services, DatabaseType databaseType = DatabaseType.SQLite, - MetadataLocation metadataLocation = MetadataLocation.XmlFile, + MetadataStorage metadataStorage = MetadataStorage.XmlFile, string connectionName = "DatabaseConnection", 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(); services.AddDataAccess(serviceConfiguration); services.AddSingleton(); services.AddSingleton(); @@ -72,5 +78,38 @@ namespace Netmash.Infrastructure.DatabaseMigration var migrationService = app.ApplicationServices.GetService(); 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."); + } + } } } diff --git a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Entities/Configurations/MigratedScriptConfiguration.cs b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Entities/Configurations/MigratedScriptConfiguration.cs index 9675510..17f37e2 100644 --- a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Entities/Configurations/MigratedScriptConfiguration.cs +++ b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Entities/Configurations/MigratedScriptConfiguration.cs @@ -1,13 +1,24 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Netmash.Infrastructure.DatabaseMigration.Services; namespace Netmash.Infrastructure.DatabaseMigration.Entities.Configurations { internal class MigratedScriptConfiguration : IEntityTypeConfiguration { + private readonly TableNamingService _tableNamingService; + + public MigratedScriptConfiguration(TableNamingService tableNamingService) + { + _tableNamingService = tableNamingService; + } + public void Configure(EntityTypeBuilder 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(); } } diff --git a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Entities/Configurations/MigratedVersionConfiguration.cs b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Entities/Configurations/MigratedVersionConfiguration.cs index ededa15..f35e4c5 100644 --- a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Entities/Configurations/MigratedVersionConfiguration.cs +++ b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Entities/Configurations/MigratedVersionConfiguration.cs @@ -1,13 +1,24 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Netmash.Infrastructure.DatabaseMigration.Services; namespace Netmash.Infrastructure.DatabaseMigration.Entities.Configurations { internal class MigratedVersionConfiguration : IEntityTypeConfiguration { + private readonly TableNamingService _tableNamingService; + + public MigratedVersionConfiguration(TableNamingService tableNamingService) + { + _tableNamingService = tableNamingService; + } + public void Configure(EntityTypeBuilder 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.HasMany(z => z.Scripts).WithOne().HasForeignKey(z => z.VersionId); } diff --git a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Entities/Configurations/MigrationSignatureConfiguration.cs b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Entities/Configurations/MigrationSignatureConfiguration.cs index f5670c5..c96041f 100644 --- a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Entities/Configurations/MigrationSignatureConfiguration.cs +++ b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Entities/Configurations/MigrationSignatureConfiguration.cs @@ -1,13 +1,24 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Netmash.Infrastructure.DatabaseMigration.Services; namespace Netmash.Infrastructure.DatabaseMigration.Entities.Configurations { internal class MigrationSignatureConfiguration : IEntityTypeConfiguration { + private readonly TableNamingService _tableNamingService; + + public MigrationSignatureConfiguration(TableNamingService tableNamingService) + { + _tableNamingService = tableNamingService; + } + public void Configure(EntityTypeBuilder 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.HasMany(z => z.MigratedVersions).WithOne().HasForeignKey(z => z.SignatureId); } diff --git a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Models/ServiceConfiguration.cs b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Models/ServiceConfiguration.cs index 539bc38..5e848d2 100644 --- a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Models/ServiceConfiguration.cs +++ b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Models/ServiceConfiguration.cs @@ -5,18 +5,22 @@ namespace Netmash.Infrastructure.DatabaseMigration.Models internal class ServiceConfiguration { public DatabaseType DatabaseType { get; } - public MetadataLocation MetadataLocation { get; } + public MetadataStorage MetadataStorage { get; } public string ConnectionName { get; } public string Workspace { 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; - MetadataLocation = metadataLocation; + MetadataStorage = metadataStorage; ConnectionName = connectionName; Workspace = workspace; ScriptsDirectory = scriptsDirectory; + MetadataSchema = metadataSchema; + MetadataPrefix = metadataPrefix; } } } diff --git a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Netmash.Infrastructure.DatabaseMigration.csproj b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Netmash.Infrastructure.DatabaseMigration.csproj index 767a115..e38e233 100644 --- a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Netmash.Infrastructure.DatabaseMigration.csproj +++ b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Netmash.Infrastructure.DatabaseMigration.csproj @@ -7,10 +7,12 @@ https://lab.code-rove.com/gitea/bricks/netmash Git Netmash database migration - 1.3.0 + 1.4.0 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.3.0: .NET 8 upgrade +1.3.0: .NET 8 upgrade +1.4.0: Multi-schema support, table prefixing + README.md @@ -38,4 +40,11 @@ + + + True + \ + + + diff --git a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/README.md b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/README.md new file mode 100644 index 0000000..8853e43 --- /dev/null +++ b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/README.md @@ -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 diff --git a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Repositories/MigrationRepository.cs b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Repositories/MigrationRepository.cs index 57d7be0..979e76c 100644 --- a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Repositories/MigrationRepository.cs +++ b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Repositories/MigrationRepository.cs @@ -2,6 +2,7 @@ using Netmash.Infrastructure.DatabaseMigration.Constants; using Netmash.Infrastructure.DatabaseMigration.DbContexts; using Netmash.Infrastructure.DatabaseMigration.Entities; +using Netmash.Infrastructure.DatabaseMigration.Services; using System; using System.Linq; using System.Threading.Tasks; @@ -11,10 +12,12 @@ namespace Netmash.Infrastructure.DatabaseMigration.Repositories internal class MigrationRepository : IMigrationRepository { private readonly MigrationDbContext _dbContext; + private readonly TableNamingService _tableNamingService; - public MigrationRepository(MigrationDbContext dbContext) + public MigrationRepository(MigrationDbContext dbContext, TableNamingService tableNamingService) { _dbContext = dbContext; + _tableNamingService = tableNamingService; } public async Task ExecuteSqlRaw(string sqlRaw) @@ -24,10 +27,13 @@ namespace Netmash.Infrastructure.DatabaseMigration.Repositories public async Task MigrationTablesAreSet(DatabaseType databaseType) { + var tableName = _tableNamingService.GetTableName("MigrationSignature"); + var schemaName = _tableNamingService.GetSchemaName(); + 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.SQLite => "select count(1) from sqlite_master where type='table' and name='MigrationSignature';", + 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='{tableName}';", _ => throw new NotImplementedException($"DatabaseMigration type {databaseType} is not implemented"), }; diff --git a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Scripts/SqlServer/01.CreateMigrationSchema.sql b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Scripts/SqlServer/01.CreateMigrationSchema.sql index 93ecca7..1fe7d8d 100644 --- a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Scripts/SqlServer/01.CreateMigrationSchema.sql +++ b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Scripts/SqlServer/01.CreateMigrationSchema.sql @@ -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 - EXEC ('CREATE SCHEMA [migration] AUTHORIZATION [dbo]') + EXEC ('CREATE SCHEMA [{MetadataSchema}] AUTHORIZATION [dbo]') end \ No newline at end of file diff --git a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Scripts/SqlServer/02.MigrationTables.sql b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Scripts/SqlServer/02.MigrationTables.sql index 5a76d52..4a13dcc 100644 --- a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Scripts/SqlServer/02.MigrationTables.sql +++ b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Scripts/SqlServer/02.MigrationTables.sql @@ -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 - 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, MachineName varchar(30) not null, LastVersion varchar(20) not null ) 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 - create table migration.MigratedVersion + create table {MetadataSchema}.{TablePrefix}MigratedVersion ( - Id int identity(1, 1) constraint PK_MigratedVersion primary key, - SignatureId int constraint FK_MigratedVersion_MigrationSignature foreign key references migration.MigrationSignature(Id), + Id int identity(1, 1) constraint PK_{TablePrefix}MigratedVersion primary key, + SignatureId int constraint FK_{TablePrefix}MigratedVersion_{TablePrefix}MigrationSignature foreign key references {MetadataSchema}.{TablePrefix}MigrationSignature(Id), [Version] varchar(20) not null ) 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 - create table migration.MigratedScript + create table {MetadataSchema}.{TablePrefix}MigratedScript ( - Id int identity(1, 1) constraint PK_MigratedScript primary key, - VersionId int constraint FK_MigratedScript_MigratedVersion foreign key references migration.MigratedVersion(Id), + Id int identity(1, 1) constraint PK_{TablePrefix}MigratedScript primary key, + VersionId int constraint FK_{TablePrefix}MigratedScript_{TablePrefix}MigratedVersion foreign key references {MetadataSchema}.{TablePrefix}MigratedVersion(Id), Script varchar(250) not null ) end \ No newline at end of file diff --git a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Scripts/Sqlite/01.MigrationSignatureTable.sql b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Scripts/Sqlite/01.MigrationSignatureTable.sql index fa68826..1b3329a 100644 --- a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Scripts/Sqlite/01.MigrationSignatureTable.sql +++ b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Scripts/Sqlite/01.MigrationSignatureTable.sql @@ -1,7 +1,7 @@ -CREATE TABLE "MigrationSignature" ( +CREATE TABLE "{TablePrefix}MigrationSignature" ( "Id" INTEGER NOT NULL, "MigrationDate" TEXT NOT NULL, "MachineName" TEXT NOT NULL, "LastVersion" TEXT NOT NULL, - CONSTRAINT "PK_MigrationSignature" PRIMARY KEY("Id" AUTOINCREMENT) + CONSTRAINT "PK_{TablePrefix}MigrationSignature" PRIMARY KEY("Id" AUTOINCREMENT) ); \ No newline at end of file diff --git a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Scripts/Sqlite/02.MigratedVersionTable.sql b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Scripts/Sqlite/02.MigratedVersionTable.sql index e799cd7..ab06ad2 100644 --- a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Scripts/Sqlite/02.MigratedVersionTable.sql +++ b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Scripts/Sqlite/02.MigratedVersionTable.sql @@ -1,7 +1,7 @@ -CREATE TABLE "MigratedVersion" ( +CREATE TABLE "{TablePrefix}MigratedVersion" ( "Id" INTEGER NOT NULL, "SignatureId" INTEGER, "Version" TEXT NOT NULL, - CONSTRAINT "PK_MigratedVersion" PRIMARY KEY("Id" AUTOINCREMENT), - CONSTRAINT "FK_MigratedVersion_MigrationSignature" FOREIGN KEY("SignatureId") REFERENCES "MigrationSignature"("Id") + CONSTRAINT "PK_{TablePrefix}MigratedVersion" PRIMARY KEY("Id" AUTOINCREMENT), + CONSTRAINT "FK_{TablePrefix}MigratedVersion_{TablePrefix}MigrationSignature" FOREIGN KEY("SignatureId") REFERENCES "{TablePrefix}MigrationSignature"("Id") ); \ No newline at end of file diff --git a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Scripts/Sqlite/03.MigratedScriptTable.sql b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Scripts/Sqlite/03.MigratedScriptTable.sql index 3dd59e8..81938f5 100644 --- a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Scripts/Sqlite/03.MigratedScriptTable.sql +++ b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Scripts/Sqlite/03.MigratedScriptTable.sql @@ -1,7 +1,7 @@ -CREATE TABLE "MigratedScript" ( +CREATE TABLE "{TablePrefix}MigratedScript" ( "Id" INTEGER NOT NULL, "VersionId" INTEGER, "Script" TEXT NOT NULL, - CONSTRAINT "PK_MigratedScript" PRIMARY KEY("Id" AUTOINCREMENT), - CONSTRAINT "FK_MigratedScript_MigratedVersion" FOREIGN KEY("VersionId") REFERENCES "MigratedVersion"("Id") + CONSTRAINT "PK_{TablePrefix}MigratedScript" PRIMARY KEY("Id" AUTOINCREMENT), + CONSTRAINT "FK_{TablePrefix}MigratedScript_{TablePrefix}MigratedVersion" FOREIGN KEY("VersionId") REFERENCES "{TablePrefix}MigratedVersion"("Id") ); \ No newline at end of file diff --git a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Services/MetadataLocationService.cs b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Services/MetadataLocationService.cs index b4376f8..e89d362 100644 --- a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Services/MetadataLocationService.cs +++ b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Services/MetadataLocationService.cs @@ -17,26 +17,28 @@ namespace Netmash.Infrastructure.DatabaseMigration.Services private readonly ServiceConfiguration _configuration; private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; + private readonly TableNamingService _tableNamingService; - public MetadataLocationService(ServiceConfiguration configuration, IServiceProvider serviceProvider, ILogger logger) + public MetadataLocationService(ServiceConfiguration configuration, IServiceProvider serviceProvider, ILogger logger, TableNamingService tableNamingService) { _configuration=configuration; _serviceProvider=serviceProvider; _logger=logger; + _tableNamingService = tableNamingService; } public async Task Check() { - switch (_configuration.MetadataLocation) + switch (_configuration.MetadataStorage) { - case MetadataLocation.XmlFile: + case MetadataStorage.XmlFile: CheckWorkspace(); break; - case MetadataLocation.Database: + case MetadataStorage.Database: await CheckMigrationTables(); break; 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); - await _repository.ExecuteSqlRaw(resourceContent); + var processedContent = _tableNamingService.ProcessSqlScript(resourceContent); + await _repository.ExecuteSqlRaw(processedContent); } } } diff --git a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Services/MigrationSignaturesService.cs b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Services/MigrationSignaturesService.cs index 4961fd0..e48c203 100644 --- a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Services/MigrationSignaturesService.cs +++ b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Services/MigrationSignaturesService.cs @@ -25,36 +25,39 @@ namespace Netmash.Infrastructure.DatabaseMigration.Services 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; _serviceProvider = serviceProvider; } public async Task GetLastMigrationSignature() { - switch (_configuration.MetadataLocation) + switch (_configuration.MetadataStorage) { - case MetadataLocation.XmlFile: + case MetadataStorage.XmlFile: return GetLastMigrationSignatureFromFile(); - case MetadataLocation.Database: + case MetadataStorage.Database: return await GetLastMigrationSignatureFromDatabase(); 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) { - switch (_configuration.MetadataLocation) + switch (_configuration.MetadataStorage) { - case MetadataLocation.XmlFile: + case MetadataStorage.XmlFile: SaveMigrationSignatureToFile(migrationSignature); break; - case MetadataLocation.Database: + case MetadataStorage.Database: await SaveMigrationSignatureToDatabase(migrationSignature); break; default: - throw new NotImplementedException($"Metadata location {_configuration.MetadataLocation} is not implemented."); + throw new NotImplementedException($"Metadata storage {_configuration.MetadataStorage} is not implemented."); } } diff --git a/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Services/TableNamingService.cs b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Services/TableNamingService.cs new file mode 100644 index 0000000..841e9c1 --- /dev/null +++ b/src/infrastructure/Netmash.Infrastructure.DatabaseMigration/Services/TableNamingService.cs @@ -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); + } + } +}