Created
September 9, 2013 20:41
-
-
Save HerrKater/6501214 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Linq.Expressions; | |
namespace Utility | |
{ | |
using System; | |
using System.Collections.Generic; | |
using System.ComponentModel.DataAnnotations.Schema; | |
using System.Data.Entity; | |
using System.Linq; | |
using System.Reflection; | |
/// <summary> | |
/// This class provides the following additional functionalities for Entity Framework 5.0 code first: | |
/// Enum support, | |
/// Ability to set the decimal scale and precision properties of the database column | |
/// </summary> | |
public class EntityHelper | |
{ | |
/// <summary> | |
/// This method creates lookup tables for enums | |
/// </summary> | |
/// <param name="context">the actual DbContext</param> | |
/// <remarks> | |
/// Declare a public property of type EntityHelper.EnumSet{TEnum} in the appropriate DbContext class | |
/// where TEnum is the type of the enum. | |
/// e.g. : public EntityHelper.EnumSet{TEnum} EnumType { get; set; } | |
/// For initializing the database, create a DbContext instance and | |
/// call the CreateEnumTables method passing the actual DbContext as argument. | |
/// </remarks> | |
public static void CreateEnumTables(DbContext context) | |
{ | |
var contextProperties = context.GetType().GetProperties(); | |
var enumsetType = typeof (EnumSet<>); | |
var enumSets = | |
contextProperties.Where(p => | |
p.PropertyType.IsGenericType | |
&& p.PropertyType.GetGenericTypeDefinition() == enumsetType); | |
foreach (var enumType in enumSets) | |
{ | |
var referencingTpyes = GetReferencingTypes(enumType, contextProperties); | |
CreateEnumTable(enumType, referencingTpyes, context); | |
} | |
} | |
//[ExceptionHandlingAspect] | |
private static void CreateEnumTable(PropertyInfo enumProperty, IEnumerable<PropertyInfo> referencingTypes, | |
DbContext context) | |
{ | |
var enumType = enumProperty.PropertyType.GetGenericArguments()[0]; | |
#region create table | |
var createTableCommand = string.Format( | |
"CREATE TABLE {0} ([Id] [int] NOT NULL,[Value] [varchar](100) NOT NULL,CONSTRAINT pk_{0}_Id PRIMARY KEY (Id));", | |
enumType.Name); | |
context.Database.ExecuteSqlCommand(createTableCommand); | |
#endregion | |
#region insert values | |
foreach (var enumValue in Enum.GetValues(enumType)) | |
{ | |
createTableCommand = string.Format("INSERT INTO {0} VALUES({1},'{2}');", enumType.Name, (int) enumValue, | |
enumValue); | |
context.Database.ExecuteSqlCommand(createTableCommand); | |
} | |
#endregion | |
#region foreign keys | |
foreach (var referencingType in referencingTypes.Distinct()) | |
{ | |
var tableType = referencingType.PropertyType.GetGenericArguments()[0]; | |
foreach (var propertyInfo in tableType.GetProperties()) | |
{ | |
if (propertyInfo.PropertyType != enumType) continue; | |
var tableName = tableType.Name; | |
var tablesAttributes = | |
(TableAttribute[]) tableType.GetCustomAttributes(typeof (TableAttribute), false); | |
if (tablesAttributes.Any() && !string.IsNullOrEmpty(tablesAttributes[0].Schema)) | |
tableName = tablesAttributes[0].Schema + "." + tableName; | |
var foreignLeyCreateCommand = | |
string.Format( | |
"ALTER TABLE {0} ADD CONSTRAINT [FK_{0}_{1}_{2}] FOREIGN KEY({2}) REFERENCES {1}([Id])", | |
tableName, enumProperty.Name, propertyInfo.Name | |
); | |
try | |
{ | |
context.Database.ExecuteSqlCommand(foreignLeyCreateCommand); | |
} | |
catch (Exception ex) | |
{ | |
//TODO:log | |
} | |
} | |
} | |
#endregion | |
} | |
private static IEnumerable<PropertyInfo> GetReferencingTypes(PropertyInfo enumProperty, | |
IEnumerable<PropertyInfo> contextProperties) | |
{ | |
var result = new List<PropertyInfo>(); | |
try | |
{ | |
var enumType = enumProperty.PropertyType.GetGenericArguments()[0]; | |
var genericDbSetType = typeof (DbSet<>); | |
foreach (var contextProperty in contextProperties) | |
{ | |
if (!contextProperty.PropertyType.IsGenericType || | |
contextProperty.PropertyType.GetGenericTypeDefinition() != genericDbSetType) continue; | |
var tableType = contextProperty.PropertyType.GetGenericArguments()[0]; | |
foreach (var propertyInfo in tableType.GetProperties()) | |
{ | |
if (propertyInfo.PropertyType == enumType) | |
result.Add(contextProperty); | |
} | |
} | |
} | |
catch (Exception ex) | |
{ | |
//TODO:log | |
} | |
return result; | |
} | |
public class EnumSet<T> | |
{ | |
} | |
/// <summary> | |
/// This method helps to avoid boilerplate code at specify precision and scale | |
/// </summary> | |
/// <param name="modelBuilder">The DbModelBuilder instance</param> | |
/// <typeparam name="T">The type of the DbContext</typeparam> | |
/// <remarks> | |
/// usage: | |
/// add the DecimalPrecisionAttribute to appropriate properties of the Entity class | |
/// e.g. public class EmployeeEntity{ | |
/// [DecimalPrecision(10,2)] | |
/// public decimal Salary {get;set;} | |
/// ... | |
/// } | |
/// override the OnModelCreating method in the DbContext class and call the ProcessDecimalProperties method | |
/// passing the modelBuilder instance as an argument. | |
/// </remarks> | |
public static void ProcessDecimalProperties<T>(DbModelBuilder modelBuilder) where T : DbContext | |
{ | |
var contextProperties = | |
typeof (T).GetProperties() | |
.Where(p => | |
p.PropertyType.IsGenericType | |
&& p.PropertyType.GetGenericTypeDefinition() == typeof (DbSet<>)); | |
foreach (var tableTypeProperty in contextProperties) | |
{ | |
if (!tableTypeProperty.PropertyType.IsGenericType) | |
continue; | |
var tableType = tableTypeProperty.PropertyType.GetGenericArguments()[0]; | |
var entityMethodInfo = typeof (DbModelBuilder).GetMethod("Entity"); | |
MethodInfo genericMethodEntity = entityMethodInfo.MakeGenericMethod(tableType); | |
// modelBuilder.Entity<T>(). | |
var entityTypeConfiguraton = genericMethodEntity.Invoke(modelBuilder, null); | |
foreach (var columnProperty in tableType.GetProperties()) | |
{ | |
var decimalAttrib = | |
(DecimalPrecisionAttribute[]) | |
columnProperty.GetCustomAttributes(typeof (DecimalPrecisionAttribute), false); | |
if (decimalAttrib.Any()) | |
{ | |
//input => input.DecimalProperty | |
var inParam = Expression.Parameter(tableType, "input"); | |
var propertyAccess = Expression.Property(inParam, columnProperty); | |
Type doubleGenericDelegateType = typeof (Func<string, string>).GetGenericTypeDefinition(); | |
Type extensionMethodDelegate = doubleGenericDelegateType.MakeGenericType(new[] { tableType, typeof(decimal) }); | |
var propertyMethodExpression = Expression.Lambda(extensionMethodDelegate, propertyAccess, inParam); | |
//.Property(input => input.DecimalProperty) | |
MethodInfo methodProperty = entityTypeConfiguraton.GetType() | |
.GetMethod("Property", new[] { propertyMethodExpression.GetType() }); | |
var decimalPropertyConfiguration = methodProperty.Invoke(entityTypeConfiguraton, | |
new object[] { propertyMethodExpression }); | |
//.HasPrecision | |
MethodInfo methodHasPrecision = decimalPropertyConfiguration.GetType().GetMethod("HasPrecision"); | |
methodHasPrecision.Invoke(decimalPropertyConfiguration, | |
new object[] {decimalAttrib[0].Precision, decimalAttrib[0].Scale}); | |
} | |
} | |
} | |
} | |
public class DecimalPrecisionAttribute : Attribute | |
{ | |
public DecimalPrecisionAttribute(byte precision, byte scale) | |
{ | |
Precision = precision; | |
Scale = scale; | |
} | |
public byte Precision { get; private set; } | |
public byte Scale { get; private set; } | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment