jeudi 25 février 2016

Paging API calls using Linq to Sql OrderBy with string parameter and reflection

I am trying to generalize paging calls for an API. If I have a filter passed from the web page, I want to be able to return a range of items based off from the page number and page size. This is pretty simple to do if the OrderBy uses a set parameter or possibly using an ugly switch statement for every single call.

Currently I am trying to extend IQueryable using expressions and reflection (of which I am not experienced in) I found a couple examples to start from and it works if I use a property that is a simple type in the root class (passing the string "FixValidatedCount"). I can not get it to work if I try to order by a nested class property like the Island.Name example below.

Is there any way to updated my expression to accept nested class / properties? Or is there a better way to do this?

    [Route("api/issues/paged")]
    [HttpPost]
    public HttpResponseMessage GetIssuesPage(EntityPageFilter filter)
    {
        //THE BELOW COMMENTED OUT OBJECT IS THE PARAMETER 
        //var filter = new EntityPageFilter
        //{
        //    PageSize = 5,
        //    PageNumber = 1,
        //    OrderBy = "Island.Name",
        //    OrderByAscending = true
        //};

        var query = _issueService.GetIssues()
            .OrderByField(filter.OrderBy, filter.OrderByAscending)
            //.OrderBy(i => i.Island.Name)  THIS WORKS, BUT HOW DO I DO THIS WITH A STRING
            .Skip(filter.Skip).Take(filter.PageSize);

        return Request.CreateResponse(HttpStatusCode.OK, new PagedEntity<IssueSummary>
        {
            PageNumber = filter.PageNumber,
            PageSize = filter.PageSize,
            ItemCount = _issueService.GetIssues().Count(),
            Data = query
        });
    }

The IssueSummary is the return type of the query.

public class Issue
{
    public int Id { get; set; }
    public Island Island { get; set; }
    public Type Type { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime? FixedAt { get; set; }
}

public class IssueSummary: Issue
{
    public int FixCount { get; set; }
    public int FixValidatedCount { get; set; }
}

public class Island
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
}

The extension method for IQueryable

    public static IQueryable<T> OrderByField<T>(this IQueryable<T> q, string sortField, bool ascending)
    {
        var properties = sortField.Split('.').ToList();

        var xType = typeof(T);

        for (var i = 0; i < properties.Count - 1; i++)
        {
            xType = xType.GetProperty(properties[i]).PropertyType;
        }

        var param = Expression.Parameter(xType, String.Empty);
        var prop = Expression.Property(param, properties.Last());

        var exp = Expression.Lambda(prop, param);
        var method = ascending ? "OrderBy" : "OrderByDescending";
        //var types = new[] { q.ElementType, exp.Body.Type };
        var types = new[] { xType, exp.Body.Type };
        var mce = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
        return q.Provider.CreateQuery<T>(mce);
    }





Aucun commentaire:

Enregistrer un commentaire