diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index add9b65fce..ad559f8814 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -275,7 +275,7 @@ namespace Umbraco.Core //the database migration objects MigrationResolver.Current = new MigrationResolver( - () => PluginManager.Current.ResolveMigrationTypes()); + () => PluginManager.Current.ResolveTypes()); // todo: remove once we drop IPropertyEditorValueConverter support. PropertyEditorValueConvertersResolver.Current = new PropertyEditorValueConvertersResolver( diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationAttribute.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationAttribute.cs index b7e16614e6..b815896539 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationAttribute.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationAttribute.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Persistence.Migrations /// Represents the Migration attribute, which is used to mark classes as /// database migrations with Up/Down methods for pushing changes UP or pulling them DOWN. /// - [AttributeUsage(AttributeTargets.Class)] + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class MigrationAttribute : Attribute { public MigrationAttribute(string targetVersion, int sortOrder, string product) diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationResolver.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationResolver.cs index 9d7d845646..3d28e99c56 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationResolver.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationResolver.cs @@ -27,26 +27,8 @@ namespace Umbraco.Core.Persistence.Migrations /// public IEnumerable Migrations { - get { return GetSortedValues(); } + get { return Values; } } - /// - /// Override how we determine object weight, for this resolver we use the MigrationAttribute attribute - /// - /// - /// - protected override int GetObjectWeight(object o) - { - var type = o.GetType(); - var attr = type.GetCustomAttribute(true); - return attr == null ? DefaultPluginWeight : attr.SortOrder; - } - - protected override int DefaultPluginWeight - { - get { return 0; } //set's the default to 0 - set { base.DefaultPluginWeight = value; } - } - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs index ad5fcdebc5..a988d77740 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs @@ -51,27 +51,15 @@ namespace Umbraco.Core.Persistence.Migrations ? OrderedUpgradeMigrations(foundMigrations).ToList() : OrderedDowngradeMigrations(foundMigrations).ToList(); + //SD: Why do we want this? if (Migrating.IsRaisedEventCancelled(new MigrationEventArgs(migrations, _configuredVersion, _targetVersion, true), this)) return false; //Loop through migrations to generate sql - var context = new MigrationContext(databaseProvider, database); - foreach (MigrationBase migration in migrations) - { - if (isUpgrade) - { - migration.GetUpExpressions(context); - LogHelper.Info(string.Format("Added UPGRADE migration '{0}' to context", migration.GetType().Name)); - } - else - { - migration.GetDownExpressions(context); - LogHelper.Info(string.Format("Added DOWNGRADE migration '{0}' to context", migration.GetType().Name)); - } - } + var context = ExecuteMigrations(migrations, database, databaseProvider, isUpgrade); //Transactional execution of the sql that was generated from the found migrations - using (Transaction transaction = database.GetTransaction()) + using (var transaction = database.GetTransaction()) { int i = 1; foreach (var expression in context.Expressions) @@ -96,32 +84,73 @@ namespace Umbraco.Core.Persistence.Migrations return true; } + internal MigrationContext ExecuteMigrations(List migrations, Database database, DatabaseProviders databaseProvider, bool isUpgrade = true) + { + //Loop through migrations to generate sql + var context = new MigrationContext(databaseProvider, database); + + foreach (var migration in migrations) + { + var baseMigration = migration as MigrationBase; + if (baseMigration != null) + { + if (isUpgrade) + { + baseMigration.GetUpExpressions(context); + LogHelper.Info(string.Format("Added UPGRADE migration '{0}' to context", baseMigration.GetType().Name)); + } + else + { + baseMigration.GetDownExpressions(context); + LogHelper.Info(string.Format("Added DOWNGRADE migration '{0}' to context", baseMigration.GetType().Name)); + } + } + else + { + //this is just a normal migration so we can only call Up/Down + if (isUpgrade) + { + migration.Up(); + LogHelper.Info(string.Format("Added UPGRADE migration '{0}' to context", migration.GetType().Name)); + } + else + { + migration.Down(); + LogHelper.Info(string.Format("Added DOWNGRADE migration '{0}' to context", migration.GetType().Name)); + } + } + } + + return context; + } + internal IEnumerable OrderedUpgradeMigrations(IEnumerable foundMigrations) { var migrations = (from migration in foundMigrations - let migrationAttribute = migration.GetType().FirstAttribute() - where migrationAttribute != null - where - migrationAttribute.TargetVersion > _configuredVersion && - migrationAttribute.TargetVersion <= _targetVersion && - migrationAttribute.ProductName == _productName - orderby migrationAttribute.TargetVersion, migrationAttribute.SortOrder ascending - select migration); + let migrationAttributes = migration.GetType().GetCustomAttributes(false) + from migrationAttribute in migrationAttributes + where migrationAttribute != null + where + migrationAttribute.TargetVersion > _configuredVersion && + migrationAttribute.TargetVersion <= _targetVersion && + migrationAttribute.ProductName == _productName + orderby migrationAttribute.TargetVersion, migrationAttribute.SortOrder ascending + select migration).Distinct(); return migrations; } public IEnumerable OrderedDowngradeMigrations(IEnumerable foundMigrations) { var migrations = (from migration in foundMigrations - let migrationAttribute = migration.GetType().FirstAttribute() - where migrationAttribute != null - where - migrationAttribute.TargetVersion > _configuredVersion && - migrationAttribute.TargetVersion <= _targetVersion && - migrationAttribute.ProductName == _productName - orderby migrationAttribute.TargetVersion, migrationAttribute.SortOrder descending - - select migration); + let migrationAttributes = migration.GetType().GetCustomAttributes(false) + from migrationAttribute in migrationAttributes + where migrationAttribute != null + where + migrationAttribute.TargetVersion > _configuredVersion && + migrationAttribute.TargetVersion <= _targetVersion && + migrationAttribute.ProductName == _productName + orderby migrationAttribute.TargetVersion, migrationAttribute.SortOrder descending + select migration).Distinct(); return migrations; } diff --git a/src/Umbraco.Core/PluginManager.cs b/src/Umbraco.Core/PluginManager.cs index f6b5cf7f91..3928a0c1cc 100644 --- a/src/Umbraco.Core/PluginManager.cs +++ b/src/Umbraco.Core/PluginManager.cs @@ -529,16 +529,7 @@ namespace Umbraco.Core { return ResolveTypesWithAttribute(); } - - /// - /// Returns all available IMigrations in application - /// - /// - internal IEnumerable ResolveMigrationTypes() - { - return ResolveTypes(); - } - + /// /// Returns all SqlSyntaxProviders with the SqlSyntaxProviderAttribute /// diff --git a/src/Umbraco.Tests/Migrations/FindingMigrationsTest.cs b/src/Umbraco.Tests/Migrations/FindingMigrationsTest.cs index b958dba401..5775ad8718 100644 --- a/src/Umbraco.Tests/Migrations/FindingMigrationsTest.cs +++ b/src/Umbraco.Tests/Migrations/FindingMigrationsTest.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.ObjectResolution; @@ -24,18 +25,19 @@ namespace Umbraco.Tests.Migrations { typeof (AlterUserTableMigrationStub), typeof(Dummy), - typeof (FourNineMigration), - typeof (FourTenMigration), - typeof (FourElevenMigration) + typeof (SixZeroMigration1), + typeof (SixZeroMigration2), + typeof (FourElevenMigration), + typeof (FiveZeroMigration) }); Resolution.Freeze(); SqlSyntaxContext.SqlSyntaxProvider = SqlCeSyntax.Provider; - } + } [Test] - public void Can_Find_Migrations_With_Targtet_Version_Six() + public void Can_Find_Migrations_With_Target_Version_Six() { var foundMigrations = MigrationResolver.Current.Migrations; var targetVersion = new Version("6.0.0"); @@ -56,8 +58,9 @@ namespace Umbraco.Tests.Migrations Assert.That(list.Count, Is.EqualTo(3)); var context = new MigrationContext(DatabaseProviders.SqlServerCE, null); - foreach (MigrationBase migration in list) + foreach (var migration1 in list) { + var migration = (MigrationBase) migration1; migration.GetUpExpressions(context); } diff --git a/src/Umbraco.Tests/Migrations/MigrationRunnerTests.cs b/src/Umbraco.Tests/Migrations/MigrationRunnerTests.cs new file mode 100644 index 0000000000..6964a5b8a7 --- /dev/null +++ b/src/Umbraco.Tests/Migrations/MigrationRunnerTests.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Migrations; +using Umbraco.Core.Persistence.Migrations.Syntax.Alter.Expressions; + +namespace Umbraco.Tests.Migrations +{ + [TestFixture] + public class MigrationRunnerTests + { + [Test] + public void Executes_Only_One_Migration_For_Spanning_Multiple_Targets() + { + var runner = new MigrationRunner(new Version(4, 0, 0), new Version(6, 0, 0), "Test"); + + var migrations = runner.OrderedUpgradeMigrations(new List {new MultiMigration()}); + + var ctx = runner.ExecuteMigrations( + //new List {new DoRunMigration(), new DoNotRunMigration()}, + migrations.ToList(), + new Database("umbracoDbDSN") + , DatabaseProviders.SqlServerCE, true); + + Assert.AreEqual(1, ctx.Expressions.Count()); + } + + [Test] + public void Executes_Migration_For_Spanning_One_Target_1() + { + var runner = new MigrationRunner(new Version(4, 0, 0), new Version(5, 0, 0), "Test"); + + var migrations = runner.OrderedUpgradeMigrations(new List { new MultiMigration() }); + + var ctx = runner.ExecuteMigrations( + //new List {new DoRunMigration(), new DoNotRunMigration()}, + migrations.ToList(), + new Database("umbracoDbDSN") + , DatabaseProviders.SqlServerCE, true); + + Assert.AreEqual(1, ctx.Expressions.Count()); + } + + [Test] + public void Executes_Migration_For_Spanning_One_Target_2() + { + var runner = new MigrationRunner(new Version(5, 0, 1), new Version(6, 0, 0), "Test"); + + var migrations = runner.OrderedUpgradeMigrations(new List { new MultiMigration() }); + + var ctx = runner.ExecuteMigrations( + //new List {new DoRunMigration(), new DoNotRunMigration()}, + migrations.ToList(), + new Database("umbracoDbDSN") + , DatabaseProviders.SqlServerCE, true); + + Assert.AreEqual(1, ctx.Expressions.Count()); + } + + [Migration("6.0.0", 1, "Test")] + [Migration("5.0.0", 1, "Test")] + private class MultiMigration : MigrationBase + { + public override void Up() + { + Context.Expressions.Add(new AlterColumnExpression()); + } + + public override void Down() + { + Context.Expressions.Add(new AlterColumnExpression()); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Migrations/Stubs/FiveZeroMigration.cs b/src/Umbraco.Tests/Migrations/Stubs/FiveZeroMigration.cs new file mode 100644 index 0000000000..1c4c9f9db7 --- /dev/null +++ b/src/Umbraco.Tests/Migrations/Stubs/FiveZeroMigration.cs @@ -0,0 +1,16 @@ +using Umbraco.Core.Persistence.Migrations; + +namespace Umbraco.Tests.Migrations.Stubs +{ + [Migration("5.0.0", 0, "Test")] + public class FiveZeroMigration : MigrationBase + { + public override void Up() + { + } + + public override void Down() + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Migrations/Stubs/FourElevenMigration.cs b/src/Umbraco.Tests/Migrations/Stubs/FourElevenMigration.cs new file mode 100644 index 0000000000..bf75a678cb --- /dev/null +++ b/src/Umbraco.Tests/Migrations/Stubs/FourElevenMigration.cs @@ -0,0 +1,18 @@ +using Umbraco.Core.Persistence.Migrations; + +namespace Umbraco.Tests.Migrations.Stubs +{ + [Migration("4.11.0", 0, "Test")] + public class FourElevenMigration : MigrationBase + { + public override void Up() + { + Alter.Table("umbracoUser").AddColumn("companyPhone").AsString(255); + } + + public override void Down() + { + Alter.Table("umbracoUser").AlterColumn("regularPhone").AsString(255); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Migrations/Stubs/FourNineMigration.cs b/src/Umbraco.Tests/Migrations/Stubs/FourNineMigration.cs deleted file mode 100644 index 7226dd0ace..0000000000 --- a/src/Umbraco.Tests/Migrations/Stubs/FourNineMigration.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Umbraco.Core.Persistence.Migrations; - -namespace Umbraco.Tests.Migrations.Stubs -{ - [MigrationAttribute("6.0.0", 1, "Test")] - public class FourNineMigration : MigrationBase - { - public override void Up() - { - Alter.Table("umbracoUser").AddColumn("secret").AsString(255); - } - - public override void Down() - { - Alter.Table("umbracoUser").AlterColumn("passwordTip").AsString(100); - } - } - - [MigrationAttribute("6.0.0", 2, "Test")] - public class FourTenMigration : MigrationBase - { - public override void Up() - { - Alter.Table("umbracoUser").AddColumn("secondEmail").AsString(255); - } - - public override void Down() - { - Alter.Table("umbracoUser").AlterColumn("sendEmail").AsBoolean(); - } - } - - [MigrationAttribute("4.11.0", 0, "Test")] - public class FourElevenMigration : MigrationBase - { - public override void Up() - { - Alter.Table("umbracoUser").AddColumn("companyPhone").AsString(255); - } - - public override void Down() - { - Alter.Table("umbracoUser").AlterColumn("regularPhone").AsString(255); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Tests/Migrations/Stubs/SixZeroMigration1.cs b/src/Umbraco.Tests/Migrations/Stubs/SixZeroMigration1.cs new file mode 100644 index 0000000000..7770b4a481 --- /dev/null +++ b/src/Umbraco.Tests/Migrations/Stubs/SixZeroMigration1.cs @@ -0,0 +1,18 @@ +using Umbraco.Core.Persistence.Migrations; + +namespace Umbraco.Tests.Migrations.Stubs +{ + [MigrationAttribute("6.0.0", 1, "Test")] + public class SixZeroMigration1 : MigrationBase + { + public override void Up() + { + Alter.Table("umbracoUser").AddColumn("secret").AsString(255); + } + + public override void Down() + { + Alter.Table("umbracoUser").AlterColumn("passwordTip").AsString(100); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Migrations/Stubs/SixZeroMigration2.cs b/src/Umbraco.Tests/Migrations/Stubs/SixZeroMigration2.cs new file mode 100644 index 0000000000..277d44775f --- /dev/null +++ b/src/Umbraco.Tests/Migrations/Stubs/SixZeroMigration2.cs @@ -0,0 +1,18 @@ +using Umbraco.Core.Persistence.Migrations; + +namespace Umbraco.Tests.Migrations.Stubs +{ + [Migration("6.0.0", 2, "Test")] + public class SixZeroMigration2 : MigrationBase + { + public override void Up() + { + Alter.Table("umbracoUser").AddColumn("secondEmail").AsString(255); + } + + public override void Down() + { + Alter.Table("umbracoUser").AlterColumn("sendEmail").AsBoolean(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index cc0cf95c37..4f632ed9d9 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -157,6 +157,10 @@ + + + + @@ -253,7 +257,7 @@ - +