我为我正在处理的项目做了同样的事情,其中查询是在运行时根据用户在 UI 中所做的选择完全创建的。
我使用命名空间中的类使用表达式树构造 LINQ 查询System.Linq.Expressions
。它非常强大,但学习曲线陡峭。
您可以使用LINQPad编写查询,然后转储表达式以查看树下面的样子,以便您知道如何自己构建查询。
例如,在 LINQPad 中运行以下代码将生成表达式树的转储。
var query = from p in Puzzles
select p;
query.Expression.Dump(20);

那么,如何真正编写动态创建简单 LINQ 查询的代码呢?
考虑以下最简单的查询示例:
var query = from person in data
select person;
以下代码将即时生成等效查询。
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace TestLinqGenerator
{
class Program
{
static void Main(string[] args)
{
// Set up dummy data
var data = new[]
{
new {Name = "Fred"},
new {Name = "Simon"}
}.AsQueryable();
var dataType = data.ElementType;
// IQueryable: data
var source = Expression.Constant(data);
// Parameter: person
var parameter = Expression.Parameter(dataType, "person");
// person => person
var lambda = Expression.Lambda(parameter, parameter);
// Expression: data.Select(person => person)
var callSelect = Expression.Call(GetSelect().MakeGenericMethod(dataType, dataType), source, Expression.Quote(lambda));
// IQueryable: data.Select(person => person)
var query = data.Provider.CreateQuery(callSelect);
// Execute query
var results = query.Cast<object>().ToList();
}
private static MethodInfo GetSelect()
{
// Get MethodInfo of the following method from System.Linq.Queryable:
// public static IQueryable<TSource> Select<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
return typeof(System.Linq.Queryable).GetMethods().Where(
method => method.Name == "Select" && method.GetParameters().Length == 2 &&
method.GetParameters()[1].ParameterType.GetGenericArguments()[0].Name == typeof(Func<,>).Name).Single();
}
}
}
您应该能够通过将其粘贴到控制台应用程序来运行此代码。使用调试器逐步了解每个步骤的作用。
额外信息
查看Queryable.Select
使用反射器的实现有助于理解动态编写查询时需要发生什么。我在下面复制了它:
public static IQueryable<TResult> Select<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, int, TResult>> selector)
{
if (source == null)
{
throw Error.ArgumentNull("source");
}
if (selector == null)
{
throw Error.ArgumentNull("selector");
}
return source.Provider.CreateQuery<TResult>(Expression.Call(null, ((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSource), typeof(TResult) }), new Expression[] { source.Expression, Expression.Quote(selector) }));
}
有趣的是, 的实现Queryable.Select
只是创建了一个调用自身的 LINQ 表达式表示。LINQ 提供程序实际上将该表达式转换为其他内容 - TSQL。该Select
方法本身实际上并不执行选择。
您的代码应该做同样的事情 - 创建 LINQ 表达式。
一旦您熟悉了如何进行简单的选择,您就可以考虑添加Queryable.Where
LINQ 查询的混合和其他功能。我建议保留预测(select new {x, y, z}
等),因为它们非常困难。您需要在运行时生成类型,就像编译器为您生成匿名类型一样。System.Reflection.Emit
是您完成工作的工具。
这种方法的好处之一是您可以将它与任何 LINQ 提供程序一起使用,例如 LINQ to Entities、LINQ to SQL、Mindscape Lightspeed 以及由AsQueryable
.
我的生成 LINQ 表达式的代码将接受 IQueryable,并且在运行时这当前由 Mindscape Lightspeed IQueryables 提供,但也可能是其他代码之一。然后在我的单元测试中,我使用对象数组创建测试数据,然后将其转换为传递给 LINQ 表达式生成器的IQueryable
using 。AsQueryable
然后,我的单元测试可以生成所有范围的复杂查询,但无需数据库即可轻松测试。上面的示例显示了如何做到这一点。