Add support for Sqlite - Installation path tested but AddSite not supported yet

This commit is contained in:
Charles Nurse 2021-03-23 11:06:18 -07:00
parent 8f1c760e87
commit cbcfc88492
22 changed files with 227 additions and 116 deletions

1
.gitignore vendored
View File

@ -13,6 +13,7 @@ msbuild.binlog
Oqtane.Server/appsettings.json
Oqtane.Server/Data/*.mdf
Oqtane.Server/Data/*.ldf
Oqtane.Server/Data/*.db
/Oqtane.Server/Properties/PublishProfiles/FolderProfile.pubxml
Oqtane.Server/Content

View File

@ -27,10 +27,19 @@
<select class="custom-select" @bind="@_databaseType">
<option value="LocalDB">@Localizer["Local Database"]</option>
<option value="SQLServer">@Localizer["SQL Server"]</option>
<option value="Sqlite">@Localizer["Sqlite"]</option>
</select>
</td>
</tr>
<tr>
<tr style="@(_databaseType == "Sqlite" ? "" : "display: none")">
<td>
<label class="control-label" style="font-weight: bold">@Localizer["File Name:"] </label>
</td>
<td>
<input type="text" class="form-control" @bind="@_fileName" />
</td>
</tr>
<tr style="@(_databaseType == "Sqlite" ? "display: none" : "")">
<td>
<label class="control-label" style="font-weight: bold">@Localizer["Server:"] </label>
</td>
@ -38,7 +47,7 @@
<input type="text" class="form-control" @bind="@_serverName" />
</td>
</tr>
<tr>
<tr style="@(_databaseType == "Sqlite" ? "display: none" : "")">
<td>
<label class="control-label" style="font-weight: bold">@Localizer["Database:"] </label>
</td>
@ -46,7 +55,7 @@
<input type="text" class="form-control" @bind="@_databaseName" />
</td>
</tr>
<tr>
<tr style="@(_databaseType == "Sqlite" ? "display: none" : "")">
<td>
<label class="control-label" style="font-weight: bold">@Localizer["Integrated Security:"] </label>
</td>
@ -129,6 +138,7 @@
@code {
private string _databaseType = "LocalDB";
private string _serverName = "(LocalDb)\\MSSQLLocalDB";
private string _fileName = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm") + ".db";
private string _databaseName = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm");
private string _username = string.Empty;
private string _password = string.Empty;
@ -138,6 +148,8 @@
private string _hostEmail = string.Empty;
private string _message = string.Empty;
private string _integratedSecurityDisplay = "display: none;";
private string _fileFieldsDisplay = "display: none;";
private string _serverFieldsDisplay = "display: none;";
private string _loadingDisplay = "display: none;";
protected override async Task OnAfterRenderAsync(bool firstRender)
@ -158,18 +170,18 @@
private async Task Install()
{
if (_serverName != "" && _databaseName != "" && _hostUsername != "" && _hostPassword.Length >= 6 && _hostPassword == _confirmPassword && _hostEmail != "")
if (((_serverName != "" && _databaseName != "") || _fileName !="") && _hostUsername != "" && _hostPassword.Length >= 6 && _hostPassword == _confirmPassword && _hostEmail != "")
{
_loadingDisplay = "";
StateHasChanged();
var connectionstring = "";
if (_databaseType == "LocalDB")
switch (_databaseType)
{
case "LocalDB":
connectionstring = "Data Source=" + _serverName + ";AttachDbFilename=|DataDirectory|\\" + _databaseName + ".mdf;Initial Catalog=" + _databaseName + ";Integrated Security=SSPI;";
}
else
{
break;
case "SQLServer":
connectionstring = "Data Source=" + _serverName + ";Initial Catalog=" + _databaseName + ";";
if (_integratedSecurityDisplay == "display: none;")
{
@ -179,12 +191,17 @@
{
connectionstring += "User ID=" + _username + ";Password=" + _password;
}
break;
case "Sqlite":
connectionstring = "Data Source=" + _fileName;
break;
}
Uri uri = new Uri(NavigationManager.Uri);
var config = new InstallConfig
{
DatabaseType = _databaseType,
ConnectionString = connectionstring,
Aliases = uri.Authority,
HostEmail = _hostEmail,

Binary file not shown.

View File

@ -5,10 +5,18 @@ namespace Oqtane.Extensions
{
public static class DbContextOptionsBuilderExtensions
{
public static DbContextOptionsBuilder UseOqtaneDatabase([NotNull] this DbContextOptionsBuilder optionsBuilder, string connectionString)
public static DbContextOptionsBuilder UseOqtaneDatabase([NotNull] this DbContextOptionsBuilder optionsBuilder, string databaseType, string connectionString)
{
switch (databaseType)
{
case "SqlServer":
optionsBuilder.UseSqlServer(connectionString);
//optionsBuilder.UseSqlite("Data Source=Oqtane.db");
break;
case "Sqlite":
optionsBuilder.UseSqlite(connectionString);
break;
}
return optionsBuilder;
}

View File

@ -171,7 +171,8 @@ namespace Oqtane.Infrastructure
if (!Directory.Exists(dataDirectory)) Directory.CreateDirectory(dataDirectory ?? String.Empty);
var connectionString = NormalizeConnectionString(install.ConnectionString);
using (var dbc = new DbContext(new DbContextOptionsBuilder().UseOqtaneDatabase(connectionString).Options))
var databaseType = install.DatabaseType;
using (var dbc = new DbContext(new DbContextOptionsBuilder().UseOqtaneDatabase(databaseType, connectionString).Options))
{
// create empty database if it does not exist
dbc.Database.EnsureCreated();
@ -199,7 +200,7 @@ namespace Oqtane.Infrastructure
{
try
{
var dbConfig = new DbConfig(null, null) {ConnectionString = install.ConnectionString};
var dbConfig = new DbConfig(null, null) {ConnectionString = install.ConnectionString, DatabaseType = install.DatabaseType};
using (var masterDbContext = new MasterDBContext(new DbContextOptions<MasterDBContext>(), dbConfig))
{
@ -216,6 +217,7 @@ namespace Oqtane.Infrastructure
if (result.Success)
{
UpdateConnectionString(install.ConnectionString);
UpdateDatabaseType(install.DatabaseType);
}
}
else
@ -232,12 +234,18 @@ namespace Oqtane.Infrastructure
if (!string.IsNullOrEmpty(install.TenantName) && !string.IsNullOrEmpty(install.Aliases))
{
using (var db = new InstallationContext(NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey))))
using (var db = new InstallationContext(install.DatabaseType, NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey))))
{
Tenant tenant;
if (install.IsNewTenant)
{
tenant = new Tenant { Name = install.TenantName, DBConnectionString = DenormalizeConnectionString(install.ConnectionString), CreatedBy = "", CreatedOn = DateTime.UtcNow, ModifiedBy = "", ModifiedOn = DateTime.UtcNow };
tenant = new Tenant { Name = install.TenantName,
DBConnectionString = DenormalizeConnectionString(install.ConnectionString),
DBType = install.DatabaseType,
CreatedBy = "",
CreatedOn = DateTime.UtcNow,
ModifiedBy = "",
ModifiedOn = DateTime.UtcNow };
db.Tenant.Add(tenant);
db.SaveChanges();
_cache.Remove("tenants");
@ -276,13 +284,15 @@ namespace Oqtane.Infrastructure
{
var upgrades = scope.ServiceProvider.GetRequiredService<IUpgradeManager>();
using (var db = new InstallationContext(NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey))))
var databaseType = _config.GetSection(SettingKeys.DatabaseSection)[SettingKeys.DatabaseTypeKey];
var connectionString = NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey));
using (var db = new InstallationContext(databaseType, connectionString))
{
foreach (var tenant in db.Tenant.ToList())
{
try
{
var dbConfig = new DbConfig(null, null) {ConnectionString = install.ConnectionString};
var dbConfig = new DbConfig(null, null) {ConnectionString = install.ConnectionString, DatabaseType = install.DatabaseType};
using (var tenantDbContext = new TenantDBContext(dbConfig, null))
{
// Push latest model into database
@ -337,16 +347,10 @@ namespace Oqtane.Infrastructure
if (moduleType != null)
{
var versions = moduleDefinition.ReleaseVersions.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
using (var db = new InstallationContext(NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey))))
{
var databaseType = _config.GetSection(SettingKeys.DatabaseSection)[SettingKeys.DatabaseTypeKey];
var connectionString = NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey));
using (var db = new InstallationContext(databaseType, connectionString)) {
foreach (var tenant in db.Tenant.ToList())
{
if (moduleType.GetInterface("IMigratable") != null)
{
var moduleObject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, moduleType) as IMigratable;
moduleObject.Migrate(tenant, MigrationType.Up);
}
else
{
var index = Array.FindIndex(versions, item => item == moduleDefinition.Version);
if (tenant.Name == install.TenantName && install.TenantName != TenantNames.Master)
@ -362,8 +366,8 @@ namespace Oqtane.Infrastructure
{
if (moduleType.GetInterface("IInstallable") != null)
{
var moduleObject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, moduleType);
((IInstallable)moduleObject).Install(tenant, versions[i]);
var moduleObject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, moduleType) as IInstallable;
moduleObject?.Install(tenant, versions[i]);
}
else
{
@ -377,7 +381,6 @@ namespace Oqtane.Infrastructure
}
}
}
}
if (string.IsNullOrEmpty(result.Message) && moduleDefinition.Version != versions[versions.Length - 1])
{
moduleDefinition.Version = versions[versions.Length - 1];
@ -530,11 +533,17 @@ namespace Oqtane.Infrastructure
connectionString = DenormalizeConnectionString(connectionString);
if (_config.GetConnectionString(SettingKeys.ConnectionStringKey) != connectionString)
{
AddOrUpdateAppSetting($"ConnectionStrings:{SettingKeys.ConnectionStringKey}", connectionString);
AddOrUpdateAppSetting($"{SettingKeys.ConnectionStringsSection}:{SettingKeys.ConnectionStringKey}", connectionString);
_config.Reload();
}
}
public void UpdateDatabaseType(string databaseType)
{
AddOrUpdateAppSetting($"{SettingKeys.DatabaseSection}:{SettingKeys.DatabaseTypeKey}", databaseType);
_config.Reload();
}
public void AddOrUpdateAppSetting<T>(string sectionPathKey, T value)
{
try

View File

@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Hosting;
using Oqtane.Shared;
// ReSharper disable AssignNullToNotNullAttribute
namespace Oqtane.Infrastructure
{

View File

@ -1,10 +0,0 @@
using Oqtane.Enums;
using Oqtane.Models;
namespace Oqtane.Infrastructure
{
public interface IMigratable
{
bool Migrate(Tenant tenant, MigrationType migrationType);
}
}

View File

@ -21,6 +21,12 @@ namespace Oqtane.Migrations
pageEntityBuilder.Create();
pageEntityBuilder.AddIndex("IX_Page", new [] {"SiteId", "Path", "UserId"}, true);
//Add Column to Page table (for Sql Server only) we will drop it later for Sql Server only
if (migrationBuilder.ActiveProvider == "Microsoft.EntityFrameworkCore.SqlServer")
{
pageEntityBuilder.AddBooleanColumn("EditMode");
}
//Create Module table
var moduleEntityBuilder = new ModuleEntityBuilder(migrationBuilder);
moduleEntityBuilder.Create();

View File

@ -12,7 +12,7 @@ namespace Oqtane.Migrations
protected override void Up(MigrationBuilder migrationBuilder)
{
//Drop Column from Page table
if (migrationBuilder.ActiveProvider != "Microsoft.EntityFrameworkCore.Sqlite")
if (migrationBuilder.ActiveProvider == "Microsoft.EntityFrameworkCore.SqlServer")
{
var pageEntityBuilder = new PageEntityBuilder(migrationBuilder);
pageEntityBuilder.DropColumn("EditMode");

View File

@ -0,0 +1,27 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Oqtane.Migrations.EntityBuilders;
using Oqtane.Repository;
namespace Oqtane.Migrations
{
[DbContext(typeof(MasterDBContext))]
[Migration("Master.02.01.00.01")]
public class AddDatabaseTypeColumnToTenant : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
//Add Column to Site table
var tenantEntityBuilder = new TenantEntityBuilder(migrationBuilder);
tenantEntityBuilder.AddStringColumn("DBType", 200, true);
//Update new column
migrationBuilder.Sql(
@"
UPDATE Tenant
SET DBType = 'SqlServer'
");
}
}
}

View File

@ -35,7 +35,6 @@ namespace Oqtane.Migrations.EntityBuilders
IsNavigation = table.AddBooleanColumn("IsNavigation");
Url = table.AddStringColumn("Url", 500, true);
LayoutType = table.AddStringColumn("LayoutType", 200);
EditMode = table.AddBooleanColumn("EditMode");
UserId = table.AddIntegerColumn("UserId", true);
IsPersonalizable = table.AddBooleanColumn("IsPersonalizable");
DefaultContainerType = table.AddStringColumn("DefaultContainerType", 200, true);
@ -69,8 +68,6 @@ namespace Oqtane.Migrations.EntityBuilders
public OperationBuilder<AddColumnOperation> LayoutType { get; private set; }
public OperationBuilder<AddColumnOperation> EditMode { get; private set; }
public OperationBuilder<AddColumnOperation> UserId { get; private set; }
public OperationBuilder<AddColumnOperation> IsPersonalizable { get; private set; }

View File

@ -12,7 +12,7 @@ using Oqtane.Enums;
namespace Oqtane.Modules.HtmlText.Manager
{
public class HtmlTextManager : IMigratable, IPortable
public class HtmlTextManager : MigratableModuleBase, IInstallable, IPortable
{
private readonly IHtmlTextRepository _htmlText;
private readonly ISqlRepository _sql;
@ -23,35 +23,6 @@ namespace Oqtane.Modules.HtmlText.Manager
_sql = sql;
}
public bool Migrate(Tenant tenant, MigrationType migrationType)
{
var result = true;
var dbConfig = new DbConfig(null, null) {ConnectionString = tenant.DBConnectionString};
using (var db = new HtmlTextContext(dbConfig, null))
{
try
{
var migrator = db.GetService<IMigrator>();
if (migrationType == MigrationType.Down)
{
migrator.Migrate(Migration.InitialDatabase);
}
else
{
migrator.Migrate();
}
}
catch (Exception e)
{
Console.WriteLine(e);
result = false;
}
}
return result;
}
public string ExportModule(Module module)
{
string content = "";
@ -80,5 +51,17 @@ namespace Oqtane.Modules.HtmlText.Manager
_htmlText.AddHtmlText(htmlText);
}
}
public bool Install(Tenant tenant, string version)
{
var dbConfig = new DbConfig(null, null) {ConnectionString = tenant.DBConnectionString, DatabaseType = tenant.DBType};
return Migrate(new HtmlTextContext(dbConfig, null), tenant, MigrationType.Up);
}
public bool Uninstall(Tenant tenant)
{
var dbConfig = new DbConfig(null, null) {ConnectionString = tenant.DBConnectionString, DatabaseType = tenant.DBType};
return Migrate(new HtmlTextContext(dbConfig, null), tenant, MigrationType.Down);
}
}
}

View File

@ -0,0 +1,42 @@
using System;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Oqtane.Enums;
using Oqtane.Models;
using Oqtane.Modules.HtmlText.Repository;
using Oqtane.Repository;
namespace Oqtane.Modules
{
public class MigratableModuleBase
{
public bool Migrate(DBContextBase dbContext, Tenant tenant, MigrationType migrationType)
{
var result = true;
using (dbContext)
{
try
{
var migrator = dbContext.GetService<IMigrator>();
if (migrationType == MigrationType.Down)
{
migrator.Migrate(Migration.InitialDatabase);
}
else
{
migrator.Migrate();
}
}
catch (Exception e)
{
Console.WriteLine(e);
result = false;
}
}
return result;
}
}
}

View File

@ -7,48 +7,62 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Oqtane.Extensions;
using Oqtane.Models;
using Oqtane.Shared;
// ReSharper disable BuiltInTypeReferenceStyleForMemberAccess
namespace Oqtane.Repository
{
public class DBContextBase : IdentityUserContext<IdentityUser>
{
private readonly IDbConfig _dbConfig;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _accessor;
private readonly IConfiguration _configuration;
private string _connectionString;
private string _databaseType;
public DBContextBase(ITenantResolver tenantResolver, IHttpContextAccessor httpContextAccessor)
{
_connectionString = String.Empty;
_tenantResolver = tenantResolver;
_accessor = httpContextAccessor;
}
public DBContextBase(IDbConfig dbConfig, ITenantResolver tenantResolver)
{
_dbConfig = dbConfig;
_accessor = dbConfig.Accessor;
_configuration = dbConfig.Configuration;
_connectionString = dbConfig.ConnectionString;
_databaseType = dbConfig.DatabaseType;
_tenantResolver = tenantResolver;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var connectionString = _dbConfig.ConnectionString;
if (string.IsNullOrEmpty(connectionString) && _tenantResolver != null)
if (string.IsNullOrEmpty(_connectionString) && _tenantResolver != null)
{
var tenant = _tenantResolver.GetTenant();
var configuration = _dbConfig.Configuration;
if (tenant != null)
{
connectionString = tenant.DBConnectionString
_connectionString = tenant.DBConnectionString
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString());
_databaseType = tenant.DBType;
}
else
{
if (!String.IsNullOrEmpty(configuration.GetConnectionString("DefaultConnection")))
if (!String.IsNullOrEmpty(_configuration.GetConnectionString("DefaultConnection")))
{
connectionString = configuration.GetConnectionString("DefaultConnection")
_connectionString = _configuration.GetConnectionString("DefaultConnection")
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString());
}
_databaseType = _configuration.GetSection(SettingKeys.DatabaseSection)[SettingKeys.DatabaseTypeKey];
}
}
if (!string.IsNullOrEmpty(connectionString))
if (!string.IsNullOrEmpty(_connectionString) && !string.IsNullOrEmpty(_databaseType))
{
optionsBuilder.UseOqtaneDatabase(connectionString);
optionsBuilder.UseOqtaneDatabase(_databaseType, _connectionString);
}
base.OnConfiguring(optionsBuilder);
@ -56,7 +70,7 @@ namespace Oqtane.Repository
public override int SaveChanges()
{
DbContextUtils.SaveChanges(this, _dbConfig.Accessor);
DbContextUtils.SaveChanges(this, _accessor);
return base.SaveChanges();
}

View File

@ -16,5 +16,7 @@ namespace Oqtane.Repository
public IConfiguration Configuration { get; }
public string ConnectionString { get; set; }
public string DatabaseType { get; set; }
}
}

View File

@ -9,14 +9,16 @@ namespace Oqtane.Repository
public class InstallationContext : DbContext
{
private readonly string _connectionString;
private readonly string _databaseType;
public InstallationContext(string connectionString)
public InstallationContext(string databaseType, string connectionString)
{
_connectionString = connectionString;
_databaseType = databaseType;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseOqtaneDatabase(_connectionString);
=> optionsBuilder.UseOqtaneDatabase(_databaseType, _connectionString);
public virtual DbSet<Alias> Alias { get; set; }
public virtual DbSet<Tenant> Tenant { get; set; }

View File

@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore;
using Oqtane.Models;
using Microsoft.Extensions.Configuration;
using Oqtane.Extensions;
using Oqtane.Shared;
// ReSharper disable BuiltInTypeReferenceStyleForMemberAccess
// ReSharper disable UnusedAutoPropertyAccessor.Global
@ -24,6 +25,7 @@ namespace Oqtane.Repository
{
var connectionString = _dbConfig.ConnectionString;
var configuration = _dbConfig.Configuration;
var databaseType = _dbConfig.DatabaseType;
if(string.IsNullOrEmpty(connectionString) && configuration != null)
{
@ -33,11 +35,12 @@ namespace Oqtane.Repository
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString());
}
databaseType = configuration.GetSection(SettingKeys.DatabaseSection)[SettingKeys.DatabaseTypeKey];
}
if (!string.IsNullOrEmpty(connectionString))
{
optionsBuilder.UseOqtaneDatabase(connectionString);
optionsBuilder.UseOqtaneDatabase(databaseType, connectionString);
}
base.OnConfiguring(optionsBuilder);
}

View File

@ -10,5 +10,6 @@ namespace Oqtane.Repository
public IConfiguration Configuration { get; }
public string ConnectionString { get; set; }
public string DatabaseType { get; set; }
}
}

View File

@ -1,10 +1,10 @@
{
"Database": {
"DatabaseType": "",
"DatabaseType": "Sqlite",
"DatabaseEngineVersion": ""
},
"ConnectionStrings": {
"DefaultConnection": "Data Source=.;Initial Catalog=Oqtane-Migrations;Integrated Security=SSPI;"
"DefaultConnection": "Data Source=Oqtane.db"
},
"Runtime": "Server",
"Installation": {

View File

@ -7,6 +7,7 @@ namespace Oqtane.Models
public int TenantId { get; set; }
public string Name { get; set; }
public string DBConnectionString { get; set; }
public string DBType { get; set; }
public string Version { get; set; }
public string CreatedBy { get; set; }
public DateTime CreatedOn { get; set; }

View File

@ -3,6 +3,7 @@ namespace Oqtane.Shared
public class InstallConfig
{
public string ConnectionString { get; set; }
public string DatabaseType { get; set; }
public string Aliases { get; set; }
public string TenantName { get; set; }
public bool IsNewTenant { get; set; }

View File

@ -2,12 +2,18 @@
{
public static class SettingKeys
{
public const string ConnectionStringsSection = "ConnectionStrings";
public const string DatabaseSection = "Database";
public const string InstallationSection = "Installation";
public const string ConnectionStringKey = "DefaultConnection";
public const string DatabaseTypeKey = "DatabaseType";
public const string DefaultAliasKey = "DefaultAlias";
public const string HostPasswordKey = "HostPassword";
public const string HostEmailKey = "HostEmail";
public const string SiteTemplateKey = "SiteTemplate";
public const string ConnectionStringKey = "DefaultConnection";
public const string DefaultThemeKey = "DefaultTheme";
public const string DefaultLayoutKey = "DefaultLayout";
public const string DefaultContainerKey = "DefaultContainer";