C# 用于优化Skip()Take()的LINQ扩展
我一直在应用这个博客中的优化: 我想把它转换成一个通用的LINQ扩展,但是,我不知道如何在Contains中引用x.Id。它看起来不像是可以作为表达式传递的东西,但是它没有特别引用x的实例 此处的更新正在进行代码: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>
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);
}
}