diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs
index 380eaa3034..c4cd28f6e0 100644
--- a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs
+++ b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs
@@ -16,7 +16,7 @@ namespace Umbraco.Core.Models.Rdbms
[Column("contentNodeId")]
[ForeignKey(typeof(NodeDto))]
- [Index(IndexTypes.NonClustered, Name = "IX_cmsPropertyData_1")]
+ [Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsPropertyData_1", ForColumns = "contentNodeId,versionId,propertytypeid")]
public int NodeId { get; set; }
[Column("versionId")]
diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/DeleteIndexBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/DeleteIndexBuilder.cs
index 72250877db..93661c9ece 100644
--- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/DeleteIndexBuilder.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/DeleteIndexBuilder.cs
@@ -1,4 +1,5 @@
-using Umbraco.Core.Persistence.DatabaseModelDefinitions;
+using System;
+using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions;
namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Index
@@ -17,12 +18,14 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Index
return this;
}
+ [Obsolete("I don't think this would ever be used when dropping an index, see DeleteIndexExpression.ToString")]
public void OnColumn(string columnName)
{
var column = new IndexColumnDefinition { Name = columnName };
Expression.Index.Columns.Add(column);
}
+ [Obsolete("I don't think this would ever be used when dropping an index, see DeleteIndexExpression.ToString")]
public void OnColumns(params string[] columnNames)
{
foreach (string columnName in columnNames)
diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/IDeleteIndexOnColumnSyntax.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/IDeleteIndexOnColumnSyntax.cs
index f2f4280f23..fcf5038a86 100644
--- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/IDeleteIndexOnColumnSyntax.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Index/IDeleteIndexOnColumnSyntax.cs
@@ -1,8 +1,13 @@
-namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Index
+using System;
+
+namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Index
{
public interface IDeleteIndexOnColumnSyntax : IFluentSyntax
{
+ [Obsolete("I don't think this would ever be used when dropping an index, see DeleteIndexExpression.ToString")]
void OnColumn(string columnName);
+
+ [Obsolete("I don't think this would ever be used when dropping an index, see DeleteIndexExpression.ToString")]
void OnColumns(params string[] columnNames);
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/UpdateUniqueIndexOnCmsPropertyData.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/UpdateUniqueIndexOnCmsPropertyData.cs
new file mode 100644
index 0000000000..4bae9c80e4
--- /dev/null
+++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFiveZero/UpdateUniqueIndexOnCmsPropertyData.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using Umbraco.Core.Configuration;
+using Umbraco.Core.Logging;
+using Umbraco.Core.Models.Rdbms;
+using Umbraco.Core.Persistence.SqlSyntax;
+
+namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZero
+{
+ ///
+ /// See: http://issues.umbraco.org/issue/U4-8522
+ ///
+ [Migration("7.5.0", 2, GlobalSettings.UmbracoMigrationName)]
+ public class UpdateUniqueIndexOnCmsPropertyData : MigrationBase
+ {
+ public UpdateUniqueIndexOnCmsPropertyData(ISqlSyntaxProvider sqlSyntax, ILogger logger)
+ : base(sqlSyntax, logger)
+ {
+ }
+
+ public override void Up()
+ {
+ //Clear all stylesheet data if the tables exist
+ //tuple = tablename, indexname, columnname, unique
+ var indexes = SqlSyntax.GetDefinedIndexes(Context.Database).ToArray();
+ var found = indexes.FirstOrDefault(
+ x => x.Item1.InvariantEquals("cmsPropertyData")
+ && x.Item2.InvariantEquals("IX_cmsPropertyData_1")
+ //we're searching for the old index which is not unique
+ && x.Item4 == false);
+
+ if (found != null)
+ {
+ //Check for MySQL
+ if (Context.CurrentDatabaseProvider == DatabaseProviders.MySql)
+ {
+ //Use the special double nested sub query for MySQL since that is the only
+ //way delete sub queries works
+ SqlSyntax.GetDeleteSubquery(
+ "cmsPropertyData",
+ "id",
+ new Sql("SELECT MIN(id) FROM cmsPropertyData GROUP BY contentNodeId, versionId, propertytypeid HAVING MIN(id) IS NOT NULL"),
+ WhereInType.NotIn);
+ }
+ else
+ {
+ //NOTE: Even though the above will work for MSSQL, we are not going to execute the
+ // nested delete sub query logic since it will be slower and there could be a ton of property
+ // data here so needs to be as fast as possible.
+ Execute.Sql("DELETE FROM cmsPropertyData WHERE id NOT IN (SELECT MIN(id) FROM cmsPropertyData GROUP BY contentNodeId, versionId, propertytypeid HAVING MIN(id) IS NOT NULL)");
+ }
+
+ //we need to re create this index
+ Delete.Index("IX_cmsPropertyData_1").OnTable("cmsPropertyData");
+ Create.Index("IX_cmsPropertyData_1").OnTable("cmsPropertyData")
+ .OnColumn("contentNodeId").Ascending()
+ .OnColumn("versionId").Ascending()
+ .OnColumn("propertytypeid").Ascending()
+ .WithOptions().NonClustered()
+ .WithOptions().Unique();
+ }
+ }
+
+ public override void Down()
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs
index af18d9c3d9..1231765f20 100644
--- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs
+++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs
@@ -22,12 +22,23 @@
///
/// See: http://issues.umbraco.org/issue/U4-3876
///
- public static Sql GetDeleteSubquery(this ISqlSyntaxProvider sqlProvider, string tableName, string columnName, Sql subQuery)
+ public static Sql GetDeleteSubquery(this ISqlSyntaxProvider sqlProvider, string tableName, string columnName, Sql subQuery, WhereInType whereInType = WhereInType.In)
{
- return new Sql(string.Format(@"DELETE FROM {0} WHERE {1} IN (SELECT {1} FROM ({2}) x)",
+
+ return
+ new Sql(string.Format(
+ whereInType == WhereInType.In
+ ? @"DELETE FROM {0} WHERE {1} IN (SELECT {1} FROM ({2}) x)"
+ : @"DELETE FROM {0} WHERE {1} NOT IN (SELECT {1} FROM ({2}) x)",
sqlProvider.GetQuotedTableName(tableName),
sqlProvider.GetQuotedColumnName(columnName),
subQuery.SQL), subQuery.Arguments);
}
}
+
+ internal enum WhereInType
+ {
+ In,
+ NotIn
+ }
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 749502b310..22aa68ab7a 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -433,6 +433,7 @@
+
diff --git a/src/Umbraco.Tests/Migrations/MigrationIssuesTests.cs b/src/Umbraco.Tests/Migrations/MigrationIssuesTests.cs
index 111837319d..d122a3cd25 100644
--- a/src/Umbraco.Tests/Migrations/MigrationIssuesTests.cs
+++ b/src/Umbraco.Tests/Migrations/MigrationIssuesTests.cs
@@ -81,14 +81,16 @@ namespace Umbraco.Tests.Migrations
{
NodeId = n.NodeId,
PropertyTypeId = pt.Id,
- Text = "text"
+ Text = "text",
+ VersionId = Guid.NewGuid()
};
DatabaseContext.Database.Insert(data);
data = new PropertyDataDto
{
NodeId = n.NodeId,
PropertyTypeId = pt.Id,
- Text = ""
+ Text = "",
+ VersionId = Guid.NewGuid()
};
DatabaseContext.Database.Insert(data);