繁体   English   中英

使用表达 <Func<TSource, TKey> &gt;使用IQueryable

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM