用于优化 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