如何解决LINQ扩展,用于优化SkipTake
我一直在通过此博客进行优化: 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,int paramCount) =>
t.GetMethods().Where(mi => mi.Name == methodName && mi.IsGenericMethodDefinition && mi.GetParameters().Length == paramCount).Single();
public static MethodInfo MakeGenericMethod(this Type t,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
。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。