Add LocalDB database option to installer

This commit is contained in:
Ronald Barendse
2021-09-17 12:50:25 +02:00
parent 785630922e
commit 81cbbd8614
7 changed files with 107 additions and 81 deletions

View File

@@ -1,4 +1,4 @@
using System.Runtime.Serialization;
using System.Runtime.Serialization;
namespace Umbraco.Cms.Core.Install.Models
{
@@ -8,7 +8,7 @@ namespace Umbraco.Cms.Core.Install.Models
public DatabaseModel()
{
//defaults
DatabaseType = DatabaseType.SqlCe;
DatabaseType = DatabaseType.SqlLocalDb;
}
[DataMember(Name = "dbType")]

View File

@@ -1,7 +1,8 @@
namespace Umbraco.Cms.Core.Install.Models
namespace Umbraco.Cms.Core.Install.Models
{
public enum DatabaseType
{
SqlLocalDb,
SqlCe,
SqlServer,
SqlAzure,

View File

@@ -7,6 +7,7 @@ using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Install;
using Umbraco.Cms.Core.Install.Models;
using Umbraco.Cms.Infrastructure.Migrations.Install;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Install.InstallSteps
@@ -39,7 +40,9 @@ namespace Umbraco.Cms.Infrastructure.Install.InstallSteps
{
throw new InstallException("Could not connect to the database");
}
ConfigureConnection(database);
return Task.FromResult<InstallSetupResult>(null);
}
@@ -49,6 +52,10 @@ namespace Umbraco.Cms.Infrastructure.Install.InstallSteps
{
_databaseBuilder.ConfigureDatabaseConnection(database.ConnectionString);
}
else if (database.DatabaseType == DatabaseType.SqlLocalDb)
{
_databaseBuilder.ConfigureSqlLocalDbDatabaseConnection();
}
else if (database.DatabaseType == DatabaseType.SqlCe)
{
_databaseBuilder.ConfigureEmbeddedDatabaseConnection();
@@ -62,9 +69,7 @@ namespace Umbraco.Cms.Infrastructure.Install.InstallSteps
var password = database.Password.Replace("'", "''");
password = string.Format("'{0}'", password);
_databaseBuilder.ConfigureDatabaseConnection(
database.Server, database.DatabaseName, database.Login, password,
database.DatabaseType.ToString());
_databaseBuilder.ConfigureDatabaseConnection(database.Server, database.DatabaseName, database.Login, password, database.DatabaseType.ToString());
}
}
@@ -84,28 +89,31 @@ namespace Umbraco.Cms.Infrastructure.Install.InstallSteps
databases.Insert(0, new { name = "Microsoft SQL Server Compact (SQL CE)", id = DatabaseType.SqlCe.ToString() });
}
if (IsLocalDbAvailable())
{
// Ensure this is always inserted as first when available
databases.Insert(0, new { name = "Microsoft SQL Server Express (LocalDB)", id = DatabaseType.SqlLocalDb.ToString() });
}
return new
{
databases = databases
databases
};
}
}
public static bool IsSqlCeAvailable()
{
public static bool IsLocalDbAvailable() => new LocalDb().IsAvailable;
public static bool IsSqlCeAvailable() =>
// NOTE: Type.GetType will only return types that are currently loaded into the appdomain. In this case
// that is ok because we know if this is availalbe we will have manually loaded it into the appdomain.
// Else we'd have to use Assembly.LoadFrom and need to know the DLL location here which we don't need to do.
return !(Type.GetType("Umbraco.Cms.Persistence.SqlCe.SqlCeSyntaxProvider, Umbraco.Persistence.SqlCe") is null);
}
!(Type.GetType("Umbraco.Cms.Persistence.SqlCe.SqlCeSyntaxProvider, Umbraco.Persistence.SqlCe") is null);
public override string View => ShouldDisplayView() ? base.View : "";
public override bool RequiresExecution(DatabaseModel model)
{
return ShouldDisplayView();
}
public override bool RequiresExecution(DatabaseModel model) => ShouldDisplayView();
private bool ShouldDisplayView()
{
@@ -118,6 +126,7 @@ namespace Umbraco.Cms.Infrastructure.Install.InstallSteps
{
//Since a connection string was present we verify the db can connect and query
_ = _databaseBuilder.ValidateSchema();
return false;
}
catch (Exception ex)

View File

@@ -1,11 +1,14 @@
using System;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Install;
using Umbraco.Cms.Core.Install.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Migrations.Install;
using Umbraco.Cms.Infrastructure.Persistence;
namespace Umbraco.Cms.Infrastructure.Install.InstallSteps
{
@@ -15,11 +18,13 @@ namespace Umbraco.Cms.Infrastructure.Install.InstallSteps
{
private readonly DatabaseBuilder _databaseBuilder;
private readonly IRuntimeState _runtime;
private readonly IOptionsMonitor<GlobalSettings> _globalSettings;
public DatabaseInstallStep(DatabaseBuilder databaseBuilder, IRuntimeState runtime)
public DatabaseInstallStep(DatabaseBuilder databaseBuilder, IRuntimeState runtime, IOptionsMonitor<GlobalSettings> globalSettings)
{
_databaseBuilder = databaseBuilder;
_runtime = runtime;
_globalSettings = globalSettings;
}
public override Task<InstallSetupResult> ExecuteAsync(object model)
@@ -27,6 +32,11 @@ namespace Umbraco.Cms.Infrastructure.Install.InstallSteps
if (_runtime.Level == RuntimeLevel.Run)
throw new Exception("Umbraco is already configured!");
if (_globalSettings.CurrentValue.InstallMissingDatabase)
{
_databaseBuilder.CreateDatabase();
}
var result = _databaseBuilder.CreateSchemaAndData();
if (result.Success == false)

View File

@@ -1,6 +1,4 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration;
@@ -85,7 +83,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
public bool CanConnect(string databaseType, string connectionString, string server, string database, string login, string password, bool integratedAuth)
{
// we do not test SqlCE connection
if (databaseType.InvariantContains("sqlce"))
if (databaseType.InvariantContains("sqlce") || databaseType.InvariantContains("localdb"))
return true;
string providerName;
@@ -153,23 +151,32 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
/// </summary>
public void ConfigureEmbeddedDatabaseConnection()
{
ConfigureEmbeddedDatabaseConnection(_databaseFactory);
const string connectionString = EmbeddedDatabaseConnectionString;
const string providerName = Constants.DbProviderNames.SqlCe;
_configManipulator.SaveConnectionString(connectionString, providerName);
_databaseFactory.Configure(connectionString, providerName);
// Always create embedded database
CreateDatabase();
}
private void ConfigureEmbeddedDatabaseConnection(IUmbracoDatabaseFactory factory)
public const string LocalDbConnectionString = @"Server=(localdb)\MSSQLLocalDB;Integrated Security=true;AttachDbFileName=|DataDirectory|\Umbraco.mdf";
public void ConfigureSqlLocalDbDatabaseConnection()
{
_configManipulator.SaveConnectionString(EmbeddedDatabaseConnectionString, Constants.DbProviderNames.SqlCe);
string connectionString = LocalDbConnectionString;
const string providerName = Constants.DbProviderNames.SqlServer;
var path = _hostingEnvironment.MapPathContentRoot(Path.Combine(Constants.SystemDirectories.Data, "Umbraco.sdf"));
if (File.Exists(path) == false)
{
// this should probably be in a "using (new SqlCeEngine)" clause but not sure
// of the side effects and it's been like this for quite some time now
// Replace data directory placeholder (this is not supported by LocalDB)
var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString();
connectionString = connectionString.Replace("|DataDirectory|", dataDirectory);
_dbProviderFactoryCreator.CreateDatabase(Constants.DbProviderNames.SqlCe);
}
_configManipulator.SaveConnectionString(connectionString, providerName);
_databaseFactory.Configure(connectionString, providerName);
factory.Configure(EmbeddedDatabaseConnectionString, Constants.DbProviderNames.SqlCe);
// Always create LocalDB database
CreateDatabase();
}
/// <summary>
@@ -214,9 +221,10 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
public static string GetDatabaseConnectionString(string server, string databaseName, string user, string password, string databaseProvider, out string providerName)
{
providerName = Constants.DbProviderNames.SqlServer;
var provider = databaseProvider.ToLower();
if (provider.InvariantContains("azure"))
if (databaseProvider.InvariantContains("Azure"))
return GetAzureConnectionString(server, databaseName, user, password);
return $"server={server};database={databaseName};user id={user};password={password}";
}
@@ -228,8 +236,10 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
public void ConfigureIntegratedSecurityDatabaseConnection(string server, string databaseName)
{
var connectionString = GetIntegratedSecurityDatabaseConnectionString(server, databaseName);
_configManipulator.SaveConnectionString(connectionString, Constants.DbProviderNames.SqlServer);
_databaseFactory.Configure(connectionString, Constants.DbProviderNames.SqlServer);
const string providerName = Constants.DbProviderNames.SqlServer;
_configManipulator.SaveConnectionString(connectionString, providerName);
_databaseFactory.Configure(connectionString, providerName);
}
/// <summary>
@@ -292,18 +302,14 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
return $"Server={server};Database={databaseName};User ID={user};Password={password}";
}
private static bool ServerStartsWithTcp(string server)
{
return server.ToLower().StartsWith("tcp:".ToLower());
}
private static bool ServerStartsWithTcp(string server) => server.InvariantStartsWith("tcp:");
#endregion
#region Database Schema
public void CreateDatabase() => _dbProviderFactoryCreator.CreateDatabase(_databaseFactory.ProviderName, _databaseFactory.ConnectionString);
/// <summary>
/// Validates the database schema.
/// </summary>

View File

@@ -96,7 +96,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence
_loggerFactory = loggerFactory;
var settings = connectionStrings.CurrentValue.UmbracoConnectionString;
if (settings == null)
{
logger.LogDebug("Missing connection string, defer configuration.");
@@ -105,9 +104,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence
// could as well be <add name="umbracoDbDSN" connectionString="" providerName="" />
// so need to test the values too
var connectionString = settings.ConnectionString;
var providerName = settings.ProviderName;
if (string.IsNullOrWhiteSpace(connectionString) || string.IsNullOrWhiteSpace(providerName))
if (settings.IsConnectionStringConfigured() == false)
{
logger.LogDebug("Empty connection string or provider name, defer configuration.");
return; // not configured
@@ -148,7 +145,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence
private void UpdateSqlServerDatabaseType()
{
// replace NPoco database type by a more efficient one
var setting = _globalSettings.Value.DatabaseFactoryServerVersion;
var fromSettings = false;
@@ -188,6 +184,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence
{
// must be initialized to have a context
EnsureInitialized();
return _sqlContext;
}
}
@@ -199,15 +196,13 @@ namespace Umbraco.Cms.Infrastructure.Persistence
{
// must be initialized to have a bulk insert provider
EnsureInitialized();
return _bulkSqlInsertProvider;
}
}
/// <inheritdoc />
public void ConfigureForUpgrade()
{
_upgrading = true;
}
public void ConfigureForUpgrade() => _upgrading = true;
/// <inheritdoc />
public void Configure(string connectionString, string providerName)
@@ -248,13 +243,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence
throw new Exception($"Can't find a provider factory for provider name \"{_providerName}\".");
}
// cannot initialize without being able to talk to the database
// TODO: Why not?
if (!DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DbProviderFactory))
{
throw new Exception("Cannot connect to the database.");
}
_connectionRetryPolicy = RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(ConnectionString);
_commandRetryPolicy = RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(ConnectionString);

View File

@@ -10,38 +10,48 @@
<legend>What type of database do you use?</legend>
<label class="control-label" for="dbType">Database type</label>
<div class="controls">
<select id="dbType" name="dbType"
<select id="dbType"
ng-options="db.id as db.name for db in dbs"
required ng-model="installer.current.model.dbType">
required
ng-model="installer.current.model.dbType">
</select>
</div>
</div>
<div class="controls" ng-if="installer.current.model.dbType == 'SqlCe' ">
<div class="controls" ng-if="installer.current.model.dbType == 'SqlLocalDb'">
<p>Great! No need to configure anything, you can simply click the <strong>continue</strong> button below to continue to the next step</p>
</div>
<div ng-if="installer.current.model.dbType == 'Custom' ">
<div class="controls" ng-if="installer.current.model.dbType == 'SqlCe'">
<p>Great! No need to configure anything, you can simply click the <strong>continue</strong> button below to continue to the next step</p>
</div>
<div ng-if="installer.current.model.dbType == 'Custom'">
<legend>What is the exact connection string we should use?</legend>
<div class="control-group">
<label class="control-label" for="server">Connection string</label>
<label class="control-label" for="Custom_connectionString">Connection string</label>
<div class="controls">
<textarea class="input-block-level" required ng-model="installer.current.model.connectionString" rows="5"></textarea>
<textarea id="Custom_connectionString"
class="input-block-level"
required
ng-model="installer.current.model.connectionString"
rows="5"></textarea>
<small class="inline-help">Enter a valid database connection string.</small>
</div>
</div>
</div>
<div ng-if="installer.current.model.dbType == 'SqlAzure' || installer.current.model.dbType == 'SqlServer' ">
<div ng-if="installer.current.model.dbType == 'SqlAzure' || installer.current.model.dbType == 'SqlServer'">
<div class="row">
<legend>Where do we find your database?</legend>
<div class="span6">
<div class="control-group">
<label class="control-label" for="server">Server</label>
<label class="control-label" for="Sql_Server">Server</label>
<div class="controls">
<input type="text" id="server" name="server"
placeholder="{{ (installer.current.model.dbType == 'SqlAzure') ? 'umbraco-database.database.windows.net' : '127.0.0.1\\SQLEXPRESS'}}"
required ng-model="installer.current.model.server" />
<input type="text" id="Sql_Server"
placeholder="{{ (installer.current.model.dbType == 'SqlAzure') ? 'umbraco-database.database.windows.net' : '(local)\\SQLEXPRESS'}}"
required
ng-model="installer.current.model.server" />
<small class="inline-help">Enter server domain or IP</small>
</div>
</div>
@@ -49,11 +59,12 @@
<div class="span6">
<div class="control-group">
<label class="control-label" for="databaseName">Database name</label>
<label class="control-label" for="Sql_databaseName">Database name</label>
<div class="controls">
<input type="text" id="databaseName" name="installer.current.model.databaseName"
<input type="text" id="Sql_databaseName"
placeholder="umbraco-cms"
required ng-model="installer.current.model.databaseName" />
required
ng-model="installer.current.model.databaseName" />
<small class="inline-help">Enter the name of the database</small>
</div>
</div>
@@ -64,11 +75,12 @@
<legend>What credentials are used to access the database?</legend>
<div class="span6">
<div class="control-group" ng-if="!installer.current.model.integratedAuth">
<label class="control-label" for="login">Login</label>
<label class="control-label" for="Sql_login">Login</label>
<div class="controls">
<input type="text" id="login" name="login"
<input type="text" id="Sql_login"
placeholder="umbraco-db-user"
required ng-model="installer.current.model.login" />
required
ng-model="installer.current.model.login" />
<small class="inline-help">Enter the database user name</small>
</div>
</div>
@@ -76,21 +88,21 @@
<div class="span6">
<div class="control-group" ng-if="!installer.current.model.integratedAuth">
<label class="control-label" for="password">Password</label>
<label class="control-label" for="Sql_password">Password</label>
<div class="controls">
<input type="password" id="password" name="password"
<input type="password" id="Sql_password"
placeholder="umbraco-db-password"
required ng-model="installer.current.model.password" />
required
ng-model="installer.current.model.password" />
<small class="inline-help">Enter the database password</small>
</div>
</div>
</div>
<div class="span12 control-group" ng-if="installer.current.model.dbType =='SqlServer' ">
<div class="span12 control-group" ng-if="installer.current.model.dbType == 'SqlServer'">
<div class="controls">
<label class="checkbox">
<input type="checkbox" id="integratedAuth" name="integratedAuth"
ng-model="installer.current.model.integratedAuth" />
<input type="checkbox" ng-model="installer.current.model.integratedAuth" />
Use integrated authentication
</label>
</div>