Changing abstract migration classes to be a bit more intuitive and actually use the class instead of the decorations.

Added unit test for sample add and drop column changes.
This commit is contained in:
Morten@Thinkpad-X220.ab-nat1.dk
2012-10-13 09:57:45 -02:00
parent ba45e0d365
commit 46a54d0bc3
21 changed files with 419 additions and 15 deletions

View File

@@ -2,15 +2,20 @@
{
public static class AttributeExtensions
{
public static string ToSqlSyntax(this NullSettingAttribute attribute)
public static string ToSqlSyntax(this NullSettings settings)
{
return attribute.NullSetting == NullSettings.Null ? "NULL" : "NOT NULL";
return settings == NullSettings.Null ? "NULL" : "NOT NULL";
}
public static string ToSqlSyntax(this DatabaseTypeAttribute attribute)
public static string ToSqlSyntax(this NullSettingAttribute attribute)
{
return attribute.NullSetting.ToSqlSyntax();
}
public static string ToSqlSyntax(this DatabaseTypes databaseTypes, int length)
{
string syntax = string.Empty;
switch (attribute.DatabaseType)
switch (databaseTypes)
{
case DatabaseTypes.Bool:
syntax = "[bit]";
@@ -32,13 +37,18 @@
break;
case DatabaseTypes.Nvarchar:
syntax = "[nvarchar]";
if (attribute.Length > 0)
syntax += string.Format(" ({0})", attribute.Length);
if (length > 0)
syntax += string.Format(" ({0})", length);
break;
}
return syntax;
}
public static string ToSqlSyntax(this DatabaseTypeAttribute attribute)
{
return attribute.DatabaseType.ToSqlSyntax(attribute.Length);
}
public static string ToSqlSyntax(this PrimaryKeyColumnAttribute attribute)
{
string syntax = string.Empty;
@@ -53,6 +63,10 @@
{
string constraintName = string.IsNullOrEmpty(attribute.Name) ? string.Format("PK_{0}", tableName) : attribute.Name;
string clustered = attribute.Clustered ? "CLUSTERED" : "NONCLUSTERED";
if (DatabaseFactory.Current.DatabaseProvider == DatabaseProviders.SqlServerCE)
clustered = string.Empty;
string syntax = string.Format("ALTER TABLE [{0}] ADD CONSTRAINT [{1}] PRIMARY KEY {2} ([{3}])", tableName,
constraintName, clustered, propertyName);
@@ -62,7 +76,7 @@
public static string ToSqlSyntax(this ConstraintAttribute attribute, string tableName, string propertyName)
{
if (!string.IsNullOrEmpty(attribute.Name))
return attribute.Name;
return string.Format("CONSTRAINT [{0}] DEFAULT ({1})", attribute.Name, attribute.Default);
return string.Format("CONSTRAINT [DF_{0}_{1}] DEFAULT ({2})", tableName, propertyName, attribute.Default);
}

View File

@@ -0,0 +1,9 @@
namespace Umbraco.Core.Persistence.DatabaseAnnotations
{
public enum ChangeTypes
{
ADD,
DROP,
RENAME
}
}

View File

@@ -1,18 +1,19 @@
using System;
using System.Threading;
using Umbraco.Core.Configuration;
using System.Configuration;
using System.Data.Common;
namespace Umbraco.Core.Persistence
{
/// <summary>
/// Provides access to the PetaPoco database as Singleton, so the database is created once in app lifecycle.
/// Provides access to the PetaPoco database as Singleton, so the database is created once in app lifetime.
/// This is necessary for transactions to work properly
/// </summary>
public sealed class DatabaseFactory
{
#region Singleton
private static readonly Database _database = new Database("umbracoDbDSN");
private const string ConnectionStringName = "umbracoDbDSN";
private static readonly Database _database = new Database(ConnectionStringName);
private static readonly Lazy<DatabaseFactory> lazy = new Lazy<DatabaseFactory>(() => new DatabaseFactory());
public static DatabaseFactory Current { get { return lazy.Value; } }
@@ -30,5 +31,47 @@ namespace Umbraco.Core.Persistence
{
get { return _database; }
}
/// <summary>
/// Returns the name of the dataprovider from the connectionstring setting in config
/// </summary>
public string ProviderName
{
get
{
var providerName = "System.Data.SqlClient";
if (ConfigurationManager.ConnectionStrings[ConnectionStringName] != null)
{
if (!string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[ConnectionStringName].ProviderName))
providerName = ConfigurationManager.ConnectionStrings[ConnectionStringName].ProviderName;
}
else
{
throw new InvalidOperationException("Can't find a connection string with the name '" + ConnectionStringName + "'");
}
return providerName;
}
}
/// <summary>
/// Returns the Type of DatabaseProvider used
/// </summary>
public DatabaseProviders DatabaseProvider
{
get
{
var factory = DbProviderFactories.GetFactory(ProviderName);
string dbtype = (factory.GetType()).Name;
if (dbtype.StartsWith("MySql")) return DatabaseProviders.MySql;
if (dbtype.StartsWith("SqlCe")) return DatabaseProviders.SqlServerCE;
if (dbtype.StartsWith("Npgsql")) return DatabaseProviders.PostgreSQL;
if (dbtype.StartsWith("Oracle")) return DatabaseProviders.Oracle;
if (dbtype.StartsWith("SQLite")) return DatabaseProviders.SQLite;
return DatabaseProviders.SqlServer;
}
}
}
}

View File

@@ -0,0 +1,12 @@
namespace Umbraco.Core.Persistence
{
public enum DatabaseProviders
{
SqlServer,
SqlServerCE,
MySql,
PostgreSQL,
Oracle,
SQLite
}
}

View File

@@ -0,0 +1,41 @@
using Umbraco.Core.Persistence.DatabaseAnnotations;
namespace Umbraco.Core.Persistence.Migrations
{
/// <summary>
/// Represents an abstract class for descriping changes to a database column
/// Used to Add a column with an optional constraint
/// </summary>
public abstract class AddColumnChange : DropColumnChange
{
public abstract string Constraint { get; }
public abstract string DefaultForConstraint { get; }
public abstract DatabaseTypes DatabaseType { get; }
public virtual int DatabaseTypeLength
{
get { return 0; }
}
public abstract NullSettings NullSetting { get; }
public override Sql ToSql()
{
var sql = new Sql();
string constraint = !string.IsNullOrEmpty(Constraint)
? string.Format("CONSTRAINT [{0}] DEFAULT ({1})", Constraint, DefaultForConstraint)
: string.Format("CONSTRAINT [DF_{0}_{1}] DEFAULT ({2})", TableName, ColumnName,
DefaultForConstraint);
sql.Append(string.Format("ALTER TABLE [{0}] ADD [{1}] {2} {3} {4};",
TableName, ColumnName,
DatabaseType.ToSqlSyntax(DatabaseTypeLength),
NullSetting.ToSqlSyntax(),
constraint));
return sql;
}
}
}

View File

@@ -0,0 +1,11 @@
namespace Umbraco.Core.Persistence.Migrations
{
public abstract class BaseChange : IDatabaseChange
{
public abstract Sql ToSql();
public abstract string TableName { get; }
public abstract string Version { get; }
}
}

View File

@@ -0,0 +1,10 @@
namespace Umbraco.Core.Persistence.Migrations
{
/// <summary>
/// Represents an abstract class for descriping changes to a database constraint
/// Used to Add, Remove or Rename a constraint
/// </summary>
public abstract class ConstraintChange
{
}
}

View File

@@ -0,0 +1,10 @@
namespace Umbraco.Core.Persistence.Migrations
{
/// <summary>
/// Represents an abstract class for descriping changes to a database column's type
/// Used to Add, Remove or Change (increase/decrease length) a column's database
/// </summary>
public abstract class DatabaseTypeChange
{
}
}

View File

@@ -0,0 +1,29 @@
using Umbraco.Core.Persistence.DatabaseAnnotations;
namespace Umbraco.Core.Persistence.Migrations
{
/// <summary>
/// Represents an abstract class for descriping changes to a database column
/// Used to Remove a column
/// </summary>
public abstract class DropColumnChange : BaseChange
{
public abstract string ColumnName { get; }
public virtual ChangeTypes ChangeType
{
get
{
return ChangeTypes.DROP;
}
}
public override Sql ToSql()
{
var sql = new Sql();
sql.Append(string.Format("ALTER TABLE [{0}] DROP COLUMN [{1}];",
TableName, ColumnName));
return sql;
}
}
}

View File

@@ -0,0 +1,10 @@
namespace Umbraco.Core.Persistence.Migrations
{
/// <summary>
/// Represents an abstract class for descriping changes to a database column's foreign key
/// Used to Add, Remove or Rename a column's foreign key
/// </summary>
public abstract class ForeignKeyChange
{
}
}

View File

@@ -0,0 +1,8 @@
namespace Umbraco.Core.Persistence.Migrations
{
public interface IDatabaseChange
{
string Version { get; }
Sql ToSql();
}
}

View File

@@ -0,0 +1,10 @@
namespace Umbraco.Core.Persistence.Migrations
{
/// <summary>
/// Represents an abstract class for descriping changes to a database index
/// Used to Add, Remove or Rename an index
/// </summary>
public abstract class IndexChange
{
}
}

View File

@@ -0,0 +1,10 @@
namespace Umbraco.Core.Persistence.Migrations
{
/// <summary>
/// Represents an abstract class for descriping changes to a database table.
/// Used to create or drop tables
/// </summary>
public abstract class TableChange
{
}
}

View File

@@ -105,6 +105,7 @@
<Compile Include="Persistence\Caching\NullCacheProvider.cs" />
<Compile Include="Persistence\Caching\RuntimeCacheProvider.cs" />
<Compile Include="Persistence\DatabaseAnnotations\AttributeExtensions.cs" />
<Compile Include="Persistence\DatabaseAnnotations\ChangeTypes.cs" />
<Compile Include="Persistence\DatabaseAnnotations\ConstraintAttribute.cs" />
<Compile Include="Persistence\DatabaseAnnotations\DatabaseTypeAttribute.cs" />
<Compile Include="Persistence\DatabaseAnnotations\DatabaseTypes.cs" />
@@ -116,6 +117,7 @@
<Compile Include="Persistence\DatabaseAnnotations\PrimaryKeyColumnAttribute.cs" />
<Compile Include="Persistence\DatabaseAnnotations\ReferencesAttribute.cs" />
<Compile Include="Persistence\DatabaseFactory.cs" />
<Compile Include="Persistence\DatabaseProviders.cs" />
<Compile Include="Persistence\Factories\ContentFactory.cs" />
<Compile Include="Persistence\Factories\ContentTypeFactory.cs" />
<Compile Include="Persistence\Factories\DataTypeDefinitionFactory.cs" />
@@ -130,6 +132,15 @@
<Compile Include="Persistence\Factories\RelationFactory.cs" />
<Compile Include="Persistence\Factories\RelationTypeFactory.cs" />
<Compile Include="Persistence\Mappers\ModelDtoMapper.cs" />
<Compile Include="Persistence\Migrations\AddColumnChange.cs" />
<Compile Include="Persistence\Migrations\BaseChange.cs" />
<Compile Include="Persistence\Migrations\ConstraintChange.cs" />
<Compile Include="Persistence\Migrations\DatabaseTypeChange.cs" />
<Compile Include="Persistence\Migrations\DropColumnChange.cs" />
<Compile Include="Persistence\Migrations\ForeignKeyChange.cs" />
<Compile Include="Persistence\Migrations\IDatabaseChange.cs" />
<Compile Include="Persistence\Migrations\IndexChange.cs" />
<Compile Include="Persistence\Migrations\TableChange.cs" />
<Compile Include="Persistence\PetaPocoExtensions.cs" />
<Compile Include="Persistence\Querying\ExpressionHelper.cs" />
<Compile Include="Persistence\Querying\IQuery.cs" />

View File

@@ -8,7 +8,7 @@
</configSections>
<connectionStrings>
<add name="umbracoDbDSN" connectionString="server=.\SQLEXPRESS;database=EmptyForTest;user id=umbraco;password=umbraco" providerName="System.Data.SqlClient"/>
<add name="umbracoDbDSN" connectionString="Datasource=|DataDirectory|test.sdf" providerName="System.Data.SqlServerCe.4.0"/>
</connectionStrings>
<FileSystemProviders>

View File

@@ -1,5 +1,7 @@
using System;
using System.Configuration;
using System.Data.SqlServerCe;
using System.IO;
using NUnit.Framework;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
@@ -13,14 +15,25 @@ namespace Umbraco.Tests.Persistence
[SetUp]
public virtual void Initialize()
{
AppDomain.CurrentDomain.SetData("DataDirectory", TestHelper.CurrentAssemblyDirectory);
string path = TestHelper.CurrentAssemblyDirectory;
AppDomain.CurrentDomain.SetData("DataDirectory", path);
string filePath = string.Concat(path, "\\test.sdf");
if(File.Exists(filePath))
{
File.Delete(filePath);
}
string connectionString = "Datasource=|DataDirectory|test.sdf";
var engine = new SqlCeEngine(connectionString);
engine.CreateDatabase();
}
[Test]
public void Can_Create_umbracoNode_Table()
{
var factory = DatabaseFactory.Current;
//var database = new Database("Datasource=|DataDirectory|test.sdf", "System.Data.SqlServerCe.4.0");
using(Transaction transaction = factory.Database.GetTransaction())
{
factory.Database.CreateTable<NodeDto>();

View File

@@ -14,5 +14,13 @@ namespace Umbraco.Tests.Persistence
Assert.AreSame(db1, db2);
}
[Test]
public void Can_Assert_DatabaseProvider()
{
var provider = DatabaseFactory.Current.DatabaseProvider;
Assert.AreEqual(DatabaseProviders.SqlServerCE, provider);
}
}
}

View File

@@ -0,0 +1,28 @@
using NUnit.Framework;
using Umbraco.Core.Persistence;
using Umbraco.Tests.TestHelpers.MockedMigrations;
namespace Umbraco.Tests.Persistence
{
[TestFixture]
public class MigrationTests
{
[Test]
public void Can_Verify_Add_ColumnChange()
{
var columnChange = new AddAllowAtRootColumn();
Sql sql = columnChange.ToSql();
Assert.AreEqual(sql.SQL, "ALTER TABLE [cmsContentType] ADD [allowAtRoot] [bit] NOT NULL CONSTRAINT [df_cmsContentType_allowAtRoot] DEFAULT (0);");
}
[Test]
public void Can_Verify_Drop_ColumnChange()
{
var columnChange = new DropMasterContentTypeColumn();
Sql sql = columnChange.ToSql();
Assert.AreEqual(sql.SQL, "ALTER TABLE [cmsContentType] DROP COLUMN [masterContentType];");
}
}
}

View File

@@ -0,0 +1,49 @@
using Umbraco.Core.Persistence.DatabaseAnnotations;
using Umbraco.Core.Persistence.Migrations;
namespace Umbraco.Tests.TestHelpers.MockedMigrations
{
/// <summary>
/// Mocked Column Change that'll generate the following sql:
/// ALTER TABLE [cmsContentType]
/// ADD [allowAtRoot] bit NOT NULL
/// CONSTRAINT [df_cmsContentType_allowAtRoot] DEFAULT 0
/// </summary>
public class AddAllowAtRootColumn : AddColumnChange
{
public override string TableName
{
get { return "cmsContentType"; }
}
public override string Version
{
get { return "4.10.0"; }
}
public override string ColumnName
{
get { return "allowAtRoot"; }
}
public override string Constraint
{
get { return "df_cmsContentType_allowAtRoot"; }
}
public override string DefaultForConstraint
{
get { return "0"; }
}
public override DatabaseTypes DatabaseType
{
get { return DatabaseTypes.Bool; }
}
public override NullSettings NullSetting
{
get { return NullSettings.NotNull; }
}
}
}

View File

@@ -0,0 +1,26 @@
using Umbraco.Core.Persistence.Migrations;
namespace Umbraco.Tests.TestHelpers.MockedMigrations
{
/// <summary>
/// Mocked Column Change that'll generate the following sql:
/// ALTER TABLE [cmsContentType] DROP COLUMN [masterContentType];
/// </summary>
public class DropMasterContentTypeColumn : DropColumnChange
{
public override string Version
{
get { return "4.10.0"; }
}
public override string TableName
{
get { return "cmsContentType"; }
}
public override string ColumnName
{
get { return "masterContentType"; }
}
}
}

View File

@@ -14,6 +14,21 @@
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -46,6 +61,14 @@
<Reference Include="System" />
<Reference Include="System.configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\lib\SQLCE4\System.Data.SqlServerCe.dll</HintPath>
</Reference>
<Reference Include="System.Data.SqlServerCe.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\lib\SQLCE4\System.Data.SqlServerCe.Entity.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Web" />
<Reference Include="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
@@ -69,6 +92,7 @@
<Compile Include="Models\StylesheetTests.cs" />
<Compile Include="Persistence\DatabaseExtensionsTest.cs" />
<Compile Include="Persistence\DatabaseFactoryTests.cs" />
<Compile Include="Persistence\MigrationTests.cs" />
<Compile Include="Persistence\RepositoryResolverTests.cs" />
<Compile Include="Resolvers\ActionsResolverTests.cs" />
<Compile Include="AsynchronousRollingFileAppenderTests.cs" />
@@ -105,6 +129,8 @@
<Compile Include="TestHelpers\Entities\MockedContent.cs" />
<Compile Include="TestHelpers\Entities\MockedContentTypes.cs" />
<Compile Include="TestHelpers\Entities\MockedEntity.cs" />
<Compile Include="TestHelpers\MockedMigrations\AddAllowAtRootColumn.cs" />
<Compile Include="TestHelpers\MockedMigrations\DropMasterContentTypeColumn.cs" />
<Compile Include="UriUtilityTests.cs" />
<Compile Include="Resolvers\MacroFieldEditorsResolverTests.cs" />
<Compile Include="MacroEngineFactoryTests.cs" />
@@ -186,7 +212,33 @@
<Name>Umbraco.Web</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup />
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.0">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
<Install>false</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.SQL.Server.Compact.4.0">
<Visible>False</Visible>
<ProductName>SQL Server Compact 4.0 SP1</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Windows.Installer.4.5">
<Visible>False</Visible>
<ProductName>Windows Installer 4.5</ProductName>
<Install>true</Install>
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<Content Include="Masterpages\dummy.txt" />
<Content Include="Views\dummy.txt" />