Net Core Linq 中用于扩展方法的 OrderBy 表达式树

OrderBy Expression Tree in Net Core Linq for Extension Method

我想创建一个模仿这个的扩展方法,https://dejanstojanovic.net/aspnet/2019/january/filtering-and-paging-in-aspnet-core-web-api/

但是,我想在 StartsWith 之后添加一个 OrderBy(用于 ColumnName),我该如何操作?

尝试添加以下内容但没有成功。OrderBy(参数)

示例:

return persons.Where(p => p.Name.StartsWith(filterModel.Term ?? String.Empty, StringComparison.InvariantCultureIgnoreCase))
   .OrderBy(c=>c.Name)  
   .Skip((filterModel.Page-1) * filter.Limit)  
   .Take(filterModel.Limit);  


public static class PaginateClass
{
    static readonly MethodInfo startsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string), typeof(System.StringComparison) });

    public static IEnumerable<T> Paginate<T>(this IEnumerable<T> input, PageModel pageModel, string columnName) where T : class
    {
        var type = typeof(T);
        var propertyInfo = type.GetProperty(columnName);
        //T p =>
        var parameter = Expression.Parameter(type, "p");
        //T p => p.ColumnName
        var name = Expression.Property(parameter, propertyInfo);
        // filterModel.Term ?? String.Empty
        var term = Expression.Constant(pageModel.Term ?? String.Empty);
        //StringComparison.InvariantCultureIgnoreCase
        var comparison = Expression.Constant(StringComparison.InvariantCultureIgnoreCase);
        //T p => p.ColumnName.StartsWith(filterModel.Term ?? String.Empty, StringComparison.InvariantCultureIgnoreCase)
        var methodCall = Expression.Call(name, startsWith, term, comparison);

        var lambda = Expression.Lambda<Func<T, bool>>(methodCall, parameter);


            return input.Where(lambda.Compile()) //tried adding this and did not work .OrderBy(parameter)  
            .Skip((pageModel.Page - 1) * pageModel.Limit)
            .Take(pageModel.Limit);

    }

其他项目 PageModel:

public class PageModel
{

    public int Page { get; set; }
    public int Limit { get; set; }
    public string Term { get; set; }

    public PageModel()
    {
        this.Page = 1;
        this.Limit = 3;
    }

    public object Clone()
    {
        var jsonString = JsonConvert.SerializeObject(this);
        return JsonConvert.DeserializeObject(jsonString, this.GetType());
    }
}

查看示例代码解决方案:

void Main()
{
    var queryableRecords = Product.FetchQueryableProducts();

    Expression expression = queryableRecords.OrderBy("Name");

    var func = Expression.Lambda<Func<IQueryable<Product>>>(expression)
                         .Compile();

    func().Dump();
}

public class Product
{
    public int Id { get; set; }

    public string Name { get; set; }

    public static IQueryable<Product> FetchQueryableProducts()
    {
        List<Product> productList = new List<Product>()
        {
          new Product {Id=1, Name = "A"},
          new Product {Id=1, Name = "B"},
          new Product {Id=1, Name = "A"},
          new Product {Id=2, Name = "C"},
          new Product {Id=2, Name = "B"},
          new Product {Id=2, Name = "C"},
        };

        return productList.AsQueryable();
    }
}

public static class ExpressionTreesExtesion
{

    public static Expression OrderBy(this IQueryable queryable, string propertyName)
    {
        var propInfo = queryable.ElementType.GetProperty(propertyName);

        var collectionType = queryable.ElementType;

        var parameterExpression = Expression.Parameter(collectionType, "g");
        var propertyAccess = Expression.MakeMemberAccess(parameterExpression, propInfo);
        var orderLambda = Expression.Lambda(propertyAccess, parameterExpression);
        return Expression.Call(typeof(Queryable),
                               "OrderBy",
                               new Type[] { collectionType, propInfo.PropertyType },
                               queryable.Expression,
                               Expression.Quote(orderLambda));

    }


}

结果

How it Works:

Queryable 类型上使用扩展方法创建了一个表达式,它在内部调用 Queryable 类型的 OrderBy 方法,期望 IQueryable 作为输入,带有字段名称,因此 运行s 排序函数和有序集合是最终输出

Option 2:

这可能更适合您的用例,这里我们不是调用 OrderBy 方法,而是创建 Expression<Func<T,string>> 作为 IEnumerable<T> 的扩展方法,然后可以对其进行编译并提供给 OrderBy 调用,如示例所示,因此是更加直观和简单的解决方案:

Creating Expression:

public static class ExpressionTreesExtesion
{
    public static Expression<Func<T,string>> OrderByExpression<T>(this IEnumerable<T> enumerable, string propertyName)
    {
        var propInfo = typeof(T).GetProperty(propertyName);

        var collectionType = typeof(T);

        var parameterExpression = Expression.Parameter(collectionType, "x");
        var propertyAccess = Expression.MakeMemberAccess(parameterExpression, propInfo);
        var orderExpression = Expression.Lambda<Func<T,string>>(propertyAccess, parameterExpression);
        return orderExpression;
    }
}

How to Call:

var ProductExpression = records.OrderByExpression("Name");

var result  = records.OrderBy(ProductExpression.Compile());
上面的

ProductExpression.Compile() 将编译成 x => x.Name,其中列名在 运行-time

时提供

请注意,如果排序字段可以是除字符串数据类型之外的其他类型,则将其也设为泛型并在调用扩展方法时提供它,只有被调用的条件 属性 应具有与提供的值,否则它将是 运行 时间异常,同时创建表达式

Edit 1, how to make the OrderType field also generic

public static Expression<Func<T, TField>> OrderByFunc<T,TField>(this IEnumerable<T> enumerable, string propertyName)
    {
        var propInfo = typeof(T).GetProperty(propertyName);

        var collectionType = typeof(T);

        var parameterExpression = Expression.Parameter(collectionType, "x");
        var propertyAccess = Expression.MakeMemberAccess(parameterExpression, propInfo);
        var orderExpression = Expression.Lambda<Func<T, TField>>(propertyAccess, parameterExpression);
        return orderExpression;
    }

How to call:

现在这两种类型都需要显式提供,之前使用的是来自 IEnumerable<T> 的泛型类型推断:

// 对于整数 Id 字段

var ProductExpression = records.OrderByFunc<Product,int>("Id");

// 对于字符串名称字段

var ProductExpression = records.OrderByFunc<Product,string>("Name");