diff --git a/Oqtane.Database.PostgreSQL/OqtaneHistoryRepository.cs b/Oqtane.Database.PostgreSQL/OqtaneHistoryRepository.cs new file mode 100644 index 00000000..3172946e --- /dev/null +++ b/Oqtane.Database.PostgreSQL/OqtaneHistoryRepository.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Migrations.Internal; +using Oqtane.Migrations.Framework; +using Oqtane.Models; +using Oqtane.Shared; + +// ReSharper disable ClassNeverInstantiated.Global + +namespace Oqtane.Database.PostgreSQL +{ + public class OqtaneHistoryRepository : NpgsqlHistoryRepository + { + private string _appliedDateColumnName = "applied_date"; + private string _appliedVersionColumnName = "applied_version"; + private MigrationHistoryTable _migrationHistoryTable; + + public OqtaneHistoryRepository(HistoryRepositoryDependencies dependencies) : base(dependencies) + { + _migrationHistoryTable = new MigrationHistoryTable + { + TableName = TableName, + TableSchema = TableSchema, + MigrationIdColumnName = MigrationIdColumnName, + ProductVersionColumnName = ProductVersionColumnName, + AppliedVersionColumnName = _appliedVersionColumnName, + AppliedDateColumnName = _appliedDateColumnName + }; + + } + + protected override void ConfigureTable(EntityTypeBuilder history) + { + base.ConfigureTable(history); + history.Property(_appliedVersionColumnName).HasMaxLength(10); + history.Property(_appliedDateColumnName); + } + + public override string GetInsertScript(HistoryRow row) + { + if (row == null) + { + throw new ArgumentNullException(nameof(row)); + } + + return MigrationUtils.BuildInsertScript(row, Dependencies, _migrationHistoryTable); + } + } +} diff --git a/Oqtane.Database.PostgreSQL/PostgreSQLDatabase.cs b/Oqtane.Database.PostgreSQL/PostgreSQLDatabase.cs index 8f9552dd..064e6f34 100644 --- a/Oqtane.Database.PostgreSQL/PostgreSQLDatabase.cs +++ b/Oqtane.Database.PostgreSQL/PostgreSQLDatabase.cs @@ -119,7 +119,9 @@ namespace Oqtane.Database.PostgreSQL public override DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder optionsBuilder, string connectionString) { - return optionsBuilder.UseNpgsql(connectionString).UseSnakeCaseNamingConvention(); + return optionsBuilder.UseNpgsql(connectionString) + .UseSnakeCaseNamingConvention() + .ReplaceService(); } private void PrepareCommand(NpgsqlConnection conn, NpgsqlCommand cmd, string query) diff --git a/Oqtane.Database.SqlServer/OqtaneHistoryRepository.cs b/Oqtane.Database.SqlServer/OqtaneHistoryRepository.cs new file mode 100644 index 00000000..394aedb0 --- /dev/null +++ b/Oqtane.Database.SqlServer/OqtaneHistoryRepository.cs @@ -0,0 +1,53 @@ +using System; +using System.Text; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.SqlServer.Migrations.Internal; +using Microsoft.EntityFrameworkCore.Storage; +using Oqtane.Migrations.Framework; +using Oqtane.Models; +using Oqtane.Shared; + +// ReSharper disable ClassNeverInstantiated.Global + +namespace Oqtane.Database.SqlServer +{ + public class OqtaneHistoryRepository : SqlServerHistoryRepository + { + private string _appliedDateColumnName = "AppliedDate"; + private string _appliedVersionColumnName = "AppliedVersion"; + private MigrationHistoryTable _migrationHistoryTable; + + public OqtaneHistoryRepository(HistoryRepositoryDependencies dependencies) : base(dependencies) + { + _migrationHistoryTable = new MigrationHistoryTable + { + TableName = TableName, + TableSchema = TableSchema, + MigrationIdColumnName = MigrationIdColumnName, + ProductVersionColumnName = ProductVersionColumnName, + AppliedVersionColumnName = _appliedVersionColumnName, + AppliedDateColumnName = _appliedDateColumnName + }; + + } + + protected override void ConfigureTable(EntityTypeBuilder history) + { + base.ConfigureTable(history); + history.Property(_appliedVersionColumnName).HasMaxLength(10); + history.Property(_appliedDateColumnName); + } + + public override string GetInsertScript(HistoryRow row) + { + if (row == null) + { + throw new ArgumentNullException(nameof(row)); + } + + return MigrationUtils.BuildInsertScript(row, Dependencies, _migrationHistoryTable); + } + } +} diff --git a/Oqtane.Database.SqlServer/SqlServerDatabase.cs b/Oqtane.Database.SqlServer/SqlServerDatabase.cs index 08162320..fef0b752 100644 --- a/Oqtane.Database.SqlServer/SqlServerDatabase.cs +++ b/Oqtane.Database.SqlServer/SqlServerDatabase.cs @@ -64,7 +64,8 @@ namespace Oqtane.Database.SqlServer public override DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder optionsBuilder, string connectionString) { - return optionsBuilder.UseSqlServer(connectionString); + return optionsBuilder.UseSqlServer(connectionString) + .ReplaceService(); } private string FormatConnectionString(string connectionString) diff --git a/Oqtane.Database.Sqlite/OqtaneHistoryRepository.cs b/Oqtane.Database.Sqlite/OqtaneHistoryRepository.cs new file mode 100644 index 00000000..4703714a --- /dev/null +++ b/Oqtane.Database.Sqlite/OqtaneHistoryRepository.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Sqlite.Migrations.Internal; +using Oqtane.Migrations.Framework; +using Oqtane.Models; +using Oqtane.Shared; + +// ReSharper disable ClassNeverInstantiated.Global + +namespace Oqtane.Database.Sqlite +{ + public class OqtaneHistoryRepository : SqliteHistoryRepository + { + private string _appliedDateColumnName = "AppliedDate"; + private string _appliedVersionColumnName = "AppliedVersion"; + private MigrationHistoryTable _migrationHistoryTable; + + public OqtaneHistoryRepository(HistoryRepositoryDependencies dependencies) : base(dependencies) + { + _migrationHistoryTable = new MigrationHistoryTable + { + TableName = TableName, + TableSchema = TableSchema, + MigrationIdColumnName = MigrationIdColumnName, + ProductVersionColumnName = ProductVersionColumnName, + AppliedVersionColumnName = _appliedVersionColumnName, + AppliedDateColumnName = _appliedDateColumnName + }; + + } + + protected override void ConfigureTable(EntityTypeBuilder history) + { + base.ConfigureTable(history); + history.Property(_appliedVersionColumnName).HasMaxLength(10); + history.Property(_appliedDateColumnName); + } + + public override string GetInsertScript(HistoryRow row) + { + if (row == null) + { + throw new ArgumentNullException(nameof(row)); + } + + return MigrationUtils.BuildInsertScript(row, Dependencies, _migrationHistoryTable); + } + } +} diff --git a/Oqtane.Database.Sqlite/SqliteDatabase.cs b/Oqtane.Database.Sqlite/SqliteDatabase.cs index b9f47594..a6b078c9 100644 --- a/Oqtane.Database.Sqlite/SqliteDatabase.cs +++ b/Oqtane.Database.Sqlite/SqliteDatabase.cs @@ -77,7 +77,8 @@ namespace Oqtane.Database.Sqlite public override DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder optionsBuilder, string connectionString) { - return optionsBuilder.UseSqlite(connectionString); + return optionsBuilder.UseSqlite(connectionString) + .ReplaceService(); } private void PrepareCommand(SqliteConnection conn, SqliteCommand cmd, string query) diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index 7fa40011..47c19692 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -726,7 +726,10 @@ namespace Oqtane.Infrastructure { var script = (isMaster) ? "MigrateMaster.sql" : "MigrateTenant.sql"; - sql.ExecuteScript(connectionString, databaseType, Assembly.GetExecutingAssembly(), script); + var query = sql.GetScriptFromAssembly(Assembly.GetExecutingAssembly(), script); + query = query.Replace("{{Version}}", Constants.Version); + + sql.ExecuteNonQuery(connectionString, databaseType, query); } } } diff --git a/Oqtane.Server/Migrations/Framework/MigrationUtils.cs b/Oqtane.Server/Migrations/Framework/MigrationUtils.cs new file mode 100644 index 00000000..a2b78934 --- /dev/null +++ b/Oqtane.Server/Migrations/Framework/MigrationUtils.cs @@ -0,0 +1,42 @@ +using System; +using System.Text; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Oqtane.Models; +using Oqtane.Shared; + +namespace Oqtane.Migrations.Framework +{ + public static class MigrationUtils + { + public static string BuildInsertScript(HistoryRow row, HistoryRepositoryDependencies dependencies, MigrationHistoryTable historyTable ) + { + var sqlGenerationHelper = dependencies.SqlGenerationHelper; + var stringTypeMapping = dependencies.TypeMappingSource.GetMapping(typeof(string)); + + return new StringBuilder().Append("INSERT INTO ") + .Append(sqlGenerationHelper.DelimitIdentifier(historyTable.TableName, historyTable.TableSchema)) + .Append(" (") + .Append(sqlGenerationHelper.DelimitIdentifier(historyTable.MigrationIdColumnName)) + .Append(", ") + .Append(sqlGenerationHelper.DelimitIdentifier(historyTable.ProductVersionColumnName)) + .Append(", ") + .Append(sqlGenerationHelper.DelimitIdentifier(historyTable.AppliedVersionColumnName)) + .Append(", ") + .Append(sqlGenerationHelper.DelimitIdentifier(historyTable.AppliedDateColumnName)) + .AppendLine(")") + .Append("VALUES (") + .Append(stringTypeMapping.GenerateSqlLiteral(row.MigrationId)) + .Append(", ") + .Append(stringTypeMapping.GenerateSqlLiteral(row.ProductVersion)) + .Append(", ") + .Append(stringTypeMapping.GenerateSqlLiteral(Constants.Version)) + .Append(", ") + .Append(stringTypeMapping.GenerateSqlLiteral(DateTime.Now.ToString("yyyy'-'MM'-'dd' 'HH':'mm':'ss'.'fffffffK"))) + .Append(")") + .AppendLine(sqlGenerationHelper.StatementTerminator) + .ToString(); + } + + } +} diff --git a/Oqtane.Server/Migrations/Tenant/02010002_DropAppVersionsTableInTenant.cs b/Oqtane.Server/Migrations/Tenant/02010002_DropAppVersionsTableInTenant.cs new file mode 100644 index 00000000..4a8a045e --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/02010002_DropAppVersionsTableInTenant.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations.Tenant +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.02.01.00.02")] + public class DropAppVersionsTableinTenant : MultiDatabaseMigration + { + public DropAppVersionsTableinTenant(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + //Drop AppVersions table + var appVersionsEntityBuilder = new AppVersionsEntityBuilder(migrationBuilder, ActiveDatabase); + appVersionsEntityBuilder.Drop(); + } + } +} diff --git a/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs b/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs index ca4b1128..ed1b96b0 100644 --- a/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs @@ -14,6 +14,10 @@ namespace Oqtane.Repository int ExecuteNonQuery(Tenant tenant, string query); + int ExecuteNonQuery(string connectionString, string databaseType, string query); + IDataReader ExecuteReader(Tenant tenant, string query); + + string GetScriptFromAssembly(Assembly assembly, string fileName); } } diff --git a/Oqtane.Server/Repository/SqlRepository.cs b/Oqtane.Server/Repository/SqlRepository.cs index e43602f3..59cb2a46 100644 --- a/Oqtane.Server/Repository/SqlRepository.cs +++ b/Oqtane.Server/Repository/SqlRepository.cs @@ -78,13 +78,13 @@ namespace Oqtane.Repository return db.ExecuteReader(tenant.DBConnectionString, query); } - private int ExecuteNonQuery(string connectionString, string databaseType, string query) + public int ExecuteNonQuery(string connectionString, string databaseType, string query) { var db = GetActiveDatabase(databaseType); return db.ExecuteNonQuery(connectionString, query); } - private string GetScriptFromAssembly(Assembly assembly, string fileName) + public string GetScriptFromAssembly(Assembly assembly, string fileName) { // script must be included as an Embedded Resource within an assembly var script = ""; diff --git a/Oqtane.Server/Scripts/MigrateMaster.sql b/Oqtane.Server/Scripts/MigrateMaster.sql index 3da59696..4ce2fa9e 100644 --- a/Oqtane.Server/Scripts/MigrateMaster.sql +++ b/Oqtane.Server/Scripts/MigrateMaster.sql @@ -5,13 +5,17 @@ IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'dbo.SchemaVersion CREATE TABLE __EFMigrationsHistory ( MigrationId nvarchar(150) NOT NULL CONSTRAINT PK___EFMigrationsHistory PRIMARY KEY, - ProductVersion nvarchar(32) NOT NULL + ProductVersion nvarchar(32) NOT NULL, + AppliedVersion nvarchar(10) NOT NULL, + AppliedDate datetime DEFAULT GETDATE() ) END - INSERT INTO __EFMigrationsHistory(MigrationId, ProductVersion) - VALUES ('Master.01.00.00.00', '5.0.0') - INSERT INTO __EFMigrationsHistory(MigrationId, ProductVersion) - SELECT REPLACE(REPLACE(ScriptName, 'Oqtane.Scripts.', ''), '.sql', '') As MigrationId, ProductVersion = '5.0.0' + INSERT INTO __EFMigrationsHistory(MigrationId, ProductVersion, AppliedVersion) + VALUES ('Master.01.00.00.00', '5.0.0', '{{Version}}') + INSERT INTO __EFMigrationsHistory(MigrationId, ProductVersion, AppliedVersion) + SELECT REPLACE(REPLACE(ScriptName, 'Oqtane.Scripts.', ''), '.sql', '') As MigrationId, + ProductVersion = '5.0.0', + AppliedVersion = '{{Version}}' FROM SchemaVersions WHERE ScriptName LIKE 'Oqtane.Scripts.Master.01%' END \ No newline at end of file diff --git a/Oqtane.Server/Scripts/MigrateTenant.sql b/Oqtane.Server/Scripts/MigrateTenant.sql index ca4ae38e..b83d27e0 100644 --- a/Oqtane.Server/Scripts/MigrateTenant.sql +++ b/Oqtane.Server/Scripts/MigrateTenant.sql @@ -5,16 +5,20 @@ IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'dbo.SchemaVersion CREATE TABLE __EFMigrationsHistory ( MigrationId nvarchar(150) NOT NULL CONSTRAINT PK___EFMigrationsHistory PRIMARY KEY, - ProductVersion nvarchar(32) NOT NULL + ProductVersion nvarchar(32) NOT NULL, + AppliedVersion nvarchar(10) NOT NULL, + AppliedDate datetime DEFAULT GETDATE() ) END - INSERT INTO __EFMigrationsHistory(MigrationId, ProductVersion) - VALUES ('Tenant.01.00.00.00', '5.0.0') - INSERT INTO __EFMigrationsHistory(MigrationId, ProductVersion) - SELECT REPLACE(REPLACE(ScriptName, 'Oqtane.Scripts.', ''), '.sql', '') As MigrationId, ProductVersion = '5.0.0' + INSERT INTO __EFMigrationsHistory(MigrationId, ProductVersion, AppliedVersion) + VALUES ('Tenant.01.00.00.00', '5.0.0', '{{Version}}') + INSERT INTO __EFMigrationsHistory(MigrationId, ProductVersion, AppliedVersion) + SELECT REPLACE(REPLACE(ScriptName, 'Oqtane.Scripts.', ''), '.sql', '') As MigrationId, + ProductVersion = '5.0.0', + AppliedVersion = '{{Version}}' FROM SchemaVersions WHERE ScriptName LIKE 'Oqtane.Scripts.Tenant.01%' OR ScriptName LIKE 'Oqtane.Scripts.Tenant.02%' - INSERT INTO __EFMigrationsHistory(MigrationId, ProductVersion) - VALUES ('HtmlText.01.00.00.00', '5.0.0') + INSERT INTO __EFMigrationsHistory(MigrationId, ProductVersion, AppliedVersion) + VALUES ('HtmlText.01.00.00.00', '5.0.0', '{{Version}}') END \ No newline at end of file diff --git a/Oqtane.Shared/Models/MigrationHistoryTable.cs b/Oqtane.Shared/Models/MigrationHistoryTable.cs new file mode 100644 index 00000000..ee694467 --- /dev/null +++ b/Oqtane.Shared/Models/MigrationHistoryTable.cs @@ -0,0 +1,12 @@ +namespace Oqtane.Models +{ + public class MigrationHistoryTable + { + public string TableName { get; set; } + public string TableSchema { get; set; } + public string MigrationIdColumnName { get; set; } + public string ProductVersionColumnName { get; set; } + public string AppliedVersionColumnName { get; set; } + public string AppliedDateColumnName { get; set; } + } +}