最近,我对IEnumerable
和IQueryable
接口之间的差异越来越感兴趣,因此,我发现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中进行了测试,它也有效, 但我有一些问题要了解发生了什么:
IQueryable
结果,那么我的尝试与你的尝试有什么区别(当然我知道我的不正确!)? 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)))}
如果您回答这些问题以帮助我理解IQueryable
如何运作,我将非常感激。
答案 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();
- 你怎么知道它是正确的,如果他们都给出了可以产生的结果,那么我的尝试与你的尝试有什么区别(当然我知道我的不正确!)?
醇>
因为我跑了它:-)你们不能工作,因为中间有一个.Compile()
。我知道实体框架库和LINQ-to-SQL库不支持.Compile()
(也不是.Invoke()
),所以我知道你的工作没有。< / p>
- 如果一个是正确的,那么我可以测试它以查看我自己吗? (你提到你用AsQueryable测试过它,怎么样?)
醇>
有一种&#34;气味&#34;测试,但可能甚至你的通过。
new[] { new { ID = 1 }, new { ID = 2 } }.AsQueryable().In(x => x.ID, 2, 4).ToArray()
唯一真正的考验是将其用于实体框架。
- 他们的意思是什么,我能从这些差异中得知什么?
醇>
这是一个&#34;文字表示&#34;表达式树。只要看一下,我就能看到.Invoke()
的{{1}}。你必须记住.Compile()
&#34;通常&#34; (因此不包括本地执行的IQueryable<>
)您的查询将被翻译为&#34;语言&#34;那个&#34;服务器&#34; (通常是SQL Server)可以理解,远程执行。这个&#34;翻译&#34;是非常有限的,只知道一些方法(最常见的方法)。因此,如果您尝试使用不是最常用方法之一的任何方法,那么您的查询将在执行时中断。