基于子实体的属性构建 OrderBy Lambda 表达式

Posted

技术标签:

【中文标题】基于子实体的属性构建 OrderBy Lambda 表达式【英文标题】:Building an OrderBy Lambda expression based on child entity's property 【发布时间】:2012-07-13 13:11:27 【问题描述】:

我正在尝试使用 lambda 表达式生成一个 LINQ OrderBy 子句,并将实体的列名作为字符串输入(在下面的“sortOn”变量中)。

下面的代码适用于 sortOn 值,例如生成 lambda 的“代码”

p => p.Code

但我也想对子实体进行排序,其中 lambda 可能是

p => p.Category.Description

所以在这种情况下,我只想设置 sortOn = "Category.Description" 并生成正确的 lamdba 表达式。

这可能吗?任何关于最佳方式的建议都将受到欢迎。

此代码适用于简单的情况:

var param = Expression.Parameter(typeof (Product), "p");

var sortExpression = Expression.Lambda<Func<Product, object>>(
    Expression.Property(param, sortOn), param);

if (sortAscending ?? true)

   products = products.OrderBy(sortExpression);

else

   products = products.OrderByDescending(sortExpression);

此问题的用例是显示数据网格并能够对数据进行排序,只需将要排序的列名传递回服务器即可。我想让解决方案通用,但现在已经开始使用特定类型(示例中的产品)。

【问题讨论】:

为什么要手动创建表达式? 【参考方案1】:

这将生成正确的 lambda 表达式:

var sortOn = "Category.Description";
var param = Expression.Parameter(typeof(Product), "p");
var parts = sortOn.Split('.');

Expression parent = param;

foreach (var part in parts)

    parent = Expression.Property(parent, part);


var sortExpression = Expression.Lambda<Func<Product, object>>(parent, param);

【讨论】:

【参考方案2】:

您可以使用Dynamic LINQ Query Library 轻松完成此操作。假设您有 IQueryable&lt;T&gt; implementation 或 Product,您可以轻松做到:

IQueryable<Product> products = ...;

// Order by dynamically.
products = products.OrderBy("Category.Description");

博客文章有一个link to the libary,您必须自己在解决方案中构建/包含该项目,但它运行良好,并且解析非常健壮。它使您不必自己编写解析代码;即使对于如此简单的事情,如果需求扩展,库也可以满足您的需求,而本土解决方案则不能。

它还有许多其他动态运算符(SelectWhere 等),因此您可以执行其他动态操作。

幕后没有什么神奇之处,它只是解析您传递给它的字符串,然后根据解析结果创建 lambda 表达式。

【讨论】:

感谢您的建议。可能会标记为答案,但 Tomek 给了我想要的方法。肯定会考虑将此作为替代解决方案。【参考方案3】:

这是一个扩展 OrderBy 方法,适用于任意数量的嵌套参数。

public static IQueryable<T> OrderBy<T>(this IQueryable<T> query, string key, bool asc = true)

  try
  
    string orderMethodName = asc ? "OrderBy" : "OrderByDescending";
    Type type = typeof(T);
    Type propertyType = type.GetProperty(key)?.PropertyType; ;

    var param = Expression.Parameter(type, "x");
    Expression parent = param;

    var keyParts = key.Split('.');
    for (int i = 0; i < keyParts.Length; i++)
    
      var keyPart = keyParts[i];
      parent = Expression.Property(parent, keyPart);

      if (keyParts.Length > 1)
      
        if (i == 0)
        
          propertyType = type.GetProperty(keyPart).PropertyType;
        
        else
        
          propertyType = propertyType.GetProperty(keyPart).PropertyType;
        
      
    

    MethodCallExpression orderByExpression = Expression.Call(
      typeof(Queryable),
      orderMethodName,
      new Type[]  type, propertyType ,
      query.Expression,
      CreateExpression(type, key)
    );

    return query.Provider.CreateQuery<T>(orderByExpression);
  
  catch (Exception e)
  
    return query;
  

我的解决方案中使用的CreateExpression 方法定义为in this post。

OrderBy扩展方法的用法如下。

IQueryable<Foo> q = [Your database context].Foos.AsQueryable();
IQueryable<Foo> p = null;

p = q.OrderBy("myBar.name");  // Ascending sort
// Or
p = q.OrderBy("myBar.name", false);  // Descending sort
// Materialize
var result = p.ToList();

Foo 类型及其属性也取自与方法 CreateExpression 相同的帖子。

希望这篇文章对您有所帮助。

【讨论】:

【参考方案4】:

如果你不需要表达式,如何:

products = products.Orderby(p1 => p1.Code).ThenBy(p2 => p2.Category.Description)

【讨论】:

【参考方案5】:

您好,您还可以创建一个扩展方法,例如它可以排序到任何深度,而不仅仅是子级

        public static IEnumerable<TSource> CustomOrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
    
        List<string> list=new List<string>();
        List<TSource> returnList=new List<TSource>();
        List<int> indexList = new List<int>();

        if (source == null)
            return null;
        if (source.Count() <= 0)
            return source;
        source.ToList().ForEach(sc=>list.Add(keySelector(sc).ToString())); //Extract the strings of property to be ordered

        list.Sort(); //sort the list of strings

        foreach (string l in list) // extract the list of indexes of source according to the order
        
            int i=0;
            //list.ForEach(l =>

                foreach (var s in source.ToList())
                
                    if (keySelector(s).ToString() == l)
                        break;
                    i++;
                
                indexList.Add(i);
        
        indexList.ForEach(i=>returnList.Add(source.ElementAt(i))); //rearrange the source according to the above extracted indexes
        return returnList;
    

public class Name

    public string FName  get; set; 
    public string LName  get; set; 

public class Category

    public Name Name  get; set; 

public class SortChild

    public void SortOn()
    
        List<Category> category = new List<Category>new Category()Name=new Name()FName="sahil",LName="chauhan",
            new Category()Name=new Name()FName="pankaj",LName="chauhan",
            new Category()Name=new Name()FName="harish",LName="thakur",
            new Category()Name=new Name()FName="deepak",LName="bakseth",
            new Category()Name=new Name()FName="manish",LName="dhamaka",
            new Category()Name=new Name()FName="arev",LName="raghaka"
        ;
        var a = category.CustomOrderBy(s => s.Name.FName);

    


它的自定义方法,现在它仅适用于字符串属性,但它可以使用泛型进行反应以适用于任何原始类型。我希望这会有所帮助。

【讨论】:

以上是关于基于子实体的属性构建 OrderBy Lambda 表达式的主要内容,如果未能解决你的问题,请参考以下文章

基于(包括)子实体返回父实体

使用 lambda 表达式在 Entity Framework 的实体上动态应用过滤器

如何在多个子实体和列表属性之间建立关系?

基于Dapper的开源Lambda扩展,且支持分库分表自动生成实体二

基于Dapper的开源Lambda扩展,且支持分库分表自动生成实体基础

Spring 数据规范 orderBy 子查询