diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index 36da6007f6..622dcf564d 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -381,8 +381,6 @@ namespace Umbraco.Core connString = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ConnectionString; } Initialize(providerName, connString); - - DetermineSqlServerVersion(); } else if (ConfigurationManager.AppSettings.ContainsKey(GlobalSettings.UmbracoConnectionName) && string.IsNullOrEmpty(ConfigurationManager.AppSettings[GlobalSettings.UmbracoConnectionName]) == false) { @@ -419,8 +417,6 @@ namespace Umbraco.Core //Remove the legacy connection string, so we don't end up in a loop if something goes wrong. GlobalSettings.RemoveSetting(GlobalSettings.UmbracoConnectionName); - - DetermineSqlServerVersion(); } else { @@ -465,48 +461,6 @@ namespace Umbraco.Core Initialize(providerName); } - /// - /// Set the lazy resolution of determining the SQL server version if that is the db type we're using - /// - private void DetermineSqlServerVersion() - { - - var sqlServerSyntax = SqlSyntax as SqlServerSyntaxProvider; - if (sqlServerSyntax != null) - { - //this will not execute now, it is lazy so will only execute when we need to actually know - // the sql server version. - sqlServerSyntax.VersionName = new Lazy(() => - { - try - { - var database = this._factory.CreateDatabase(); - - var version = database.ExecuteScalar("SELECT SERVERPROPERTY('productversion')"); - var firstPart = version.Split('.')[0]; - switch (firstPart) - { - case "11": - return SqlServerVersionName.V2012; - case "10": - return SqlServerVersionName.V2008; - case "9": - return SqlServerVersionName.V2005; - case "8": - return SqlServerVersionName.V2000; - case "7": - return SqlServerVersionName.V7; - default: - return SqlServerVersionName.Other; - } - } - catch (Exception) - { - return SqlServerVersionName.Invalid; - } - }); - } - } internal DatabaseSchemaResult ValidateDatabaseSchema() { diff --git a/src/Umbraco.Core/Persistence/PetaPoco.cs b/src/Umbraco.Core/Persistence/PetaPoco.cs index 8a32deb331..4f0414ea3b 100644 --- a/src/Umbraco.Core/Persistence/PetaPoco.cs +++ b/src/Umbraco.Core/Persistence/PetaPoco.cs @@ -181,7 +181,7 @@ namespace Umbraco.Core.Persistence CommonConstruct(); } - enum DBType + internal enum DBType { SqlServer, SqlServerCE, @@ -726,6 +726,42 @@ namespace Umbraco.Core.Persistence return true; } + /// + /// NOTE: This is a custom mod of PetaPoco!! This builds the paging sql for different db providers + /// + /// + /// + /// + /// + /// + /// + /// + /// + internal virtual void BuildSqlDbSpecificPagingQuery(DBType databaseType, long skip, long take, string sql, string sqlSelectRemoved, string sqlOrderBy, ref object[] args, out string sqlPage) + { + if (databaseType == DBType.SqlServer || databaseType == DBType.Oracle) + { + sqlSelectRemoved = rxOrderBy.Replace(sqlSelectRemoved, ""); + if (rxDistinct.IsMatch(sqlSelectRemoved)) + { + sqlSelectRemoved = "peta_inner.* FROM (SELECT " + sqlSelectRemoved + ") peta_inner"; + } + sqlPage = string.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) peta_rn, {1}) peta_paged WHERE peta_rn>@{2} AND peta_rn<=@{3}", + sqlOrderBy == null ? "ORDER BY (SELECT NULL)" : sqlOrderBy, sqlSelectRemoved, args.Length, args.Length + 1); + args = args.Concat(new object[] { skip, skip + take }).ToArray(); + } + else if (databaseType == DBType.SqlServerCE) + { + sqlPage = string.Format("{0}\nOFFSET @{1} ROWS FETCH NEXT @{2} ROWS ONLY", sql, args.Length, args.Length + 1); + args = args.Concat(new object[] { skip, take }).ToArray(); + } + else + { + sqlPage = string.Format("{0}\nLIMIT @{1} OFFSET @{2}", sql, args.Length, args.Length + 1); + args = args.Concat(new object[] { take, skip }).ToArray(); + } + } + public void BuildPageQueries(long skip, long take, string sql, ref object[] args, out string sqlCount, out string sqlPage) { // Add auto select clause @@ -734,34 +770,12 @@ namespace Umbraco.Core.Persistence // Split the SQL into the bits we need string sqlSelectRemoved, sqlOrderBy; - if (!SplitSqlForPaging(sql, out sqlCount, out sqlSelectRemoved, out sqlOrderBy)) + if (SplitSqlForPaging(sql, out sqlCount, out sqlSelectRemoved, out sqlOrderBy) == false) throw new Exception("Unable to parse SQL statement for paged query"); if (_dbType == DBType.Oracle && sqlSelectRemoved.StartsWith("*")) throw new Exception("Query must alias '*' when performing a paged query.\neg. select t.* from table t order by t.id"); - - // Build the SQL for the actual final result - if (_dbType == DBType.SqlServer || _dbType == DBType.Oracle) - { - sqlSelectRemoved = rxOrderBy.Replace(sqlSelectRemoved, ""); - if (rxDistinct.IsMatch(sqlSelectRemoved)) - { - sqlSelectRemoved = "peta_inner.* FROM (SELECT " + sqlSelectRemoved + ") peta_inner"; - } - sqlPage = string.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) peta_rn, {1}) peta_paged WHERE peta_rn>@{2} AND peta_rn<=@{3}", - sqlOrderBy==null ? "ORDER BY (SELECT NULL)" : sqlOrderBy, sqlSelectRemoved, args.Length, args.Length + 1); - args = args.Concat(new object[] { skip, skip+take }).ToArray(); - } - else if (_dbType == DBType.SqlServerCE) - { - sqlPage = string.Format("{0}\nOFFSET @{1} ROWS FETCH NEXT @{2} ROWS ONLY", sql, args.Length, args.Length + 1); - args = args.Concat(new object[] { skip, take }).ToArray(); - } - else - { - sqlPage = string.Format("{0}\nLIMIT @{1} OFFSET @{2}", sql, args.Length, args.Length + 1); - args = args.Concat(new object[] { take, skip }).ToArray(); - } - + + BuildSqlDbSpecificPagingQuery(_dbType, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage); } // Fetch a page @@ -2315,8 +2329,8 @@ namespace Umbraco.Core.Persistence // Member variables - string _connectionString; - string _providerName; + readonly string _connectionString; + readonly string _providerName; DbProviderFactory _factory; IDbConnection _sharedConnection; IDbTransaction _transaction; diff --git a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs index 6b8e7b46ff..a72621d1a5 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs @@ -203,7 +203,8 @@ namespace Umbraco.Core.Persistence { //if it is sql ce or it is a sql server version less than 2008, we need to do individual inserts. var sqlServerSyntax = SqlSyntaxContext.SqlSyntaxProvider as SqlServerSyntaxProvider; - if ((sqlServerSyntax != null && (int)sqlServerSyntax.VersionName.Value < (int)SqlServerVersionName.V2008) + + if ((sqlServerSyntax != null && (int)sqlServerSyntax.GetVersionName(db) < (int)SqlServerVersionName.V2008) || SqlSyntaxContext.SqlSyntaxProvider is SqlCeSyntaxProvider) { //SqlCe doesn't support bulk insert statements! diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 57bc14aebf..2a873f01bb 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -14,12 +14,57 @@ namespace Umbraco.Core.Persistence.SqlSyntax public SqlServerSyntaxProvider() { - } + } /// /// Gets/sets the version of the current SQL server instance /// - internal Lazy VersionName { get; set; } + internal SqlServerVersionName GetVersionName(Database database) + { + if (_versionName.HasValue) + return _versionName.Value; + + try + { + var version = database.ExecuteScalar("SELECT SERVERPROPERTY('productversion')"); + var firstPart = version.Split('.')[0]; + switch (firstPart) + { + case "13": + _versionName = SqlServerVersionName.V2014; + break; + case "12": + _versionName = SqlServerVersionName.V2014; + break; + case "11": + _versionName = SqlServerVersionName.V2012; + break; + case "10": + _versionName = SqlServerVersionName.V2008; + break; + case "9": + _versionName = SqlServerVersionName.V2005; + break; + case "8": + _versionName = SqlServerVersionName.V2000; + break; + case "7": + _versionName = SqlServerVersionName.V7; + break; + default: + _versionName = SqlServerVersionName.Other; + break; + } + } + catch (Exception) + { + _versionName = SqlServerVersionName.Invalid; + } + + return _versionName.Value; + } + + private SqlServerVersionName? _versionName; /// /// SQL Server stores default values assigned to columns as constraints, it also stores them with named values, this is the only diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerVersionName.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerVersionName.cs index 37870a9536..2f50f39435 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerVersionName.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerVersionName.cs @@ -3,6 +3,9 @@ /// /// Represents the version name of SQL server (i.e. the year 2008, 2005, etc...) /// + /// + /// see: https://support.microsoft.com/en-us/kb/321185 + /// internal enum SqlServerVersionName { Invalid = -1, @@ -11,6 +14,8 @@ V2005 = 2, V2008 = 3, V2012 = 4, - Other = 5 + V2014 = 5, + V2016 = 6, + Other = 100 } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs index a0490348f3..5ca30f2860 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text; using StackExchange.Profiling; using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence { @@ -154,5 +155,41 @@ namespace Umbraco.Core.Persistence } base.OnExecutedCommand(cmd); } + + /// + /// We are overriding this in the case that we are using SQL Server 2012+ so we can make paging more efficient than the default PetaPoco paging + /// see: http://issues.umbraco.org/issue/U4-8837 + /// + /// + /// + /// + /// + /// + /// + /// + /// + internal override void BuildSqlDbSpecificPagingQuery(DBType databaseType, long skip, long take, string sql, string sqlSelectRemoved, string sqlOrderBy, ref object[] args, out string sqlPage) + { + if (databaseType == DBType.SqlServer) + { + //we need to check it's version to see what kind of paging format we can use + //TODO: This is a hack, but we don't have access to the SqlSyntaxProvider here, we can in v8 but not now otherwise + // this would be a breaking change. + var sqlServerSyntax = SqlSyntaxContext.SqlSyntaxProvider as SqlServerSyntaxProvider; + if (sqlServerSyntax != null) + { + if ((int) sqlServerSyntax.GetVersionName(this) >= (int) SqlServerVersionName.V2012) + { + //we can use the good paging! to do that we are going to change the databaseType to SQLCE since + //it also uses the good paging syntax. + base.BuildSqlDbSpecificPagingQuery(DBType.SqlServerCE, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage); + return; + } + } + } + + //use the defaults + base.BuildSqlDbSpecificPagingQuery(databaseType, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage); + } } } \ No newline at end of file