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



最近,我对IEnumerableIQueryable接口之间的差异更感兴趣,因此,我发现IQueryable在许多情况下可能非常有效IEnumerable,尽管我仍然没有完全掌握它们。 既没有将表达式树与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测试了它,如何?

我注意到您的代码的结果是以下类型:

{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. 它们是什么意思,我能从这些差异中分辨出什么?

如果您回答这些问题以帮助我了解IQueryable的工作原理,我将不胜感激。

有点复杂。我已经用AsQueryable()测试过了它。我还没有用实体框架测试过它,但它应该可以工作。代码被大量注释。

// 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);
}

只是为了好玩,在不使用反射的情况下找到Enumerable.Contains<T>的"简单"方法(用于第一个样本):

private static readonly MethodInfo Contains = ((MethodCallExpression)((Expression<Func<bool>>)(() => new object[0].Contains(new object()))).Body).Method.GetGenericMethodDefinition();
  1. 你怎么知道它工作正常,如果它们都给出智商结果,我的尝试和你的尝试有什么区别(当然我知道我的是不正确的!

因为我运行了它:-)你的不能工作,因为中间有一个.Compile()。我知道实体框架库和 LINQ-to-SQL 库不支持.Compile()(也不支持.Invoke()),所以我知道你的不起作用。

  1. 我可以测试一下它以查看我自己是否正确而另一个不正确吗?(你提到你用AsQueryable测试了它,如何?

有一个"气味"测试,但可能连你的测试都通过了。

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

唯一真正的测试是针对实体框架使用它。

  1. 它们是什么意思,我能从这些差异中分辨出什么?

它是表达式树的"文本表示形式"。光看一眼,就能看出.Compile().Invoke()。您必须记住,使用IQueryable<>"正常"(因此不包括本地执行的AsQueryable()),您的查询将转换为"服务器"(通常是 SQL Server)可以理解的"语言",以便远程执行。这个"翻译器"非常有限,只知道一些方法(最常见的方法)。因此,如果您尝试使用任何不是"最常见方法之一"的内容,那么您的查询将在执行时中断。

最新更新