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:
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
|
||||
/// </summary>
|
||||
public enum SpecialDbTypes
|
||||
{
|
||||
[Obsolete("Use NVARCHARMAX instead")]
|
||||
NTEXT,
|
||||
NCHAR,
|
||||
NVARCHARMAX,
|
||||
|
||||
@@ -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!;
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user