用于优化 Skip() Take() 的 LINQ 扩展
LINQ extension for optimising Skip() Take()
我一直在应用这个博客的优化:
RimDev.io
context.Cars
.Where(x => context.Cars
.OrderBy(y => y.Id)
.Select(y => y.Id)
.Skip(50000)
.Take(1000)
.Contains(x.Id)).ToList();
我想将其转换为通用的 LINQ 扩展,但是,我不确定如何在 Contains 中引用 x.Id。它看起来不像是可以作为表达式传递的东西,但它并没有具体引用 x 的实例。
这里更新进行中代码:
public static class LinqExtension {
public static IQueryable<TSource> SkipTake<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> selector, int skip, int take)
where TSource: class {
return source.Where(x=> source.OrderBy<TSource,TKey(selector)
.Select(selector)
.Skip(skip)
.Take(take)
.Contains( ??? ));
}
}
我想你已经步入Expression
造树的世界了。你的问题启发了我创建一些新的助手来在某些情况下使这更容易。
以下是一些有助于 Expression
树操作和构建的扩展方法:
public static class ExpressionExt {
public static Expression Contains(this Expression src, Expression item) => src.Call("Contains", item);
public static Expression Call(this Expression p1, string methodName, params Expression[] px) {
var tKey = p1.Type.GetGenericArguments()[0];
var containsMI = typeof(Queryable).MakeGenericMethod(methodName, px.Length + 1, tKey);
return Expression.Call(null, containsMI, px.Prepend(p1));
}
/// <summary>
/// Replaces an Expression (reference Equals) with another Expression
/// </summary>
/// <param name="orig">The original Expression.</param>
/// <param name="from">The from Expression.</param>
/// <param name="to">The to Expression.</param>
/// <returns>Expression with all occurrences of from replaced with to</returns>
public static T Replace<T>(this T orig, Expression from, Expression to) where T : Expression => (T)new ReplaceVisitor(from, to).Visit(orig);
/// <summary>
/// ExpressionVisitor to replace an Expression (that is Equals) with another Expression.
/// </summary>
public class ReplaceVisitor : ExpressionVisitor {
readonly Expression from;
readonly Expression to;
public ReplaceVisitor(Expression from, Expression to) {
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node) => node == from ? to : base.Visit(node);
}
}
public static class TypeExt {
public static MethodInfo GetGenericMethod(this Type t, string methodName, int paramCount) =>
t.GetMethods().Where(mi => mi.Name == methodName && mi.IsGenericMethodDefinition && mi.GetParameters().Length == paramCount).Single();
public static MethodInfo MakeGenericMethod(this Type t, string methodName, int paramCount, params Type[] genericParameters) =>
t.GetGenericMethod(methodName, paramCount).MakeGenericMethod(genericParameters);
}
现在您可以创建 SkipTake
方法 - 我假设 Contains
成员选择器参数始终与 selector
参数相同。
public static class LinqExtension {
public static IQueryable<TSource> SkipTake<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> selector, int skip, int take)
where TSource : class {
// x
var xParm = Expression.Parameter(typeof(TSource), "x");
var qBase = source.OrderBy(selector)
.Select(selector)
.Skip(skip)
.Take(take);
// selector(x)
var outerSelector = selector.Body.Replace(selector.Parameters[0], xParm);
// source.OrderBy(selector).Select(selector).Skip(skip).Take(take).Contains(selector(x))
var whereBody = qBase.Expression.Contains(outerSelector);
// x => whereBody
var whereLambda = Expression.Lambda<Func<TSource,bool>>(whereBody, xParm);
return source.Where(whereLambda);
}
}
要创建方法,您需要手动为 Where
方法构建 lambda。我没有手动构建 qBase
Expression
树,而是让编译器为我做这些,然后使用生成的 Expression
。我的 Call
帮助程序可以轻松创建与 Queryable
扩展方法相对应但适用于 Expression
树的扩展方法,当然,您可以直接使用 Call
您需要的任何 Queryable
方法(但是 Constant
帮助器会很有用)。
构建 whereLambda
后,只需将其传递给 Where
即可获得原始 source
。
我一直在应用这个博客的优化: RimDev.io
context.Cars
.Where(x => context.Cars
.OrderBy(y => y.Id)
.Select(y => y.Id)
.Skip(50000)
.Take(1000)
.Contains(x.Id)).ToList();
我想将其转换为通用的 LINQ 扩展,但是,我不确定如何在 Contains 中引用 x.Id。它看起来不像是可以作为表达式传递的东西,但它并没有具体引用 x 的实例。
这里更新进行中代码:
public static class LinqExtension {
public static IQueryable<TSource> SkipTake<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> selector, int skip, int take)
where TSource: class {
return source.Where(x=> source.OrderBy<TSource,TKey(selector)
.Select(selector)
.Skip(skip)
.Take(take)
.Contains( ??? ));
}
}
我想你已经步入Expression
造树的世界了。你的问题启发了我创建一些新的助手来在某些情况下使这更容易。
以下是一些有助于 Expression
树操作和构建的扩展方法:
public static class ExpressionExt {
public static Expression Contains(this Expression src, Expression item) => src.Call("Contains", item);
public static Expression Call(this Expression p1, string methodName, params Expression[] px) {
var tKey = p1.Type.GetGenericArguments()[0];
var containsMI = typeof(Queryable).MakeGenericMethod(methodName, px.Length + 1, tKey);
return Expression.Call(null, containsMI, px.Prepend(p1));
}
/// <summary>
/// Replaces an Expression (reference Equals) with another Expression
/// </summary>
/// <param name="orig">The original Expression.</param>
/// <param name="from">The from Expression.</param>
/// <param name="to">The to Expression.</param>
/// <returns>Expression with all occurrences of from replaced with to</returns>
public static T Replace<T>(this T orig, Expression from, Expression to) where T : Expression => (T)new ReplaceVisitor(from, to).Visit(orig);
/// <summary>
/// ExpressionVisitor to replace an Expression (that is Equals) with another Expression.
/// </summary>
public class ReplaceVisitor : ExpressionVisitor {
readonly Expression from;
readonly Expression to;
public ReplaceVisitor(Expression from, Expression to) {
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node) => node == from ? to : base.Visit(node);
}
}
public static class TypeExt {
public static MethodInfo GetGenericMethod(this Type t, string methodName, int paramCount) =>
t.GetMethods().Where(mi => mi.Name == methodName && mi.IsGenericMethodDefinition && mi.GetParameters().Length == paramCount).Single();
public static MethodInfo MakeGenericMethod(this Type t, string methodName, int paramCount, params Type[] genericParameters) =>
t.GetGenericMethod(methodName, paramCount).MakeGenericMethod(genericParameters);
}
现在您可以创建 SkipTake
方法 - 我假设 Contains
成员选择器参数始终与 selector
参数相同。
public static class LinqExtension {
public static IQueryable<TSource> SkipTake<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> selector, int skip, int take)
where TSource : class {
// x
var xParm = Expression.Parameter(typeof(TSource), "x");
var qBase = source.OrderBy(selector)
.Select(selector)
.Skip(skip)
.Take(take);
// selector(x)
var outerSelector = selector.Body.Replace(selector.Parameters[0], xParm);
// source.OrderBy(selector).Select(selector).Skip(skip).Take(take).Contains(selector(x))
var whereBody = qBase.Expression.Contains(outerSelector);
// x => whereBody
var whereLambda = Expression.Lambda<Func<TSource,bool>>(whereBody, xParm);
return source.Where(whereLambda);
}
}
要创建方法,您需要手动为 Where
方法构建 lambda。我没有手动构建 qBase
Expression
树,而是让编译器为我做这些,然后使用生成的 Expression
。我的 Call
帮助程序可以轻松创建与 Queryable
扩展方法相对应但适用于 Expression
树的扩展方法,当然,您可以直接使用 Call
您需要的任何 Queryable
方法(但是 Constant
帮助器会很有用)。
构建 whereLambda
后,只需将其传递给 Where
即可获得原始 source
。