我试图编写一个函数,使用键选择器过滤IQueryable
数据源,如果集合大于特定阈值,则使用SQL或内存中的集合。
这就是我现在所拥有的。
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>>
,但不确定如何使用它。任何帮助表示赞赏。
答案 0 :(得分:2)
你在这里做的是在另一个中组成一个功能。对于委托,这很容易,因为您可以调用一个,然后将结果作为参数传递给另一个。构成表达式稍微复杂一些;您需要将该参数的所有使用实例替换为它正在编写的表达式。幸运的是,您可以将此逻辑提取到自己的方法中:
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))));
}