Повторное использование LINQ-SQL - CompiledQuery.Compile - PullRequest
13 голосов
/ 25 июня 2011

Я играл с LINQ-SQL, пытаясь получить многократно используемые фрагменты выражений, которые я могу подключить к другим запросам. Итак, я начал с чего-то вроде этого:

Func<TaskFile, double> TimeSpent = (t =>
t.TimeEntries.Sum(te => (te.DateEnded - te.DateStarted).TotalHours));

Затем мы можем использовать вышеупомянутое в запросе LINQ, как показано ниже (пример LINQPad):

TaskFiles.Select(t => new {
    t.TaskId,
    TimeSpent = TimeSpent(t),
})

Получает ожидаемый результат, , за исключением того, что для подключенного выражения создается запрос на строку. Это видно в LINQPad. Не хорошо.

Во всяком случае, я заметил CompiledQuery.Compile метод. Хотя для этого требуется DataContext в качестве параметра, я решил включить его и проигнорировать и попробовать тот же Func. В итоге я получил следующее:

static Func<UserQuery, TaskFile, double> TimeSpent =
     CompiledQuery.Compile<UserQuery, TaskFile, double>(
        (UserQuery db, TaskFile t) => 
        t.TimeEntries.Sum(te => (te.DateEnded - te.DateStarted).TotalHours));

Обратите внимание, что я не использую параметр db. Однако теперь, когда мы используем этот обновленный параметр, генерируется только 1 SQL-запрос. Выражение успешно переведено в SQL и включено в исходный запрос.

Итак, мой главный вопрос: что делает CompiledQuery.Compile таким особенным? Кажется, что параметр DataContext вообще не нужен, и на данный момент я думаю, что это более удобный параметр для генерации полных запросов.

Будет ли хорошей идеей использовать такой метод CompiledQuery.Compile? Это похоже на большой взлом, но кажется, что это единственный жизнеспособный путь для повторного использования LINQ.

UPDATE

Используя первый Func в пределах Where, мы видим следующее исключение, как показано ниже:

NotSupportedException: Method 'System.Object DynamicInvoke(System.Object[])' has no supported translation to SQL.

Как показано ниже:

.Where(t => TimeSpent(t) > 2)

Однако, когда мы используем Func, сгенерированный CompiledQuery.Compile, запрос успешно выполняется и генерируется правильный SQL.

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

Ответы [ 3 ]

3 голосов
/ 14 июля 2011

Exec Summary:

Expression.Compile создает метод CLR, тогда как CompiledQuery.Compile создает делегат, который является заполнителем для SQL.


Одна из причин, по которой вы этого не сделалиполучить правильный ответ до сих пор, что некоторые вещи в вашем примере кода неверны.А без базы данных или обобщенного примера кто-то другой может сыграть с шансами, которые еще больше уменьшатся (я знаю, это трудно представить, но обычно это того стоит).

На фактах:

Expression<Func<TaskFile, double>> TimeSpent = (t =>
    t.TimeEntries.Sum(te => (te.DateEnded - te.DateStarted).TotalHours));

Затем мы можем использовать вышеупомянутое в запросе LINQ, как показано ниже:

TaskFiles.Select(t => new {
    t.TaskId,
    TimeSpent = TimeSpent(t),
})

(Примечание: возможно, вы использовали тип Func<> для TimeSpent. Это даетта же ситуация, что и в вашем сценарии, была описана в параграфе ниже. Обязательно прочитайте и поймите это).

Нет, это не скомпилируется.Выражения не могут быть вызваны (TimeSpent является выражением).Сначала их нужно скомпилировать в делегат.Когда вы вызываете Expression.Compile(), то происходит то, что дерево выражений компилируется в IL, который внедряется в DynamicMethod, для которого вы получаете делегат.

Следующее будет работать:

var q = TaskFiles.Select(t => new {
    t.TaskId,
    TimeSpent = TimeSpent.Compile().DynamicInvoke()
});  

Это приводит к ожидаемому выводу, за исключением того, что для подключенного выражения генерируется запрос на строку.Это видно в LINQPad.Не хорошо.

Почему это происходит?Что ж, Linq To Sql нужно будет извлечь все TaskFiles, обезвоживать TaskFile экземпляров и затем запустить селектор против него в памяти .Вы получаете запрос для каждого TaskFile, вероятно, потому что он содержит одно или несколько отображений 1: m.

Хотя LTS позволяет проецировать в память выборки, он не делает это для Wheres (требуется цитирование, это лучшемои знания).Когда вы думаете об этом, это имеет смысл: скорее всего, вы перенесете намного больше данных, отфильтровав целую базу данных 1040 * в памяти, а затем преобразовав ее часть в памяти.(Хотя, как вы видите, это создает проблемы с производительностью запросов, что следует учитывать при использовании ORM).

CompiledQuery.Compile() делает что-то другое.Он компилирует запрос в SQL, и возвращаемый им делегат является только заполнителем, который Linq для SQL будет использовать для внутреннего использования.Вы не можете «вызвать» этот метод в CLR, его можно использовать только как узел в другом дереве выражений.

Так почему же тогда LTS генерирует эффективный запрос с выражением CompiledQuery.Compile 'd?Потому что он знает, что делает этот узел выражения, потому что он знает SQL, стоящий за ним.В случае Expression.Compile это просто InvokeExpression, который вызывает DynamicMethod, как я объяснил ранее.

Почему для этого требуется параметр DataContext?Да, это удобнее для создания полных запросов, но это также потому, что компилятору дерева выражений необходимо знать сопоставление, используемое для генерации SQL.Без этого параметра было бы трудно найти это отображение, поэтому это очень разумное требование.

1 голос
/ 04 июля 2011

Я удивлен, почему у вас пока нет ответов на этот вопрос.CompiledQuery.Compile компилирует и кэширует запрос.Вот почему вы видите, что генерируется только один запрос.

Мало того, что это НЕ взлом, это рекомендуемый способ!

Ознакомьтесь с этими статьями MSDN для подробного ознакомления.информация и пример:

Скомпилированные запросы (LINQ to Entities)
Как: хранить и повторно использовать запросы (LINQ to SQL)

Обновление: (превышен лимит для комментариев)
Я немного покопался в рефлекторе и вижу, что используется DataContext.В вашем примере вы просто не используете его.

Сказав это, основное различие между ними заключается в том, что первый создает делегат (для дерева выражений), а второй создает SQL, который кэшируется и фактически возвращает функцию (своего рода).Первые два выражения создают запрос, когда вы вызываете Invoke, поэтому вы видите их несколько.

Если ваш запрос не изменяется, а только DataContext и Parameters, и если вы планируете использовать его несколько раз, CompiledQuery.Compile поможет.Компиляция обходится дорого, поэтому для разовых запросов нет никакой выгоды.

0 голосов
/ 14 июля 2011
TaskFiles.Select(t => new {
  t.TaskId,
  TimeSpent = TimeSpent(t),
})

Это не запрос LinqToSql, так как нет экземпляра DataContext. Скорее всего, вы запрашиваете некоторый EntitySet, который не реализует IQueryable.

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

Кроме того, попробуйте это:

var query = myDataContext.TaskFiles
  .Where(tf => tf.Parent.Key == myParent.Key)
  .Select(t => new {
    t.TaskId,
    TimeSpent = TimeSpent(t)
  });
// where myParent is the source of the EntitySet and Parent is a relational property.
//  and Key is the primary key property of Parent.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...