Code from freedom friday project extending petapoco with a bunch of annotations used to decorate strongly typed models for table creation

This commit is contained in:
Morten@Thinkpad-X220
2012-10-12 12:37:55 -02:00
parent a9c40a7f72
commit ba45e0d365
20 changed files with 520 additions and 24 deletions

View File

@@ -1,4 +1,5 @@
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseAnnotations;
namespace Umbraco.Core.Models.Rdbms
{
@@ -8,12 +9,21 @@ namespace Umbraco.Core.Models.Rdbms
internal class ContentDto
{
[Column("pk")]
[PrimaryKeyColumn]
[DatabaseType(DatabaseTypes.Integer)]
[NullSetting(NullSetting = NullSettings.Null)]
public int PrimaryKey { get; set; }
[Column("nodeId")]
[ForeignKey(typeof(NodeDto))]
[DatabaseType(DatabaseTypes.Integer)]
[NullSetting(NullSetting = NullSettings.Null)]
[Index(IndexTypes.UniqueNonclustered, Name = "IX_cmsContent")]
public int NodeId { get; set; }
[Column("contentType")]
[DatabaseType(DatabaseTypes.Integer)]
[NullSetting(NullSetting = NullSettings.Null)]
public int ContentType { get; set; }
[ResultColumn]

View File

@@ -1,5 +1,6 @@
using System;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseAnnotations;
namespace Umbraco.Core.Models.Rdbms
{
@@ -9,36 +10,64 @@ namespace Umbraco.Core.Models.Rdbms
internal class NodeDto
{
[Column("id")]
[PrimaryKeyColumn(Name = "PK_structure")]
[DatabaseType(DatabaseTypes.Integer)]
[NullSetting(NullSetting = NullSettings.NotNull)]
public int NodeId { get; set; }
[Column("trashed")]
[DatabaseType(DatabaseTypes.Bool)]
[NullSetting(NullSetting = NullSettings.NotNull)]
[Constraint(Default = "0")]
public bool Trashed { get; set; }
[Column("parentID")]
[DatabaseType(DatabaseTypes.Integer)]
[NullSetting(NullSetting = NullSettings.NotNull)]
[ForeignKey(typeof(NodeDto))]
[IndexAttribute(IndexTypes.Nonclustered, Name = "IX_umbracoNodeParentId")]
public int ParentId { get; set; }
[Column("nodeUser")]
[DatabaseType(DatabaseTypes.Integer)]
[NullSetting(NullSetting = NullSettings.Null)]
public int? UserId { get; set; }
[Column("level")]
[DatabaseType(DatabaseTypes.SmallInteger)]
[NullSetting(NullSetting = NullSettings.NotNull)]
public short Level { get; set; }
[Column("path")]
[DatabaseType(DatabaseTypes.Nvarchar, Length = 150)]
[NullSetting(NullSetting = NullSettings.NotNull)]
public string Path { get; set; }
[Column("sortOrder")]
[DatabaseType(DatabaseTypes.Integer)]
[NullSetting(NullSetting = NullSettings.NotNull)]
public int SortOrder { get; set; }
[Column("uniqueID")]
[DatabaseType(DatabaseTypes.UniqueIdentifier)]
[NullSetting(NullSetting = NullSettings.Null)]
public Guid? UniqueId { get; set; }
[Column("text")]
[DatabaseType(DatabaseTypes.Nvarchar, Length = 255)]
[NullSetting(NullSetting = NullSettings.Null)]
public string Text { get; set; }
[Column("nodeObjectType")]
[DatabaseType(DatabaseTypes.UniqueIdentifier)]
[NullSetting(NullSetting = NullSettings.Null)]
[IndexAttribute(IndexTypes.Nonclustered, Name = "IX_umbracoNodeObjectType")]
public Guid? NodeObjectType { get; set; }
[Column("createDate")]
[DatabaseType(DatabaseTypes.DateTime)]
[NullSetting(NullSetting = NullSettings.NotNull)]
[Constraint(Default = "getdate()")]
public DateTime CreateDate { get; set; }
}
}

View File

@@ -0,0 +1,111 @@
namespace Umbraco.Core.Persistence.DatabaseAnnotations
{
public static class AttributeExtensions
{
public static string ToSqlSyntax(this NullSettingAttribute attribute)
{
return attribute.NullSetting == NullSettings.Null ? "NULL" : "NOT NULL";
}
public static string ToSqlSyntax(this DatabaseTypeAttribute attribute)
{
string syntax = string.Empty;
switch (attribute.DatabaseType)
{
case DatabaseTypes.Bool:
syntax = "[bit]";
break;
case DatabaseTypes.Ntext:
syntax = "[ntext]";
break;
case DatabaseTypes.DateTime:
syntax = "[datetime]";
break;
case DatabaseTypes.UniqueIdentifier:
syntax = "[uniqueidentifier]";
break;
case DatabaseTypes.SmallInteger:
syntax = "[smallint]";
break;
case DatabaseTypes.Integer:
syntax = "[int]";
break;
case DatabaseTypes.Nvarchar:
syntax = "[nvarchar]";
if (attribute.Length > 0)
syntax += string.Format(" ({0})", attribute.Length);
break;
}
return syntax;
}
public static string ToSqlSyntax(this PrimaryKeyColumnAttribute attribute)
{
string syntax = string.Empty;
if (attribute.AutoIncrement)
syntax = "IDENTITY(1, 1)";
return syntax;
}
public static string ToSqlSyntax(this PrimaryKeyColumnAttribute attribute, string tableName, string propertyName)
{
string constraintName = string.IsNullOrEmpty(attribute.Name) ? string.Format("PK_{0}", tableName) : attribute.Name;
string clustered = attribute.Clustered ? "CLUSTERED" : "NONCLUSTERED";
string syntax = string.Format("ALTER TABLE [{0}] ADD CONSTRAINT [{1}] PRIMARY KEY {2} ([{3}])", tableName,
constraintName, clustered, propertyName);
return syntax;
}
public static string ToSqlSyntax(this ConstraintAttribute attribute, string tableName, string propertyName)
{
if (!string.IsNullOrEmpty(attribute.Name))
return attribute.Name;
return string.Format("CONSTRAINT [DF_{0}_{1}] DEFAULT ({2})", tableName, propertyName, attribute.Default);
}
public static string ToSqlSyntax(this ForeignKeyAttribute attribute, string tableName, string propertyName)
{
var tableNameAttribute = attribute.Type.FirstAttribute<TableNameAttribute>();
var primaryKeyAttribute = attribute.Type.FirstAttribute<PrimaryKeyAttribute>();
var referencedTableName = tableNameAttribute.Value;
string constraintName = string.Format("FK_{0}_{1}", tableName, referencedTableName);
string syntax =
string.Format(
"ALTER TABLE [{0}] ADD CONSTRAINT [{1}] FOREIGN KEY ([{2}]) REFERENCES [{3}] ([{4}])",
tableName, constraintName, propertyName, referencedTableName, primaryKeyAttribute.Value);
return syntax;
}
public static string ToSqlSyntax(this IndexAttribute attribute, string tableName, string propertyName)
{
string indexType = string.Empty;
switch (attribute.IndexType)
{
case IndexTypes.Clustered:
indexType = "CLUSTERED";
break;
case IndexTypes.Nonclustered:
indexType = "NONCLUSTERED";
break;
case IndexTypes.PrimaryXml:
indexType = "PRIMARYXML";
break;
case IndexTypes.Spartial:
indexType = "SPARTIAL";
break;
case IndexTypes.UniqueNonclustered:
indexType = "UNIQUENONCLUSTERED";
break;
}
string name = string.IsNullOrEmpty(attribute.Name) ? string.Format("IX_{0}_{1}", tableName, propertyName) : attribute.Name;
string syntax = string.Format("CREATE {0} INDEX [{1}] ON [{2}] ([{3}])", indexType, name, tableName, propertyName);
return syntax;
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
namespace Umbraco.Core.Persistence.DatabaseAnnotations
{
[AttributeUsage(AttributeTargets.Property)]
public class ConstraintAttribute : Attribute
{
/// <summary>
/// Overrides the default naming of a property constraint:
/// DF_tableName_propertyName
/// </summary>
public string Name { get; set; }
public string Default { get; set; }
}
}

View File

@@ -0,0 +1,16 @@
using System;
namespace Umbraco.Core.Persistence.DatabaseAnnotations
{
[AttributeUsage(AttributeTargets.Property)]
public class DatabaseTypeAttribute : Attribute
{
public DatabaseTypeAttribute(DatabaseTypes databaseType)
{
DatabaseType = databaseType;
}
public DatabaseTypes DatabaseType { get; private set; }
public int Length { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
namespace Umbraco.Core.Persistence.DatabaseAnnotations
{
public enum DatabaseTypes
{
Integer,
SmallInteger,
UniqueIdentifier,
DateTime,
Ntext,
Nvarchar,
Bool
}
}

View File

@@ -0,0 +1,17 @@
using System;
namespace Umbraco.Core.Persistence.DatabaseAnnotations
{
[AttributeUsage(AttributeTargets.Property)]
public class ForeignKeyAttribute : ReferencesAttribute
{
public ForeignKeyAttribute(Type type) : base(type)
{
}
public string OnDelete { get; set; }
public string OnUpdate { get; set; }
//Default naming: FK_thisTableName_refTableName
}
}

View File

@@ -0,0 +1,17 @@
using System;
namespace Umbraco.Core.Persistence.DatabaseAnnotations
{
[AttributeUsage(AttributeTargets.Property)]
public class IndexAttribute : Attribute
{
public IndexAttribute(IndexTypes indexType)
{
IndexType = indexType;
}
//public Type Type { get; set; }
public string Name { get; set; }//Overrides default naming of indexes: IX_tableName
public IndexTypes IndexType { get; private set; }
}
}

View File

@@ -0,0 +1,11 @@
namespace Umbraco.Core.Persistence.DatabaseAnnotations
{
public enum IndexTypes
{
Clustered,
Nonclustered,
UniqueNonclustered,
PrimaryXml,
Spartial
}
}

View File

@@ -0,0 +1,10 @@
using System;
namespace Umbraco.Core.Persistence.DatabaseAnnotations
{
[AttributeUsage(AttributeTargets.Property)]
public class NullSettingAttribute : Attribute
{
public NullSettings NullSetting { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace Umbraco.Core.Persistence.DatabaseAnnotations
{
public enum NullSettings
{
Null,
NotNull
}
}

View File

@@ -0,0 +1,18 @@
using System;
namespace Umbraco.Core.Persistence.DatabaseAnnotations
{
[AttributeUsage(AttributeTargets.Property)]
public class PrimaryKeyColumnAttribute : Attribute
{
public PrimaryKeyColumnAttribute()
{
Clustered = true;
AutoIncrement = true;
}
public bool Clustered { get; set; }//Defaults to true
public bool AutoIncrement { get; set; }//Default to true
public string Name { get; set; }//Overrides the default naming of a PrimaryKey constraint: PK_tableName
}
}

View File

@@ -0,0 +1,15 @@
using System;
namespace Umbraco.Core.Persistence.DatabaseAnnotations
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)]
public class ReferencesAttribute : Attribute
{
public ReferencesAttribute(Type type)
{
Type = type;
}
public Type Type { get; set; }
}
}

View File

@@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence
{
#region Singleton
private static readonly Database _database = new Database(GlobalSettings.DbDsn);
private static readonly Database _database = new Database("umbracoDbDSN");
private static readonly Lazy<DatabaseFactory> lazy = new Lazy<DatabaseFactory>(() => new DatabaseFactory());
public static DatabaseFactory Current { get { return lazy.Value; } }

View File

@@ -0,0 +1,158 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Umbraco.Core.Persistence.DatabaseAnnotations;
namespace Umbraco.Core.Persistence
{
public static class PetaPocoExtensions
{
public static void CreateTable<T>(this Database db)
where T : new()
{
var tableType = typeof(T);
CreateTable(db, false, tableType);
}
public static void CreateTable<T>(this Database db, bool overwrite)
where T : new()
{
var tableType = typeof(T);
CreateTable(db, overwrite, tableType);
}
public static void CreateTable(this Database db, bool overwrite, Type modelType)
{
var tableNameAttribute = modelType.FirstAttribute<TableNameAttribute>();
var objProperties = modelType.GetProperties().ToList();
string tableName = tableNameAttribute.Value;
var sql = Sql.Builder.Append(string.Format("CREATE TABLE {0} (", tableName));
var primaryKeyConstraints = new List<Sql>();
var foreignKeyConstraints = new List<Sql>();
var indexes = new List<Sql>();
var last = objProperties.Last();
foreach (var propertyInfo in objProperties)
{
var columnAttribute = propertyInfo.FirstAttribute<ColumnAttribute>();
if(columnAttribute == null) continue;
var sb = new StringBuilder();
sb.AppendFormat("[{0}]", columnAttribute.Name);
var databaseTypeAttribute = propertyInfo.FirstAttribute<DatabaseTypeAttribute>();
if (databaseTypeAttribute != null)
sb.AppendFormat(" {0}", databaseTypeAttribute.ToSqlSyntax());
var nullSettingAttribute = propertyInfo.FirstAttribute<NullSettingAttribute>();
if(nullSettingAttribute != null)
sb.AppendFormat(" {0}", nullSettingAttribute.ToSqlSyntax());
var primaryKeyColumnAttribute = propertyInfo.FirstAttribute<PrimaryKeyColumnAttribute>();
if(primaryKeyColumnAttribute != null)
{
sb.AppendFormat(" {0}", primaryKeyColumnAttribute.ToSqlSyntax());
//Add to list of primary key constraints
primaryKeyConstraints.Add(new Sql(primaryKeyColumnAttribute.ToSqlSyntax(tableName, columnAttribute.Name)));
}
var constraintAttribute = propertyInfo.FirstAttribute<ConstraintAttribute>();
if(constraintAttribute != null)
sb.AppendFormat(" {0}", constraintAttribute.ToSqlSyntax(tableName, columnAttribute.Name));
if (propertyInfo != last)
sb.Append(", ");
sql.Append(sb.ToString());
//Look for foreign keys
var foreignKeyAttribute = propertyInfo.FirstAttribute<ForeignKeyAttribute>();
if (foreignKeyAttribute != null)
{
foreignKeyConstraints.Add(new Sql(foreignKeyAttribute.ToSqlSyntax(tableName, columnAttribute.Name)));
}
//Look for indexes
var indexAttribute = propertyInfo.FirstAttribute<IndexAttribute>();
if(indexAttribute != null)
{
indexes.Add(new Sql(indexAttribute.ToSqlSyntax(tableName, columnAttribute.Name)));
}
}
sql.Append(")");//End
var tableExist = db.TableExist(tableName);
if(overwrite && tableExist)
{
db.DropTable(tableName);
}
if(!tableExist)
{
int created = db.Execute(sql);
foreach (var constraint in primaryKeyConstraints)
{
db.Execute(constraint);
}
foreach (var constraint in foreignKeyConstraints)
{
db.Execute(constraint);
}
foreach (var index in indexes)
{
db.Execute(index);
}
}
#if DEBUG
Console.WriteLine(sql.SQL);
foreach (var constraint in primaryKeyConstraints)
{
Console.WriteLine(constraint.SQL);
}
foreach (var constraint in foreignKeyConstraints)
{
Console.WriteLine(constraint.SQL);
}
foreach (var index in indexes)
{
Console.WriteLine(index.SQL);
}
#endif
}
public static void DropTable<T>(this Database db)
where T : new()
{
Type type = typeof (T);
var tableNameAttribute = type.FirstAttribute<TableNameAttribute>();
if (tableNameAttribute == null)
throw new Exception(
string.Format(
"The Type '{0}' does not contain a TableNameAttribute, which is used to find the name of the table to drop. The operation could not be completed.",
type.Name));
string tableName = tableNameAttribute.Value;
DropTable(db, tableName);
}
public static void DropTable(this Database db, string tableName)
{
var sql = new Sql(string.Format("DROP TABLE {0}", tableName));
db.Execute(sql);
}
public static bool TableExist(this Database db, string tableName)
{
var scalar =
db.ExecuteScalar<long>("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = @tableName",
new {tableName = tableName});
return scalar > 0;
}
}
}

View File

@@ -205,8 +205,7 @@ namespace Umbraco.Core
}
/// <summary>
/// <summary>
/// Determines whether the specified actual type is type.
/// </summary>
/// <typeparam name="T"></typeparam>
@@ -219,27 +218,26 @@ namespace Umbraco.Core
return TypeHelper.IsTypeAssignableFrom<T>(actualType);
}
//internal static string GetCacheKeyFromParameters(this MemberInfo info)
//{
// var methodInfo = info as MethodInfo;
// if (methodInfo != null)
// return GetCacheKeyFromParameters(methodInfo.GetParameters());
// return string.Empty;
//}
public static TAttribute FirstAttribute<TAttribute>(this Type type)
{
return type.FirstAttribute<TAttribute>(true);
}
//internal static string GetCacheKeyFromParameters(IEnumerable<ParameterInfo> parameters)
//{
// var sb = new StringBuilder();
// sb.Append("(");
// foreach (var parameter in parameters)
// {
// sb.Append(parameter.ParameterType);
// sb.Append(" ");
// sb.Append(parameter.Name);
// sb.Append(",");
// }
// sb.Append(")");
// return sb.ToString();
//}
public static TAttribute FirstAttribute<TAttribute>(this Type type, bool inherit)
{
var attrs = type.GetCustomAttributes(typeof(TAttribute), inherit);
return (TAttribute)(attrs.Length > 0 ? attrs[0] : null);
}
public static TAttribute FirstAttribute<TAttribute>(this PropertyInfo propertyInfo)
{
return propertyInfo.FirstAttribute<TAttribute>(true);
}
public static TAttribute FirstAttribute<TAttribute>(this PropertyInfo propertyInfo, bool inherit)
{
var attrs = propertyInfo.GetCustomAttributes(typeof(TAttribute), inherit);
return (TAttribute)(attrs.Length > 0 ? attrs[0] : null);
}
}
}

View File

@@ -104,6 +104,17 @@
<Compile Include="Persistence\Caching\IRepositoryCacheProvider.cs" />
<Compile Include="Persistence\Caching\NullCacheProvider.cs" />
<Compile Include="Persistence\Caching\RuntimeCacheProvider.cs" />
<Compile Include="Persistence\DatabaseAnnotations\AttributeExtensions.cs" />
<Compile Include="Persistence\DatabaseAnnotations\ConstraintAttribute.cs" />
<Compile Include="Persistence\DatabaseAnnotations\DatabaseTypeAttribute.cs" />
<Compile Include="Persistence\DatabaseAnnotations\DatabaseTypes.cs" />
<Compile Include="Persistence\DatabaseAnnotations\ForeignKeyAttribute.cs" />
<Compile Include="Persistence\DatabaseAnnotations\IndexAttribute.cs" />
<Compile Include="Persistence\DatabaseAnnotations\IndexTypes.cs" />
<Compile Include="Persistence\DatabaseAnnotations\NullSettingAttribute.cs" />
<Compile Include="Persistence\DatabaseAnnotations\NullSettings.cs" />
<Compile Include="Persistence\DatabaseAnnotations\PrimaryKeyColumnAttribute.cs" />
<Compile Include="Persistence\DatabaseAnnotations\ReferencesAttribute.cs" />
<Compile Include="Persistence\DatabaseFactory.cs" />
<Compile Include="Persistence\Factories\ContentFactory.cs" />
<Compile Include="Persistence\Factories\ContentTypeFactory.cs" />
@@ -119,6 +130,7 @@
<Compile Include="Persistence\Factories\RelationFactory.cs" />
<Compile Include="Persistence\Factories\RelationTypeFactory.cs" />
<Compile Include="Persistence\Mappers\ModelDtoMapper.cs" />
<Compile Include="Persistence\PetaPocoExtensions.cs" />
<Compile Include="Persistence\Querying\ExpressionHelper.cs" />
<Compile Include="Persistence\Querying\IQuery.cs" />
<Compile Include="Persistence\Querying\Query.cs" />

View File

@@ -7,6 +7,10 @@
</sectionGroup>
</configSections>
<connectionStrings>
<add name="umbracoDbDSN" connectionString="server=.\SQLEXPRESS;database=EmptyForTest;user id=umbraco;password=umbraco" providerName="System.Data.SqlClient"/>
</connectionStrings>
<FileSystemProviders>
<Provider alias="media" type="Umbraco.Core.IO.PhysicalFileSystem, Umbraco.Core">
<Parameters>

View File

@@ -0,0 +1,32 @@
using System;
using System.Configuration;
using NUnit.Framework;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests.Persistence
{
[TestFixture]
public class DatabaseExtensionsTest
{
[SetUp]
public virtual void Initialize()
{
AppDomain.CurrentDomain.SetData("DataDirectory", TestHelper.CurrentAssemblyDirectory);
}
[Test]
public void Can_Create_umbracoNode_Table()
{
var factory = DatabaseFactory.Current;
using(Transaction transaction = factory.Database.GetTransaction())
{
factory.Database.CreateTable<NodeDto>();
transaction.Complete();
}
}
}
}

View File

@@ -67,6 +67,7 @@
<Compile Include="LibraryTests.cs" />
<Compile Include="Models\ContentTests.cs" />
<Compile Include="Models\StylesheetTests.cs" />
<Compile Include="Persistence\DatabaseExtensionsTest.cs" />
<Compile Include="Persistence\DatabaseFactoryTests.cs" />
<Compile Include="Persistence\RepositoryResolverTests.cs" />
<Compile Include="Resolvers\ActionsResolverTests.cs" />