I am creating a framework that is meant to allow web developers to create CRUD pages without having to go thru repetitive steps.
To that end, I want to provide "default" methods that do things like sorting, filtering, and the like. A developer using my framework can provide his own method if he chooses, but there should be a default method that works in many cases.
It is especially important to me that this be compatible with Entity Framework.
I have successfully implemented a default filtering function using the DynamicMethod
class that allows for "generic" filtering by search key on arbitrary classes generated by Entity Framework.
I'm trying to do something similar with sorting, but I'm running into a problem. The OrderBy
method takes two type parameters, the first of which is the entity type, and the second being the "key" type. This key typed needs to match the type of the column that the user wants to sort by, and in order for this to work with all entity classes, the type needs to be determined at runtime.
I've used reflection to discover the type of the column, but I don't know how to cast my "comparer" function to the appropriate Func<> type. Therefore, I've set up a switch statement that switches on the type. I want to get rid of the switch statement and factor out the key type.
How do I do this? My code follows below:
public override IEnumerable<T> GetSortedRecords<T>(IEnumerable<T> filteredRecords)
{
// The goal here is to allow for a developer to provide a method for sorting records,
// but to also provide a default method that will work with all instances of EntityObject.
// To that end, I believe it is necessary to use the fun fun DynamicMethod object.
// Note - check for ASC vs DESC
if (NumberOfSorts == 0)
return filteredRecords;
IOrderedEnumerable<T> sortedRecords = null;
for (int i = 0; i < NumberOfSorts; i++)
{
string propertyName = PropertyNames[ColumnSortOrder[i]];
PropertyInfo property = typeof(T).GetProperties().Single(p => p.Name == propertyName);
switch (property.PropertyType.Name)
{
// I want to get rid of these branches.
case "Int32":
Func<T, int> comparerInt32 = GetDefaultKeySelectorForProperty<T, Int32>(property);
sortedRecords = i == 0 ?
filteredRecords.OrderBy<T, int>(comparerInt32) :
sortedRecords.ThenBy<T, int>(comparerInt32);
break;
case "String":
Func<T, string> comparerString = GetDefaultKeySelectorForProperty<T, string>(property);
sortedRecords = i == 0 ?
filteredRecords.OrderBy<T, string>(comparerString) :
sortedRecords.ThenBy<T, string>(comparerString);
break;
default:
throw new NotImplementedException();
}
}
return sortedRecords;
}
delegate TKey GetDefaultKeySelectorForPropertyDelegate<T, TKey>(T t);
public Func<T, TKey> GetDefaultKeySelectorForProperty<T, TKey>(PropertyInfo property)
{
DynamicMethod method = new DynamicMethod("GetKeySelector", typeof(TKey), new Type[] { typeof(T) });
ILGenerator generator = method.GetILGenerator();
MethodInfo GetPropertyValue = property.GetGetMethod();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Callvirt, GetPropertyValue);
generator.Emit(OpCodes.Ret);
return ((GetDefaultKeySelectorForPropertyDelegate<T, TKey>)(method.CreateDelegate(typeof(GetDefaultKeySelectorForPropertyDelegate<T, TKey>)))).Invoke;
}
If you want to do true dynamic calls, where the calling site is also dealing with runtime fields then There are 2 basic approaches to Dynamic Expressions and Queries in LINQ.
a) String Dynamic Lambda
System.Linq.Dynamic can be found at following links
http://msdn.microsoft.com/en-US/vstudio/bb894665.aspx
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
http://www.scottgu.com/blogposts/dynquery/dynamiclinqcsharp.zip
b) Build Expression trees
More powerful but harder to master... Build expressions trees with code found here: http://msdn.microsoft.com/en-us/library/system.linq.expressions.aspx
However if you only need the Resposityory pattern generic and the calling site will know can supply the predicates then something like this.
SAMPLE CALL