V12: Replace ntext with nvarchar(max) in database (#13351)

* Update UnPublishedContentPropertyCacheCompressionOptions to also accept Nvarchar

* Update DTOs to use nvarchar(max)

* Add Migration

* Simplify migration a bit

* Refactor column migration for reuse

* Add remaning column migrations

* Support non-nullable column

* Make it idempotent

* Skip SQLite

* Revert "Update UnPublishedContentPropertyCacheCompressionOptions to also accept Nvarchar"

This reverts commit d432d165de02814d12d4e7736458b69ab60137d8.

* Obsolete NTEXT

* Ignore case when checking column type
This commit is contained in:
Mole
2022-11-08 12:26:40 +01:00
committed by GitHub
parent 8074c3fb62
commit 54131605be
10 changed files with 106 additions and 6 deletions

View File

@@ -6,6 +6,7 @@ using Umbraco.Cms.Infrastructure.Migrations.Upgrade.Common;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_10_0_0;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_10_2_0;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_10_3_0;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_12_0_0;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_0_0;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_0_1;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_1_0;
@@ -297,5 +298,8 @@ public class UmbracoPlan : MigrationPlan
// To 11.0.0/10.4.0
To<AddBlockGridPartialViews>("{56833770-3B7E-4FD5-A3B6-3416A26A7A3F}");
// To 12.0.0
To<UseNvarcharInsteadOfNText>("{888A0D5D-51E4-4C7E-AA0A-01306523C7FB}");
}
}

View File

@@ -0,0 +1,94 @@
using System.Linq.Expressions;
using System.Text;
using NPoco;
using Umbraco.Cms.Core;
using Umbraco.Cms.Infrastructure.Migrations.Expressions.Create.Column;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_12_0_0;
public class UseNvarcharInsteadOfNText : MigrationBase
{
public UseNvarcharInsteadOfNText(IMigrationContext context)
: base(context)
{
}
protected override void Migrate()
{
// We don't need to run this migration for SQLite, since ntext is not a thing there, text is just text.
if (DatabaseType == DatabaseType.SQLite)
{
return;
}
MigrateNtextColumn<ContentNuDto>("data", Constants.DatabaseSchema.Tables.NodeData, x => x.Data);
MigrateNtextColumn<CacheInstructionDto>("jsonInstruction", Constants.DatabaseSchema.Tables.CacheInstruction, x => x.Instructions, false);
MigrateNtextColumn<DataTypeDto>("config", Constants.DatabaseSchema.Tables.DataType, x => x.Configuration);
MigrateNtextColumn<ExternalLoginDto>("userData", Constants.DatabaseSchema.Tables.ExternalLogin, x => x.UserData);
MigrateNtextColumn<UserDto>("tourData", Constants.DatabaseSchema.Tables.User, x => x.TourData);
MigrateNtextColumn<PropertyDataDto>("textValue", Constants.DatabaseSchema.Tables.PropertyData, x => x.TextValue);
}
private void MigrateNtextColumn<TDto>(string columnName, string tableName, Expression<Func<TDto, object?>> fieldSelector, bool nullable = true)
{
var columnType = ColumnType(tableName, columnName);
if (columnType is null || columnType.Equals("ntext", StringComparison.InvariantCultureIgnoreCase) is false)
{
return;
}
var oldColumnName = $"Old{columnName}";
// Rename the column so we can create the new one and copy over the data.
Rename
.Column(columnName)
.OnTable(tableName)
.To(oldColumnName)
.Do();
// Create new column with the correct type
// This is pretty ugly, but we have to do ti this way because the CacheInstruction.Instruction column doesn't support nullable.
// So we have to populate with some temporary placeholder value before we copy over the actual data.
ICreateColumnOptionBuilder builder = Create
.Column(columnName)
.OnTable(tableName)
.AsCustom("nvarchar(max)");
if (nullable is false)
{
builder
.NotNullable()
.WithDefaultValue("Placeholder");
}
else
{
builder.Nullable();
}
builder.Do();
// Copy over data NPOCO doesn't support this for some reason, so we'll have to do it like so
// While we're add it we'll also set all the old values to be NULL since it's recommended here:
// https://learn.microsoft.com/en-us/sql/t-sql/data-types/ntext-text-and-image-transact-sql?view=sql-server-ver16#remarks
StringBuilder queryBuilder = new StringBuilder()
.AppendLine($"UPDATE {tableName}")
.AppendLine("SET")
.Append($"\t{SqlSyntax.GetFieldNameForUpdate(fieldSelector)} = {SqlSyntax.GetQuotedTableName(tableName)}.{SqlSyntax.GetQuotedColumnName(oldColumnName)}");
if (nullable)
{
queryBuilder.AppendLine($"\n,\t{SqlSyntax.GetQuotedColumnName(oldColumnName)} = NULL");
}
Sql<ISqlContext> copyDataQuery = Database.SqlContext.Sql(queryBuilder.ToString());
Database.Execute(copyDataQuery);
// Delete old column
Delete
.Column(oldColumnName)
.FromTable(tableName)
.Do();
}
}

View File

@@ -20,6 +20,7 @@ public struct SpecialDbType : IEquatable<SpecialDbType>
public SpecialDbType(SpecialDbTypes specialDbTypes)
=> _dbType = specialDbTypes.ToString();
[Obsolete("Use NVARCHARMAX instead")]
public static SpecialDbType NTEXT { get; } = new(SpecialDbTypes.NTEXT);
public static SpecialDbType NCHAR { get; } = new(SpecialDbTypes.NCHAR);

View File

@@ -5,6 +5,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
/// </summary>
public enum SpecialDbTypes
{
[Obsolete("Use NVARCHARMAX instead")]
NTEXT,
NCHAR,
NVARCHARMAX,

View File

@@ -19,7 +19,7 @@ public class CacheInstructionDto
public DateTime UtcStamp { get; set; }
[Column("jsonInstruction")]
[SpecialDbType(SpecialDbTypes.NTEXT)]
[SpecialDbType(SpecialDbTypes.NVARCHARMAX)]
[NullSetting(NullSetting = NullSettings.NotNull)]
public string Instructions { get; set; } = null!;

View File

@@ -25,7 +25,7 @@ public class ContentNuDto
/// Pretty much anything that would require a 1:M lookup is serialized here
/// </remarks>
[Column("data")]
[SpecialDbType(SpecialDbTypes.NTEXT)]
[SpecialDbType(SpecialDbTypes.NVARCHARMAX)]
[NullSetting(NullSetting = NullSettings.Null)]
public string? Data { get; set; }

View File

@@ -22,7 +22,7 @@ public class DataTypeDto
public string DbType { get; set; } = null!;
[Column("config")]
[SpecialDbType(SpecialDbTypes.NTEXT)]
[SpecialDbType(SpecialDbTypes.NVARCHARMAX)]
[NullSetting(NullSetting = NullSettings.Null)]
public string? Configuration { get; set; }

View File

@@ -51,6 +51,6 @@ internal class ExternalLoginDto
/// </summary>
[Column("userData")]
[NullSetting(NullSetting = NullSettings.Null)]
[SpecialDbType(SpecialDbTypes.NTEXT)]
[SpecialDbType(SpecialDbTypes.NVARCHARMAX)]
public string? UserData { get; set; }
}

View File

@@ -66,7 +66,7 @@ internal class PropertyDataDto
[Column("textValue")]
[NullSetting(NullSetting = NullSettings.Null)]
[SpecialDbType(SpecialDbTypes.NTEXT)]
[SpecialDbType(SpecialDbTypes.NVARCHARMAX)]
public string? TextValue { get; set; }
[ResultColumn]

View File

@@ -111,7 +111,7 @@ public class UserDto
/// </summary>
[Column("tourData")]
[NullSetting(NullSetting = NullSettings.Null)]
[SpecialDbType(SpecialDbTypes.NTEXT)]
[SpecialDbType(SpecialDbTypes.NVARCHARMAX)]
public string? TourData { get; set; }
[ResultColumn]