简体   繁体   English

如何通过泛型字段动态过滤IQueryable

[英]How to filter IQueryable dynamically by generic field

I'm trying to make a IDictionary<TKey, List<TSource>> class where I can decide the key by a func. 我正在尝试创建一个IDictionary <TKey,List <TSource >>类,我可以通过函数来​​决定密钥。 I have managed to build the class with arrays like this (functions are simplified for examples sake): 我已经设法用这样的数组构建类(为简单起见,函数是简化的):

private List<TSource> Filter(TKey key, Func<TSource, TKey> keySelector)
{
    IQueryable<TSource> source = GetSource(); // It's there
    IQueryable<TSource> filtered = source.Where(
        x => keySelector.Invoke(x).Equals(key)
        );
    return filtered.ToList();
}

But this works only with arrays and such, not with linq-to-sql. 但这只适用于数组等,而不适用于linq-to-sql。 I understand this can be done with expressions, but thats mostly beyond me. 我明白这可以通过表达来完成,但那些大多数都超出了我的意思。 I have googled and come up with the following functions: 我用Google搜索并提出以下功能:

private List<TSource> Filter(TKey key, Expression<Func<TSource, TKey>> keySelector)
{
    IQueryable<TSource> source = GetSource();
    Func<TSource, bool> compiledKeyFilter = GetFilter(keySelector);
    IEnumerable<TSource> filtered = source.Where(compiledKeyFilter);
    return filtered.ToList();
}
private Func<TSource, bool> GetFilter(Expression<Func<TSource, TKey>> expr)
{
    if (this.filter == null)
    {
        var invokedExpr = Expression.Invoke(expr, expr.Parameters.Cast<Expression>());
        var lamda = Expression.Lambda<Func<TSource, bool>>(
            Expression.Call(expr.Body, typeof(TKey).GetMethod("Equals", new[] { typeof(TKey) }), invokedExpr),
            expr.Parameters
            );
        this.filter = lamda.Compile();
    }

    return this.filter;
}

This currently returns all rows in the source. 这将返回源中的所有行。 The expression should be reusable, not just for one-time execution. 表达式应该是可重用的,而不仅仅是一次性执行。 I found the following from SO ( Dynamically generated lookup key for IQueryable ), what I'm doing is quie similar. 我从SO( 动态生成的IQueryable查找键 )中找到了以下内容,我正在做的是类似的。 It works, but I'm unable to combine it with the compiled approach: 它有效,但我无法将其与编译方法结合起来:

private Expression<Func<TSource, bool>> MakeFilterExpression(TKey key)
{
    var param = Expression.Parameter(typeof(TSource));
    return Expression.Lambda<Func<TSource, bool>>(
            Expression.Equal(
                Expression.Invoke(keySelector, param),
                Expression.Constant(key)
            ),
        param
        );
}

So I'm trying to come up with a class that I could use like this: 所以我试图想出一个我可以这样使用的类:

// Inside MyCache there would be something close to this:
class MyCache
{
    private Func<TSource, bool> filter;
    public MyCache(Expression<Func<TSource, TKey>> func)
    {
        this.filter = MakeFilter(func);
    }
    private Func<TSource, bool> MakeFilter(Expression<Func<TSource, TKey>> func)
    {
        // magic
    }
    public List<TSource> GetByKey(TKey key)
    {
        return GetSource().Where(this.filter(key)).ToList();
    }
}

// This is my class where I give my func to determine the key in ctor.
var cache = new MyCache<MySource>(x => x.myField);
var list1 = cache.GetByKey(3); // Now I have list to iterate.
var list2 = cache.GetByKey(4); // Here's another list.

Is it even possible to compile that into a reusable function? 甚至可以将其编译成可重用的函数吗? Help?! 救命?!

If I understand your question correctly, then you are trying to come up with a version of the Filter method that works with LINQ-to-SQL queries given an Expression<Func<TSource, TKey>> key selector. 如果我正确地理解了您的问题,那么您正在尝试使用Expression<Func<TSource, TKey>>键选择器来提供一个适用于LINQ-to-SQL查询的Filter方法。

You can use the LINQKit library to help you with that. 您可以使用LINQKit库来帮助您。 It allows you to invoke one expression from another one and then "expand" the result expression. 它允许您从另一个表达式调用一个表达式,然后“展开”结果表达式。

Here is how you would code your Filter method with the help of LINQKit: 以下是在LINQKit的帮助下编写Filter方法的方法:

private static List<TSource> Filter<TSource,TKey>(
    TKey key,
    Expression<Func<TSource, TKey>> keySelector)
{
    IQueryable<TSource> source = GetSource<TSource>(); // It's there

    Expression<Func<TSource, bool>> predicate = x => keySelector.Invoke(x).Equals(key);

    //LINQ-to-SQL cannot translate `Invoke` into SQL. However, after invoking this line
    //LINQKit would flatten the expression so that it contains something like
    //x => x.Age.Equals(5) (for a keySelector of value x => x.Age and key of value 5)
    predicate = predicate.Expand();

    IQueryable<TSource> filtered = source.Where(predicate);

    return filtered.ToList();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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