如何创建IEuumerable扩展方法的IQueryable版本?

时间:2017-04-15 07:27:03

标签: c# linq ienumerable iqueryable

最近,我对IEnumerableIQueryable接口之间的差异越来越感兴趣,因此,我发现IQueryableIEnumerable以上的许多情况下都非常有效,虽然我还没有完全掌握它们。既没有将表达式树与IQueryable结合使用,但我希望提高我创建的扩展方法的性能:

    public static IEnumerable<TSource> In<TSource, TMember>(this IEnumerable<TSource> source,
        Func<TSource, TMember> identifier, params TMember[] values) =>
     source.Where(m => values.Contains(identifier(m)));

正如我到目前为止所理解的那样,我想做IQueryable版本,所以,从服务器获取所有记录并在内存中过滤它们,我想从服务器只获取过滤后的记录,例如在调用此SELECT * FROM Books WHERE Id IN (1, 2, 3)时在服务器books.In(x => x.Id, 1, 2, 3)上运行此查询,这就是我提出的:

 public static IQueryable<TSource> In<TSource, TMember>(this IQueryable<TSource> source,
      Expression<Func<TSource, TMember>> identifier, params TMember[] values) =>
    source.Where(m => values.Contains(identifier.Compile()(m)));

老实说,经过一些产生错误的尝试后,我想出了这段代码,并且它有效,但我不确定这是否是我如何制作IQueryable扩展方法的?

修改

正如xanatos的回答所暗示的那样,我在VS中进行了测试,它也有效, 但我有一些问题要了解发生了什么:

  1. 你怎么知道它的工作正常,如果他们都给出IQueryable结果,那么我的尝试与你的尝试有什么区别(当然我知道我的不正确!)?
  2. 如果一个是正确的,那么我可以测试它以查看我自己吗? (你提到你用AsQueryable测试了它,怎么样?)
  3. 我注意到代码的结果是:

    {System.Collections.Generic.List'1[NewNS.Book].Where(x => value(System.Int32[]).Contains(x.Id))}

    我的地方是:  {System.Collections.Generic.List'1[NewNS.Book].Where(m => value(NewNS.Linqs+<>c__DisplayClass0_0'2[NewNS.Book,System.Int32]).values.Contains(Invoke(value(NewNS.Linqs+<>c__DisplayClass0_0'2[NewNS.Book,System.Int32]).identifier.Compile(), m)))}

    1. 他们的意思是什么,我能从这些差异中得知什么?
    2. 如果您回答这些问题以帮助我理解IQueryable如何运作,我将非常感激。

1 个答案:

答案 0 :(得分:4)

有点复杂。我已使用AsQueryable()对其进行了测试。我还没有使用Entity Framework对其进行测试,但它应该可行。代码被大量评论。

// The Enumerable.Contains method
private static readonly MethodInfo Contains = (from x in typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
                                                where x.Name == nameof(Enumerable.Contains)
                                                let args = x.GetGenericArguments()
                                                where args.Length == 1
                                                let pars = x.GetParameters()
                                                where pars.Length == 2 &&
                                                    pars[0].ParameterType == typeof(IEnumerable<>).MakeGenericType(args[0]) &&
                                                    pars[1].ParameterType == args[0]
                                                select x).Single();

public static IQueryable<TSource> In<TSource, TMember>(
    this IQueryable<TSource> source,
    Expression<Func<TSource, TMember>> identifier, params TMember[] values)
{
    // Some argument checks
    if (source == null)
    {
        throw new NullReferenceException(nameof(source));
    }

    if (identifier == null)
    {
        throw new NullReferenceException(nameof(identifier));
    }

    if (values == null)
    {
        throw new NullReferenceException(nameof(values));
    }

    // We only accept expressions of type x => x.Something
    // member wil be the x.Something
    var member = identifier.Body as MemberExpression;

    if (member == null)
    {
        throw new ArgumentException(nameof(identifier));
    }

    // Enumerable.Contains<TMember>
    var contains = Contains.MakeGenericMethod(typeof(TMember));

    // Enumerable.Contains<TMember>(values, x.Something)
    var call = Expression.Call(contains, Expression.Constant(values), member);

    // x => Enumerable.Contains<TMember>(values, x.Something)
    var lambda = Expression.Lambda<Func<TSource, bool>>(call, identifier.Parameters);

    return source.Where(lambda);
}

不会缓存MethodInfo的较短版本(请参阅评论):

public static IQueryable<TSource> In<TSource, TMember>(
    this IQueryable<TSource> source,
    Expression<Func<TSource, TMember>> identifier, params TMember[] values)
{
    // Some argument checks
    if (source == null)
    {
        throw new NullReferenceException(nameof(source));
    }

    if (identifier == null)
    {
        throw new NullReferenceException(nameof(identifier));
    }

    if (values == null)
    {
        throw new NullReferenceException(nameof(values));
    }

    // We only accept expressions of type x => x.Something
    // member wil be the x.Something
    var member = identifier.Body as MemberExpression;

    if (member == null)
    {
        throw new ArgumentException(nameof(identifier));
    }

    // Enumerable.Contains<TMember>(values, x.Something)
    var call = Expression.Call(typeof(Enumerable), nameof(Enumerable.Contains), new[] { typeof(TMember) }, Expression.Constant(values), member);

    // x => Enumerable.Contains<TMember>(values, x.Something)
    var lambda = Expression.Lambda<Func<TSource, bool>>(call, identifier.Parameters);

    return source.Where(lambda);
}

只是为了好玩,简单的&#34;如何在不使用反射的情况下找到Enumerable.Contains<T>(与第一个样本一起使用):

private static readonly MethodInfo Contains = ((MethodCallExpression)((Expression<Func<bool>>)(() => new object[0].Contains(new object()))).Body).Method.GetGenericMethodDefinition();
  
      
  1. 你怎么知道它是正确的,如果他们都给出了可以产生的结果,那么我的尝试与你的尝试有什么区别(当然我知道我的不正确!)?
  2.   

因为我跑了它:-)你们不能工作,因为中间有一个.Compile()。我知道实体框架库和LINQ-to-SQL库不支持.Compile()(也不是.Invoke()),所以我知道你的工作没有。< / p>

  
      
  1. 如果一个是正确的,那么我可以测试它以查看我自己吗? (你提到你用AsQueryable测试过它,怎么样?)
  2.   

有一种&#34;气味&#34;测试,但可能甚至你的通过。

new[] { new { ID = 1 }, new { ID = 2 } }.AsQueryable().In(x => x.ID, 2, 4).ToArray()

唯一真正的考验是将其用于实体框架。

  
      
  1. 他们的意思是什么,我能从这些差异中得知什么?
  2.   

这是一个&#34;文字表示&#34;表达式树。只要看一下,我就能看到.Invoke()的{​​{1}}。你必须记住.Compile()&#34;通常&#34; (因此不包括本地执行的IQueryable<>)您的查询将被翻译为&#34;语言&#34;那个&#34;服务器&#34; (通常是SQL Server)可以理解,远程执行。这个&#34;翻译&#34;是非常有限的,只知道一些方法(最常见的方法)。因此,如果您尝试使用不是最常用方法之一的任何方法,那么您的查询将在执行时中断。