Как создать очень динамический запрос LinqToEntity? - PullRequest
3 голосов
/ 17 августа 2010

Мне нужно построить очень динамический запрос Linq для различного числа таблиц. Например, у меня есть связанные таблицы:
TABLE_A
- ID
- Имя
- Описание

TABLE_B
- ID
- Table_A_ID
- Имя
- Desc

Table_C
- ID
- Table_B_ID
- Имя
- Desc

У меня есть словарь с информацией о табличных зависимостях, содержащий:
tableName, parentTableName, foreignKey, parentPK
Пример:
"Table_B", "Table_A", "Table_A_ID", "ID"
"Table_C", "Table_B", "Table_B_ID", "ID"

-> tableInfo ["Table_B"]. ForeignKey вернет "Table_A_ID" и т. Д.

Теперь пользователь может выбирать, какие столбцы он хочет видеть.
Примеры:

Table_B.Name, Table_C.Desc
или же Table_A.Name, Table_B.Name
или же Table_A.Name, Table_B.Name, Table_B.Desc, Table_C.Name

Этот выбор будет доступен в другом списке:
Например, для выбора 3:
viewInfo ["Table_A"] содержит "Имя"
viewInfo ["Table_B"] содержит "Name", "Desc"
viewInfo ["Table_C"] содержит "Имя"

Как динамически создать запрос, просто используя необходимые таблицы и поля, чтобы получить желаемый результат?

Ответы [ 3 ]

4 голосов
/ 17 августа 2010

Я сделал то же самое для проекта, над которым я работаю, когда запрос полностью создается во время выполнения на основе выбора, сделанного пользователем в пользовательском интерфейсе.

Я строю запросы LINQ, используя деревья выражений, используя классы в пространстве имен System.Linq.Expressions. Это очень мощный, но крутой курс обучения.

Вы можете использовать LINQPad для написания запросов, а затем выгрузить выражения, чтобы посмотреть, как выглядит дерево внизу, чтобы вы знали, как создавать запросы самостоятельно.

Например, при запуске следующего кода в LINQPad будет создан дамп дерева выражений.

var query = from p in Puzzles
select p;

query.Expression.Dump(20);

LINQPad Screenshot

Так как же на самом деле написать код, который динамически создает простой запрос 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. Мои модульные тесты могут генерировать все диапазоны сложных запросов, но их можно легко протестировать, не требуя базы данных. Пример выше показывает, как это можно сделать.

3 голосов
/ 17 августа 2010

Существует проект под названием Dynamic LINQ , который может помочь вам динамически создавать запросы.Я думаю, вы должны взглянуть на этот проект.

Кроме этого, также можно создавать запросы по частям, запрашивая запрос LINQ.Вы можете поместить условные операторы в свой код и, если за какой-то веткой последует, то вы можете создать новый запрос из существующего запроса, запросив его снова.Запрос не выполняется до тех пор, пока вы не запросите результаты, поэтому с точки зрения производительности не имеет большого значения, если вы строите запрос небольшими частями или делаете один огромный запрос с самого начала.Используя эту технику, вы можете (на основе значений входных данных) создавать структурно различные запросы, которые разделяют некоторые общие части, в то же время обладая преимуществами статической типизации и intellisense.

1 голос
/ 23 августа 2010

Я решил свою проблему, используя очень интересный фреймворк NLinq , найденный в Codeplex.Вам просто нужно создать строку, содержащую ваш «обычный» запрос Linq!

Цитирование из описания объекта:

NLinq - это фреймворк, нацеленный на переопределение функций Linq в Visual Studio .Net 2003 и VisualStudio 2005 (C # & VB .Net), предоставляющий синтаксический анализатор Linq и среду выполнения Linq To Objects.С NLinq вы можете прямо сейчас воспользоваться основными функциями C # 3.0, не требуя этого.

Пример:

Data sources used for the samples
        Person[] people = new Person[] { 
            new Person("Bill", 31), 
            new Person("John", 30), 
            new Person("Cindy", 25), 
            new Person("Sue", 29) 
        };

        // For testing physical links
        people[0].Friends.Add(people[0]);
        people[0].Friends.Add(people[1]);
        people[1].Friends.Add(people[2]);
        people[2].Friends.Add(people[3]);
        people[3].Friends.Add(people[0]);

        // For testing logical links
        Address[] addresses = new Address[] {
            new Address("Bill", "Redmon"),
            new Address("Bill", "Boston"),
            new Address("Cindy", "New York")
        };

Projections query = new NLinqQuery(
                @"  from c in people 
                    from d in people
                    where c.Age > d.Age
                    select new NLinq.Play.Person ( c.Firstname, d.Age )");

        linq = new LinqToMemory(query);
        linq.AddSource("people", people);


Result:
Sue (25)
John (25)
John (29)
Bill (30)
Bill (25)
Bill (29)
...