【问题标题】:LINQ extension for optimising Skip() Take()用于优化 Skip() Take() 的 LINQ 扩展
【发布时间】:2020-12-25 02:50:07
【问题描述】:

我一直在应用此博客中的优化: 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( ??? ));
     }
}

【问题讨论】:

  • 你为什么不直接做context.Cars.OrderBy(y =&gt; y.Id).Skip(50000).Take(1000).ToList()呢?确实没有必要将其作为子查询。
  • 子查询强制进行优化以避免在将大值用于 Skip 时性能不佳。我链接的博客解释了这一点。使用较大的 Skip 值查询数据库的性能会越来越差。
  • 如果你传入一个Expression&lt;Func&lt;TEnity, TKey&gt;&gt; getKey 以在OrderBySelect 中使用,你试过Contains(getKey(x))
  • 我更新了关于提供代码的问题。 selector(x) 对我不起作用,至少在我拥有的 C# 版本中,Linq 表达式似乎不能以这种方式调用。
  • 您提出的扩展方法没有添加任何有用的东西,实际上通过将 skip 和 take 参数隐藏在一对 int 参数后面来降低代码清晰度。只需使用博客中的原始代码即可。

标签: c# generics linq-to-entities


【解决方案1】:

我想你已经走进了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

【讨论】:

  • 这是一个极好的解决方案!在查看与 Expression 构建和逐步执行代码相关的文档时,我也学到了很多东西。根据我的基准,这种方法的执行时间也很出色。非常感谢!
猜你喜欢
  • 2012-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-09
相关资源
最近更新 更多