我正在尝试根据排序方向动态创建顺序
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表上创建一个order语句,如
_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();
上面的代码是工作的,但我需要改变顺序升序或降序基于Sortdirection从ColumnHeader点击。
我尝试在Queryable上使用OrderBySortDynamic扩展方法来动态获取顺序或有序降序
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);
}
我如何创建一个扩展方法来动态执行orderderby的子导航属性,其中a.t itemproducttypes .orderderby(b =祝辞b.ProductType.ProductTypeName)。我尝试用Ienumerable更新上面的扩展方法,但它不起作用。
首先要考虑的是自定义扩展方法不能在表达式树中使用。如这里
.OrderBy(a =>
a.ItemProductTypes.OrderBy(b => b.ProductType.ProductTypeName)
.Select(c => c.ProductType.ProductTypeName)
.FirstOrDefault()
)
你可以用自定义方法代替外部的OrderBy
,但不能用内部的-a =>
之后的所有东西都是表达式树的一部分,必须是众所周知的方法/属性,因为表达式树中的方法/属性通常不会被调用,而是由查询表达式转换器处理。
话虽如此,让我们看看我们能做些什么来处理你的问题。外部OrderBy
可通过自定义扩展方式替换。但你是过于复杂并不是真正的动态——它所做的是调用OrderBy
或OrderByDescending
传递准备提供参数。因此,它可以简化为(基本上与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 => 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