【问题标题】:How to create dynamic Orderby statement on child list object in ef core如何在 ef 核心中的子列表对象上创建动态 Orderby 语句
【发布时间】:2021-09-28 14:55:54
【问题描述】:

我正在尝试根据排序方向动态创建 orderby

public class Item
{   
    public Guid ItemId {get; set;}
    public string ItemName { get; set; }
    public List<ItemProductType> ItemProductTypes {get; set;}
}
public class ItemProductType
{
    public Guid ItemId {get; set;}
    public Guid ProductTypeId {get; set; }
    public Item Item { get; set; }
    public ProductType ProductType { get; set; }
}
public class ProductType
{   
    public Guid ProductTypeId {get; set;}
    public string ProductTypeName { get; set; }
    public List<ItemProductType> ItemProductTypes {get; set;}
}

我需要在 item 表上创建一个 orderby 语句,例如

_context.Items.Include(a => a.ItemProductTypes)
    .ThenInclude(a => a.ProductType)        
    .OrderBy(a => 
        a.ItemProductTypes.OrderBy(b => b.ProductType.ProductTypeName)
            .Select(c => c.ProductType.ProductTypeName)
            .FirstOrDefault()
    ).ToListAsync();

以上代码可以正常工作,但我需要根据来自 ColumnHeader 单击的 Sortdirection 升序或降序来更改顺序。

我尝试在 Queryable 上使用 OrderBySortDynamic 扩展方法来动态获取 orderby 或 orderbydescending

public static IQueryable<t> OrderBySortDynamic<t, TKey>(this IQueryable<t> query, Expression<Func<t, TKey>> keySelector, bool sortDescending) 
{ 
    string command = "OrderBy"; 
    if (sortDescending) 
    { 
        command = "OrderByDescending"; 
    } 

    Expression resultExpression = Expression.Call( 
        typeof(Queryable), 
        command, 
        new[] { typeof(t), keySelector.ReturnType },
        query.Expression, 
        Expression.Quote(keySelector));
    return query.Provider.CreateQuery<t>(resultExpression); 
} 

如何创建扩展方法以在子导航属性上动态执行 orderby,其中 a.ItemProductTypes.OrderBy(b=>b.ProductType.ProductTypeName)。我尝试使用 Ienumerable 更新上述扩展方法,但它不起作用。

【问题讨论】:

  • 在没有动态实现的情况下编写工作查询。目标很难理解。
  • 我需要根据navigationproperties(itemtype) child(type.typename)对父实体(item)进行排序,并且需要动态地进行排序
  • 显示工作的非动态查询。
  • _context.Items.Include(a => a.ItemProductTypes) .ThenInclude(a => a.ProductType) .OrderBy(a => a.ItemProductTypes.OrderBy(b => b.ProductType. ProductTypeName) .Select(c => c.ProductType.ProductTypeName) .FirstOrDefault() ).ToListAsync();这个 linq 正在工作
  • 这个查询甚至不能编译,也不会工作。使用有效的非动态查询更新问题并显示哪个部分应该是动态的。

标签: c# linq lambda entity-framework-core expression-trees


【解决方案1】:

首先要考虑的是自定义扩展方法不能在表达式树中使用。例如这里

.OrderBy(a => 
    a.ItemProductTypes.OrderBy(b => b.ProductType.ProductTypeName)
        .Select(c => c.ProductType.ProductTypeName)
        .FirstOrDefault()
)

您可以使用自定义方法代替外部OrderBy,但不能用于内部-a =&gt; 之后的所有内容都是表达式树的一部分,并且必须是众所周知的方法/属性,因为方法/属性内部表达式树一般不会被调用,而是由查询表达式翻译器处理。

话虽如此,让我们看看我们能解决您的问题。外部OrderBy 可以替换为自定义扩展方法。但是您提供的那个过于复杂并且不是真正动态的 - 它所做的只是调用 OrderByOrderByDescending 传递准备好的提供的参数。所以它可以简化为(基本上与OrderBy相同的签名加上bool参数):

public static IOrderedQueryable<T> OrderBy<T, TKey>(
    this IQueryable<T> source,
    Expression<Func<T, TKey>> keySelector,
    bool descending
) => descending ? source.OrderByDescending(keySelector) : source.OrderBy(keySelector);

如果选择器使用单个值(通过引用导航属性直接或间接),这将起作用。为了处理内部OrderBy,您需要另一个自定义方法接收必要的部分作为参数。

在这个表达式中

a => a.ItemProductTypes
    .OrderBy(b => b.ProductType.ProductTypeName)
    .Select(c => c.ProductType.ProductTypeName)
    .FirstOrDefault()
)

可定制的部分是a.ItemProductTypes 用于从外部“对象”中选择内部枚举,b =&gt; b.ProductType.ProductTypeName 用于选择内部键进行排序。

所以第二个自定义方法是这样的:

public static IOrderedQueryable<TOuter> OrderBy<TOuter, TInner, TKey>(
    this IQueryable<TOuter> source,
    Expression<Func<TOuter, IEnumerable<TInner>>> innerSelector,
    Expression<Func<TInner, TKey>> innerKeySelector,
    bool descending
)
{
    var innerOrderBy = Expression.Call(
        typeof(Enumerable),
        descending ? nameof(Enumerable.OrderByDescending) : nameof(Enumerable.OrderBy),
        new[] { typeof(TInner), typeof(TKey) },
        innerSelector.Body,
        innerKeySelector
    );

    var innerSelect = Expression.Call(
        typeof(Enumerable),
        nameof(Enumerable.Select),
        new[] { typeof(TInner), typeof(TKey) },
        innerOrderBy,
        innerKeySelector
    );

    var innerFirstOrDefault = Expression.Call(
        typeof(Enumerable),
        nameof(Enumerable.FirstOrDefault),
        new[] { typeof(TKey) },
        innerSelect
    );

    var outerKeySelector = Expression.Lambda<Func<TOuter, TKey>>(
        innerFirstOrDefault,
        innerSelector.Parameters
    );

    return source.OrderBy(outerKeySelector, descending);
}

这里的实现必须使用Expression 类方法来组成一个键选择器,以传递给之前的自定义扩展方法。您只需要知道这些方法是静态的、包含类、方法名称、泛型类型实参和参数。并且它们的调用顺序与 C# 扩展方法语法糖所提供的顺序相反,即

OrderBy(...).Select(...).FirstOrDefault()

其实是

FirstOrDefault(Select(OrderBy(...), ...))

现在您可以在查询中使用第二种扩展方法:

var query = _context.Items
    .Include(a => a.ItemProductTypes)
        .ThenInclude(a => a.ProductType)
    .OrderBy(
        a => a.ItemProductTypes,
        b => b.ProductType.ProductTypeName,
        descending: true // pass your criteria here
    );

另一种可能的解决方案是编写升序查询(如示例中所示),然后使用自定义ExpressionVisitor 将所有OrderBy 调用替换为OrderByDescending - 类似于string.Replace,但使用表达式。

以下是上述的示例实现:

public static IQueryable<T> SwitchOrderBy<T>(
    this IQueryable<T> source,
    bool descending
)
{
    if (!descending) return source;
    var expression = new OrderBySwitcher().Visit(source.Expression);
    if (source.Expression == expression) return source;
    return source.Provider.CreateQuery<T>(expression);
}

class OrderBySwitcher : ExpressionVisitor
{
    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        if ((node.Method.DeclaringType == typeof(Enumerable)
            || node.Method.DeclaringType == typeof(Queryable))
            && node.Method.Name == nameof(Enumerable.OrderBy))
        {
            var args = new Expression[node.Arguments.Count];
            for (int i = 0; i < args.Length; i++)
                args[i] = Visit(node.Arguments[i]);
            return Expression.Call(
                node.Method.DeclaringType,
                nameof(Enumerable.OrderByDescending),
                node.Method.GetGenericArguments(),
                args
            );
        }
        return base.VisitMethodCall(node);
    }
}

和示例用法


var query = _context.Items
    .Include(a => a.ItemProductTypes)
        .ThenInclude(a => a.ProductType)
    .OrderBy(a => a.ItemProductTypes
        .OrderBy(b => b.ProductType.ProductTypeName)
        .Select(c => c.ProductType.ProductTypeName)
        .FirstOrDefault()
    )
    // everything down to here is same as in the original query
    .SwitchOrderBy(descending: true); // pass your criteria here

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-08-27
    • 2021-12-09
    • 2020-11-14
    • 2018-10-30
    • 2017-06-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多