如何创建 IEnumerable 扩展方法的 IQueryable 版本?
How to create the IQueryable version of IEnumerable extension method?
最近,我对 IEnumerable
和 IQueryable
接口之间的差异更感兴趣,因此,我发现 IQueryable
在许多情况下比 [=12= 更有效],虽然我还没有完全掌握它们。既不将表达式树与 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
的工作原理,我将不胜感激..
有点复杂。我用 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);
}
只是为了好玩,"simple" 在不使用反射的情况下找到 Enumerable.Contains<T>
的方法(与第一个示例一起使用):
private static readonly MethodInfo Contains = ((MethodCallExpression)((Expression<Func<bool>>)(() => new object[0].Contains(new object()))).Body).Method.GetGenericMethodDefinition();
- How do you know that it works right, what does differentiate my try from your's if they both give IQueryable result(off course I know mine is not correct!)?
因为我 运行 它 :-) 你的不能工作,因为中间有一个 .Compile()
。我知道 Entity Framework 库和 LINQ-to-SQL 库不支持 .Compile()
(也不支持 .Invoke()
),所以我知道你的不支持工作。
- Could I test it to see my self if one is correct and other not? (you mentioned you tested it with AsQueryable, how?)
有一项 "smell" 测试,但您的测试也可能通过了。
new[] { new { ID = 1 }, new { ID = 2 } }.AsQueryable().In(x => x.ID, 2, 4).ToArray()
唯一真正的测试是用它来对抗 Entity Framework。
- what do they mean, and what can I tell from these differences?
它是一个"textual representation"的表达式树。光是看一眼,就能看到.Compile()
的.Invoke()
了。您必须记住,使用 IQueryable<>
"normally"(因此不包括在本地执行的 AsQueryable()
),您的查询 t运行 被指定为 "language" "server"(通常是SQL服务器)可以理解,可以远程执行。这个 "translator" 非常有限,只知道一些方法(最常见的)。因此,如果您尝试使用任何不是 "one of the most common methods" 的内容,那么您的查询将在执行时中断。
最近,我对 IEnumerable
和 IQueryable
接口之间的差异更感兴趣,因此,我发现 IQueryable
在许多情况下比 [=12= 更有效],虽然我还没有完全掌握它们。既不将表达式树与 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
的工作原理,我将不胜感激..
有点复杂。我用 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);
}
只是为了好玩,"simple" 在不使用反射的情况下找到 Enumerable.Contains<T>
的方法(与第一个示例一起使用):
private static readonly MethodInfo Contains = ((MethodCallExpression)((Expression<Func<bool>>)(() => new object[0].Contains(new object()))).Body).Method.GetGenericMethodDefinition();
- How do you know that it works right, what does differentiate my try from your's if they both give IQueryable result(off course I know mine is not correct!)?
因为我 运行 它 :-) 你的不能工作,因为中间有一个 .Compile()
。我知道 Entity Framework 库和 LINQ-to-SQL 库不支持 .Compile()
(也不支持 .Invoke()
),所以我知道你的不支持工作。
- Could I test it to see my self if one is correct and other not? (you mentioned you tested it with AsQueryable, how?)
有一项 "smell" 测试,但您的测试也可能通过了。
new[] { new { ID = 1 }, new { ID = 2 } }.AsQueryable().In(x => x.ID, 2, 4).ToArray()
唯一真正的测试是用它来对抗 Entity Framework。
- what do they mean, and what can I tell from these differences?
它是一个"textual representation"的表达式树。光是看一眼,就能看到.Compile()
的.Invoke()
了。您必须记住,使用 IQueryable<>
"normally"(因此不包括在本地执行的 AsQueryable()
),您的查询 t运行 被指定为 "language" "server"(通常是SQL服务器)可以理解,可以远程执行。这个 "translator" 非常有限,只知道一些方法(最常见的)。因此,如果您尝试使用任何不是 "one of the most common methods" 的内容,那么您的查询将在执行时中断。