Skip to content

Instantly share code, notes, and snippets.

@HerrKater
Created September 9, 2013 20:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HerrKater/6501214 to your computer and use it in GitHub Desktop.
Save HerrKater/6501214 to your computer and use it in GitHub Desktop.
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