mirror of
https://dev.azure.com/tstanciu94/Packages/_git/Netmash
synced 2025-07-30 11:11:10 +03:00
**1.4.0**: Multi-schema support and table prefixing
This commit is contained in:
parent
28f1bdbd2e
commit
aad1b9c39f
@ -1,6 +1,6 @@
|
||||
namespace Netmash.Infrastructure.DatabaseMigration.Constants
|
||||
{
|
||||
public enum MetadataLocation
|
||||
public enum MetadataStorage
|
||||
{
|
||||
XmlFile,
|
||||
Database
|
||||
|
@ -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<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; }
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<TableNamingService>();
|
||||
services.AddDataAccess(serviceConfiguration);
|
||||
services.AddSingleton<IMetadataLocationService, MetadataLocationService>();
|
||||
services.AddSingleton<IMigrationSignaturesService, MigrationSignaturesService>();
|
||||
@ -72,5 +78,38 @@ namespace Netmash.Infrastructure.DatabaseMigration
|
||||
var migrationService = app.ApplicationServices.GetService<IMigrationService>();
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<MigratedScript>
|
||||
{
|
||||
private readonly TableNamingService _tableNamingService;
|
||||
|
||||
public MigratedScriptConfiguration(TableNamingService tableNamingService)
|
||||
{
|
||||
_tableNamingService = tableNamingService;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -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<MigratedVersion>
|
||||
{
|
||||
private readonly TableNamingService _tableNamingService;
|
||||
|
||||
public MigratedVersionConfiguration(TableNamingService tableNamingService)
|
||||
{
|
||||
_tableNamingService = tableNamingService;
|
||||
}
|
||||
|
||||
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.HasMany(z => z.Scripts).WithOne().HasForeignKey(z => z.VersionId);
|
||||
}
|
||||
|
@ -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<MigrationSignature>
|
||||
{
|
||||
private readonly TableNamingService _tableNamingService;
|
||||
|
||||
public MigrationSignatureConfiguration(TableNamingService tableNamingService)
|
||||
{
|
||||
_tableNamingService = tableNamingService;
|
||||
}
|
||||
|
||||
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.HasMany(z => z.MigratedVersions).WithOne().HasForeignKey(z => z.SignatureId);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,10 +7,12 @@
|
||||
<RepositoryUrl>https://lab.code-rove.com/gitea/bricks/netmash</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<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)
|
||||
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>
|
||||
|
||||
<ItemGroup>
|
||||
@ -38,4 +40,11 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="README.md">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -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
|
@ -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<bool> 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"),
|
||||
};
|
||||
|
||||
|
@ -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
|
@ -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
|
@ -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)
|
||||
);
|
@ -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")
|
||||
);
|
@ -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")
|
||||
);
|
@ -17,26 +17,28 @@ namespace Netmash.Infrastructure.DatabaseMigration.Services
|
||||
private readonly ServiceConfiguration _configuration;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
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;
|
||||
_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<MigrationSignature> 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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user