Я сделал то же самое для проекта, над которым я работаю, где запрос полностью создается во время выполнения на основе выбора, сделанного пользователем в пользовательском интерфейсе.
Я создаю запросы 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
с помощью Reflector, можно понять, что должно происходить при динамическом написании запроса. Я скопировал это ниже:
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 Expression для вызова самого себя. Поставщик LINQ на самом деле переводит это выражение во что-то другое — TSQL. Сам метод Select
на самом деле не выполняет выбор.
Ваш код должен делать то же самое — создавать выражения LINQ.
Как только вы освоитесь с простым выбором, вы можете добавить Queryable.Where
к сочетанию и другим функциям запроса LINQ. Я предлагаю оставить прогнозы (select new {x, y, z}
и т. д.) напоследок, потому что они довольно сложны. Вам нужно будет генерировать типы во время выполнения почти так же, как компилятор генерирует для вас анонимные типы. System.Reflection.Emit
— ваш рабочий инструмент.
Одним из преимуществ этого подхода является то, что его можно использовать с любым провайдером LINQ, таким как LINQ to Entities, LINQ to SQL, Mindscape Lightspeed и реализацией поставщика LINQ в памяти, предоставляемой AsQueryable
.
Мой код, который генерирует выражения LINQ, будет принимать IQueryable, и во время выполнения он в настоящее время поставляется с IQueryables Mindscape Lightspeed, но также может быть одним из других. Затем в своих модульных тестах я создаю тестовые данные, используя массивы объектов, а затем превращаю их в IQueryable
, используя AsQueryable
, которые передаются в генератор выражений LINQ. Мои модульные тесты могут затем генерировать все диапазоны сложных запросов, но их можно легко протестировать, не требуя базы данных. Пример выше показывает, как это можно сделать.
person
John Mills
schedule
17.08.2010