How to factor type parameters out of my code that uses DynamicMethod and Entity Framework?

86 views Asked by At

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;
    }
1

There are 1 answers

0
phil soady On

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.

    /// <summary>
    /// The sorted page list is the main way of retrieving a subset of data.  
    /// </summary>
    /// <typeparam name="TSortKey"></typeparam>
    /// <param name="predicate"></param>
    /// <param name="sortBy"></param>
    /// <param name="descending"></param>
    /// <param name="skipRecords"></param>
    /// <param name="takeRecords"></param>
    /// <returns></returns>
    public virtual IQueryable<TPoco> GetSortedPageList<TSortKey>(Expression<Func<TPoco, bool>> predicate,
                                     Expression<Func<TPoco, TSortKey>> sortBy, bool descending, int skipRecords, int takeRecords) {
        if (!descending) {
            return Context.Set<TPoco>().Where<TPoco>(predicate)
                     .OrderBy(sortBy)
                    .Skip(skipRecords)
                    .Take(takeRecords);
        }
        return
            Context.Set<TPoco>().Where<TPoco>(predicate)
                .OrderByDescending(sortBy)
                .Skip(skipRecords)
                .Take(takeRecords);
    }

SAMPLE CALL

[TestMethod]
    public void BaseRepositoryPagingTest() {
        var luw = new LuwMaster();

        var someMore = true;
        var skip = 0;
        var take = 3;

        while (someMore) {
            var mimeList = luw.GetRepository<MimeType>().GetSortedPageList(m => true, m => m.Id, true, skip, take);
            someMore = false;
            foreach (var mimeType in mimeList) {
                Debug.WriteLine( mimeType.MimeTypeCategory + ", " + mimeType.Id);
                someMore = true;
            }
            skip = skip + take;
        }
    }