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

C# 用于优化Skip()Take()的LINQ扩展,c#,generics,linq-to-entities,C#,Generics,Linq To Entities,我一直在应用这个博客中的优化: 我想把它转换成一个通用的LINQ扩展,但是,我不知道如何在Contains中引用x.Id。它看起来不像是可以作为表达式传递的东西,但是它没有特别引用x的实例 此处的更新正在进行代码: public static class LinqExtension { public static IQueryable<TSource> SkipTake<TSource, TKey>(this IQueryable<TSource>

我一直在应用这个博客中的优化:

我想把它转换成一个通用的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( ??? ));
     }
}

我想你已经踏入了表达树的世界。你的问题启发我创建了一些新的助手,以便在某些情况下更轻松地执行此操作

以下是一些有助于表达式树操作和构建的扩展方法:

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);
}
要创建该方法,需要手动为Where方法构建lambda。我没有手动构建qBase表达式树,而是让编译器为我这样做,然后使用生成的表达式。我的Call helper使创建与可查询扩展方法相对应的扩展方法变得很容易,但是可以在表达式树上工作。当然,您可以直接对任何需要的可查询方法使用Call,但是常量helper会很有用


构建whereLambda后,只需将其传递到原始源代码的Where。

为什么不改为执行context.Cars.OrderByy=>y.Id.Skip50000.Take1000.ToList?实际上不需要将其作为子查询。子查询强制进行优化,以避免在使用大值进行跳过时出现性能不佳的情况。我链接的博客解释了这一点。查询具有较大跳过值的数据库时,性能会越来越差。如果传入表达式getKey以在OrderBy中使用并选择,是否尝试过ContainesGetKeyxi更新有关提供代码的问题。selectorx对我不起作用,Linq表达式似乎不是这样调用的,至少在我的C版本中不是这样。您提出的扩展方法没有添加任何有用的内容,实际上通过在一对int参数后面隐藏skip和take参数来降低代码的清晰度。只需使用来自博客的原始代码即可。这是一个极好的解决方案!通过查看与表达式构建和单步执行代码相关的文档,我也学到了很多东西。根据我的基准测试,这种方法的执行时间也非常好。非常感谢你!
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);
}
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);
    }
}