Я сделал то же самое для проекта, над которым я работаю, когда запрос полностью создается во время выполнения на основе выбора, сделанного пользователем в пользовательском интерфейсе.
Я строю запросы 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 для самого вызова. Поставщик 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, и во время выполнения он в настоящее время поставляется вместе с Mindscape Lightspeed IQueryables, но также может быть одним из других. Затем в своих модульных тестах я создаю тестовые данные, используя массивы объектов, а затем превращаю их в IQueryable
, используя AsQueryable
, который передается в генератор выражений LINQ. Мои модульные тесты могут генерировать все диапазоны сложных запросов, но их можно легко протестировать, не требуя базы данных. Пример выше показывает, как это можно сделать.