[英]What is TKey in System.Linq.Expressions.Expression<Func<TSource,TKey>>?
[英]Using Expression<Func<TSource, TKey>> with IQueryable
我试图编写一个函数,该函数使用键选择器和SQL或内存中的集合(如果集合大于特定阈值)来过滤IQueryable
数据源。
这就是我现在所拥有的。
public static IEnumerable<TSource> SafeFilter<TSource, TKey>(this IQueryable<TSource> source, Func<TSource, TKey> keySelector, HashSet<TKey> filterSet, int threshold = 500)
{
if (filterSet.Count > threshold)
return source.AsEnumerable().Where(x => filterSet.Contains(keySelector(x))); //In memory
return source.Where(x => filterSet.AsEnumerable().Contains(keySelector(x))); //In SQL
}
它可以针对“内存中”情况进行编译和工作,但不适用于Sql Server情况。 我得到:
方法'System.Object DynamicInvoke(System.Object [])'不支持SQL转换
我怀疑我需要将其更改为Expression<Func<TSource, TKey>>
但不确定如何使用它。 任何帮助表示赞赏。
您在这里所做的就是将一个功能组合在另一个功能中。 对于代表,这很容易,因为您可以调用一个然后将结果作为参数传递给另一个。 编写表达式要稍微复杂一些; 您需要用该参数组成的表达式替换该参数使用的所有实例。 幸运的是,您可以将此逻辑提取到其自己的方法中:
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
这使用以下方法将一个表达式的所有实例替换为另一个:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
现在您可以编写:
public static IEnumerable<TSource> SafeFilter<TSource, TKey>
(this IQueryable<TSource> source,
Expression<Func<TSource, TKey>> keySelector,
HashSet<TKey> filterSet,
int threshold = 500)
{
if (filterSet.Count > threshold)
{
var selector = keySelector.Compile();
return source.AsEnumerable()
.Where(x => filterSet.Contains(selector(x))); //In memory
}
return source.Where(keySelector.Compose(
key => filterSet.AsEnumerable().Contains(key))); //In SQL
}
顺便说一句,如果您的过滤器集足够大,除了将整个集合带入内存之外,您还有另一个选择。 您可以做的是将过滤器集合分成多个批次,从数据库中获取每个批次,然后合并结果。 这可以绕开IN
子句中最大项目数的限制,同时仍可以在数据库端完成工作。 根据数据的具体情况,它可能会更好也可能不会更好,但是要考虑的另一种选择是:
public static IEnumerable<TSource> SafeFilter<TSource, TKey>
(this IQueryable<TSource> source,
Expression<Func<TSource, TKey>> keySelector,
HashSet<TKey> filterSet,
int batchSize = 500)
{
return filterSet.Batch(batchSize)
.SelectMany(batch => source.Where(keySelector.Compose(
key => batch.Contains(key))));
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.