0
votes

I am attempting to use a web grid helper in conjunction with a generic repository to add column sorting. THe action result form the view with the grid helper has a parameter for the sort column (string). In my generic method signature I need to pass in a lambda expression based on the property name of the domain model (see below).

public IEnumerable<T>GetAllPagingAndSorting<TKey>(out int totalRecords, 
    int pageSize, int pageIndex, Expression<Func<T, TKey>> orderingKey, 
    SortDirection sortOrder, 
    params Expression<Func<T, object>>[] includes)
{}        

So for example I want to map a property name of "Name" and type of "string" to m=>m.Name.

I have tries using a dictionary as in the following way but it throws an error when calling the repository method as the type is now object instead of int,string etc....

private IDictionary<string,Expression<Func<MyModel,object>>> _orderings =
            new Dictionary<string, Expression<Func<MyModel,object>>> 
                {
                    {"Id",(m=>m.Id)},
                    {"Name",m=>m.UserName},
                    {"DateRequired",m=>m.DateRequired},
                    {"AssignedTo",m=>m.TeamMember.MemberName},
                    {"RequestedBy",m=>m.RequestedBy},

            };

Should I use a method instead? In either case how can I use the above to match the input property and return the Lambda expression with the correct type?

Update: Here's my Action in the controller....thought I'd try and get the ordering key as Lambda here as I use generic repository....

Generic respoitory method defined: IEnumerable GetAllPagingAndSorting(out int totalRecords, int pageSize, int pageIndex,Expression> orderingKey, SortDirection sortOrder, params Expression>[] includes);

  public ActionResult ServerPagingAndSorting(int page = 1, string sort = "Id", string sortDir = "Ascending")
        {


            int totalRecords;
            var viewModel =new SupportRequestsIndexVM(supportrequestRepository.GetAllPagingAndSorting(out totalRecords, PageSize,page - 1,_orderings[sort] ,GetSortDirection(sortDir),(m=>m.TeamMember)))
                    {PageSize = PageSize, PageNumber = page, TotalRows = totalRecords};

            return View(viewModel);
        }
2
apologies, I have updated the questions.... - Euston
Can you edit your question and show the code you're using to get the relevant ordering and call GetAllPagingAndSorting<TKey>? - Nick Butler
@Euston As a point your TKey in this case will most likely have to implement IComparable, so without seeing more structure I would suggest change Object -> IComparable in the dictionary definition and see if it still throws the error - user1793607
@Nicholas Butler /user1793607 - I've added some more code which hopefully shows what I am trying to attempt. How would I use IComparable instead of Object? - Euston

2 Answers

1
votes

The problem is that the expression (m=>m.Id), which is of type Expresion<Func<MyModel, int>> will automatically receive an additional cast to object to match Expresion<Func<MyModel, object>>. You don't see the cast in the code, but you can observe it analyzing the Expression Tree.

My approach is to

  • encapsulate all query parameters, such as paging and sort order into a class
  • encapsulate the query result in a class (total records, selected records)

Thus my solution looks like this

public class QueryResult<T> {
  public int TotalRecords;
  public List<T> Records;
}

public QueryResult<T> GetRecords<T>(QueryParams p)
{
    IEnumerable<T> q = BuildQueryWithConditions<T>(p);
    var result = new QueryResult<T> { TotalRecords = q.Count() };
    q = ApplySortOrder(p);
    q = ApplyPaging(p);
    result.Records = q.ToList();
    return result;
}

The ApplySortOrder is a per-entity function interpreting SortColumn and SortOrder:

switch (p.SortColumn)
{
    case "Column1":
        if (desc)
            queryDef = queryDef.OrderByDescending(record => record.Column1);
        else
            queryDef = queryDef.OrderBy(record => record.Column1);
    break;
    ....
}

To handle sorting per entity, you need to pass an IEnumerable<T> to a function and return an IOrderedEnumerable<T>. Since we cannot have generic types in a dictionary that covers different entities, the signature looks like this:

Dictionary<Type, Expression<Func<IEnumerable, IEnumerable>>>

Additionally define a method Add<T>(Expression<Func<IEnumerable<T>, IOrderedEnumerable<T>>>) to add to the dictionary, and Get() to retrieve the sort expression.

0
votes

I am now using this code to apply the sorting taken form another stack overflow q. I pass in the string to the generic repository and then call this method as follows:

Here's my repository method:

public IEnumerable<T> GetAllPagingAndSorting(out int totalRecords, int pageSize, int pageIndex, string orderingKey, string sortDir, params Expression<Func<T, object>>[] includes)
        {
                IQueryable<T> results = Entities;

                totalRecords = results.Count();

                // apply any includes
                if (includes != null)
                {
                    results = includes.Aggregate(results, (current, include) => current.Include(include));
                }

                // apply sorting    
                results = GetSortDirection(sortDir) == SortDirection.Ascending ? ApplyOrder(results, orderingKey, ORDERBY) : ApplyOrder(results, orderingKey, ORDERBYDESC);


                if (pageSize > 0 && pageIndex >= 0)
                {
                    // apply paging
                    results = results.Skip(pageIndex * pageSize).Take(pageSize);
                }

                return results.ToList();
        }


 protected static IOrderedQueryable<T> ApplyOrder(IQueryable<T> source, string property, string methodName)
        {
            string[] props = property.Split('.');
            Type type = typeof(T);
            ParameterExpression arg = Expression.Parameter(type, "m");
            Expression expr = arg;
            foreach (string prop in props)
            {
                // use reflection (not ComponentModel) to mirror LINQ
                PropertyInfo pi = type.GetProperty(prop);
                expr = Expression.Property(expr, pi);
                type = pi.PropertyType;
            }
            Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
            LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);

            object result = typeof(Queryable).GetMethods().Single(
                    method => method.Name == methodName
                            && method.IsGenericMethodDefinition
                            && method.GetGenericArguments().Length == 2
                            && method.GetParameters().Length == 2)
                    .MakeGenericMethod(typeof(T), type)
                    .Invoke(null, new object[] { source, lambda });
            return (IOrderedQueryable<T>)result;
        }