使用 Linq 对 Sql OrderBy 进行分页 API 调用,使用字符串参数和反射

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

我正在尝试概括 API 的寻呼呼叫。如果我有一个从网页传递的过滤器,我希望能够 return 基于页码和页面大小的一系列项目。如果 OrderBy 使用 set 参数或者可能对每个调用都使用难看的 switch 语句,那么这很容易做到。

目前,我正在尝试使用表达式和反射(对此我没有经验)来扩展 IQueryable,我找到了几个示例作为起点,如果我使用 属性 这是一个简单的类型,它就可以工作在根 class 中(传递字符串 "FixValidatedCount")。如果我尝试通过嵌套 class 属性 进行排序,就像下面的 Island.Name 示例一样,我将无法使用它。

有什么方法可以更新我的表达式以接受嵌套的 class / 属性吗?或者有更好的方法吗?

    [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
        });
    }

IssueSummary 是查询的 return 类型。

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

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

获得具有嵌套支持的 property/field 访问器表达式的最简单方法如下:

var root = Expression.Parameter(typeof(T), "x");
var member = sortField.Split('.').Aggregate((Expression)root, Expression.PropertyOrField);
var selector = Expression.Lambda(member, root);

这里是完整的扩展方法:

public static IQueryable<T> OrderByField<T>(this IQueryable<T> source, string sortField, bool ascending)
{
    var root = Expression.Parameter(typeof(T), "x");
    var member = sortField.Split('.').Aggregate((Expression)root, Expression.PropertyOrField);
    var selector = Expression.Lambda(member, root);
    var method = ascending ? "OrderBy" : "OrderByDescending";
    var types = new[] { typeof(T), member.Type };
    var mce = Expression.Call(typeof(Queryable), method, types,
        source.Expression, Expression.Quote(selector));
    return source.Provider.CreateQuery<T>(mce);
}