用于扩展方法的 Net Core Linq 中的 OrderBy 表达式树 [英] OrderBy Expression Tree in Net Core Linq for Extension Method
问题描述
我想创建一个模仿这个的扩展方法,
<块引用>工作原理:
使用 Queryable
类型的扩展方法创建了一个表达式,它在内部调用 Queryable
类型的 OrderBy
方法,期望 IQueryable
作为输入,连同字段名称,从而运行排序函数,有序集合是最终的输出
选项 2:
这可能更适合您的用例,这里不是调用 OrderBy
方法,而是创建 Expression
作为扩展方法到 IEnumerable
,然后可以编译并提供给 OrderBy Call,如示例所示,因此是更直观和简单的解决方案:
创建表达式:
公共静态类 ExpressionTreesExtesion{公共静态表达式<Func<T,string>>OrderByExpression(这个 IEnumerable 可枚举,字符串 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>(propertyAccess, parameterExpression);退货单表达式;}}
<块引用>
如何打电话:
var ProductExpression = records.OrderByExpression("Name");var 结果 = records.OrderBy(ProductExpression.Compile());
上面的
ProductExpression.Compile()
将编译成 x =>x.Name
,在运行时提供列名
请注意,如果排序字段可以是字符串数据类型之外的其他类型,那么在调用扩展方法时也将其设置为泛型并提供它,只有被调用的属性的条件应与提供的值具有相同的类型,否则它将成为运行时异常,同时创建表达式
<块引用>编辑 1,如何使 OrderType 字段也通用
public static Expression>OrderByFunc(这个 IEnumerable 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>(propertyAccess, parameterExpression);退货单表达式;}
<块引用>
如何调用:
现在需要显式提供这两种类型,之前使用来自 IEnumerable
的泛型类型推断:
//对于整数 ID 字段
var ProductExpression = records.OrderByFunc
//对于字符串名称字段
var ProductExpression = records.OrderByFunc
I want to create an Extension method which mimics this, https://dejanstojanovic.net/aspnet/2019/january/filtering-and-paging-in-aspnet-core-web-api/
However, I want to add an OrderBy (for ColumnName) after StartsWith, how would I conduct this?
tried adding following and did not work .OrderBy(parameter)
Example:
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);
}
Other items 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());
}
}
Dynamic Linq to Entities Orderby with Pagination
Check the sample code for the solution:
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));
}
}
Result
How it Works:
Created an expression using extension method on the Queryable
type, which internally calls OrderBy
method of the Queryable
type, expecting IQueryable
to be the Input, along with the field name and thus runs the ordering function and Ordered collection is the final Output
Option 2:
This may fit your use case better, here instead of calling OrderBy
method, we are creating the Expression<Func<T,string>>
as an extension method to the IEnumerable<T>
, which can then be compiled and supplied to the OrderBy Call, as shown in the example and is thus much more intuitive and simple solution:
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()
above will compile into x => x.Name
, where column name is supplied at the run-time
Please note in case the ordering field can be other types beside string data type, then make that also generic and supply it while calling extension method, only condition being property being called shall have the same type as supplied value, else it will be a run-time exception, while creating Expression
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:
Now both the types need to be explicitly supplied, earlier were using generic type inference from IEnumerable<T>
:
// For Integer Id field
var ProductExpression = records.OrderByFunc<Product,int>("Id");
// For string name field
var ProductExpression = records.OrderByFunc<Product,string>("Name");
这篇关于用于扩展方法的 Net Core Linq 中的 OrderBy 表达式树的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!