简体   繁体   English

用于优化 Skip() Take() 的 LINQ 扩展

[英]LINQ extension for optimising Skip() Take()

I have been applying the optimization from this blog: RimDev.io我一直在应用这个博客的优化: RimDev.io

context.Cars
.Where(x => context.Cars
.OrderBy(y => y.Id)
.Select(y => y.Id)
.Skip(50000)
.Take(1000)
.Contains(x.Id)).ToList();

I want to convert this into a general LINQ extension, however, I am not sure how to refer to x.Id in Contains.我想将其转换为通用 LINQ 扩展,但是,我不确定如何在包含中引用 x.Id。 It does not seem like something that you could pass as an expression but it does not then specifically reference an instance of x.它似乎不能作为表达式传递,但它不会专门引用 x 的实例。

Update here goes in progress code:此处更新正在进行中的代码:

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( ??? ));
     }
}

I think you have stepped into the world of Expression tree building.我想你已经步入了Expression树构建的世界。 Your question inspired me to create some new helpers to make this easier in certain cases.你的问题激励我创建一些新的助手,以便在某些情况下更容易。

Here are some extension methods that help with Expression tree manipulation and building:以下是一些有助于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);
}

Now you can create your SkipTake method - I am assuming the Contains member selector parameter is always the same member as the selector parameter.现在,你可以创建你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);
    }
}

To create the method, you need to build the lambda for the Where method manually.要创建该方法,您需要手动为Where方法构建 lambda。 Rather than build the qBase Expression tree manually, I let the compiler do that for me, then use the resulting Expression .我没有手动构建qBase Expression树,而是让编译器为我做这件事,然后使用生成的Expression My Call helper makes it easy to create extension methods that correspond to the Queryable extension methods but work on Expression trees though, of course, you could just use Call directly for any Queryable methods you need (but then a Constant helper would be useful). My Call helper 可以轻松创建与Queryable扩展方法相对应的扩展方法,但可以在Expression树上工作,当然,您可以直接将Call用于您需要的任何Queryable方法(但是Constant helper 会很有用)。

Once you've built the whereLambda , you just pass it to Where for the original source .构建whereLambda ,只需将其传递给Where获取原始source

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM