diff --git a/src/Umbraco.Core/Persistence/NPocoDatabaseTypeExtensions.cs b/src/Umbraco.Core/Persistence/NPocoDatabaseTypeExtensions.cs index e40e6dedd3..bd44c095fa 100644 --- a/src/Umbraco.Core/Persistence/NPocoDatabaseTypeExtensions.cs +++ b/src/Umbraco.Core/Persistence/NPocoDatabaseTypeExtensions.cs @@ -22,6 +22,11 @@ namespace Umbraco.Core.Persistence return databaseType is NPoco.DatabaseTypes.SqlServer2008DatabaseType; } + public static bool IsSqlServer2012OrLater(this DatabaseType databaseType) + { + return databaseType is NPoco.DatabaseTypes.SqlServer2012DatabaseType; + } + public static bool IsSqlCe(this DatabaseType databaseType) { return databaseType is NPoco.DatabaseTypes.SqlServerCEDatabaseType; diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 4a94ff3ba5..90a2215e3d 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Data.Common; using System.Linq; using NPoco; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -45,6 +46,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax V2012 = 5, V2014 = 6, V2016 = 7, + V2017 = 8, Other = 99 } @@ -71,37 +73,35 @@ namespace Umbraco.Core.Persistence.SqlSyntax public void Initialize() { - var firstPart = string.IsNullOrWhiteSpace(ProductVersion) ? "??" : ProductVersion.Split('.')[0]; - switch (firstPart) - { - case "??": - ProductVersionName = VersionName.Invalid; - break; - case "13": - ProductVersionName = VersionName.V2016; - break; - case "12": - ProductVersionName = VersionName.V2014; - break; - case "11": - ProductVersionName = VersionName.V2012; - break; - case "10": - ProductVersionName = VersionName.V2008; - break; - case "9": - ProductVersionName = VersionName.V2005; - break; - case "8": - ProductVersionName = VersionName.V2000; - break; - case "7": - ProductVersionName = VersionName.V7; - break; - default: - ProductVersionName = VersionName.Other; - break; - } + ProductVersionName = MapProductVersion(ProductVersion); + } + } + + private static VersionName MapProductVersion(string productVersion) + { + var firstPart = string.IsNullOrWhiteSpace(productVersion) ? "??" : productVersion.Split('.')[0]; + switch (firstPart) + { + case "??": + return VersionName.Invalid; + case "14": + return VersionName.V2017; + case "13": + return VersionName.V2016; + case "12": + return VersionName.V2014; + case "11": + return VersionName.V2012; + case "10": + return VersionName.V2008; + case "9": + return VersionName.V2005; + case "8": + return VersionName.V2000; + case "7": + return VersionName.V7; + default: + return VersionName.Other; } } @@ -136,6 +136,33 @@ namespace Umbraco.Core.Persistence.SqlSyntax } } + internal static VersionName GetVersionName(string connectionString, string providerName) + { + var factory = DbProviderFactories.GetFactory(providerName); + var connection = factory.CreateConnection(); + + if (connection == null) + throw new InvalidOperationException($"Could not create a connection for provider \"{providerName}\"."); + + connection.ConnectionString = connectionString; + using (connection) + { + try + { + connection.Open(); + var command = connection.CreateCommand(); + command.CommandText = "SELECT SERVERPROPERTY('ProductVersion');"; + var productVersion = command.ExecuteScalar().ToString(); + connection.Close(); + return MapProductVersion(productVersion); + } + catch + { + return VersionName.Unknown; + } + } + } + /// /// SQL Server stores default values assigned to columns as constraints, it also stores them with named values, this is the only /// server type that does this, therefore this method doesn't exist on any other syntax provider diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs b/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs index 2b82ec7fac..1e67993895 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs @@ -39,6 +39,7 @@ namespace Umbraco.Core.Persistence private string _providerName; private DbProviderFactory _dbProviderFactory; private DatabaseType _databaseType; + private bool _serverVersionDetected; private ISqlSyntaxProvider _sqlSyntax; private RetryPolicy _connectionRetryPolicy; private RetryPolicy _commandRetryPolicy; @@ -112,7 +113,56 @@ namespace Umbraco.Core.Persistence public bool Configured { get; private set; } /// - public bool CanConnect => Configured && DbConnectionExtensions.IsConnectionAvailable(_connectionString, _providerName); + public bool CanConnect + { + get + { + if (!Configured || !DbConnectionExtensions.IsConnectionAvailable(_connectionString, _providerName)) return false; + + if (_serverVersionDetected) return true; + + if (_databaseType.IsSqlServer()) + DetectSqlServerVersion(); + _serverVersionDetected = true; + + return true; + } + } + + private void DetectSqlServerVersion() + { + // replace NPoco database type by a more efficient one + + var setting = ConfigurationManager.AppSettings["Umbraco.DatabaseFactory.ServerVersion"]; + var fromSettings = false; + + if (setting.IsNullOrWhiteSpace() || !setting.StartsWith("SqlServer.") + || !Enum.TryParse(setting.Substring("SqlServer.".Length), out var versionName, true)) + { + versionName = SqlServerSyntaxProvider.GetVersionName(_connectionString, _providerName); + } + else + { + fromSettings = true; + } + + switch (versionName) + { + case SqlServerSyntaxProvider.VersionName.V2008: + _databaseType = DatabaseType.SqlServer2008; + break; + case SqlServerSyntaxProvider.VersionName.V2012: + case SqlServerSyntaxProvider.VersionName.V2014: + case SqlServerSyntaxProvider.VersionName.V2016: + case SqlServerSyntaxProvider.VersionName.V2017: + _databaseType = DatabaseType.SqlServer2012; + break; + // else leave unchanged + } + + _logger.Debug("SqlServer {SqlServerVersion}, DatabaseType is {DatabaseType} ({Source}).", + versionName, _databaseType, fromSettings ? "settings" : "detected"); + } /// public ISqlContext SqlContext => _sqlContext;