Как вы создаете выражения запроса в F #?
Я рассматривал выражения запроса здесь http://msdn.microsoft.com/en-us/library/vstudio/hh225374.aspx
И мне было интересно, почему следующее является законным
let testQuery = query {
for number in netflix.Titles do
where (number.Name.Contains("Test"))
}
Но вы действительно не можете сделать что-то вроде этого
let christmasPredicate = fun (x:Catalog.ServiceTypes.Title) -> x.Name.Contains("Christmas")
let testQuery = query {
for number in netflix.Titles do
where christmasPredicate
}
Конечно, F # позволяет такую композицию, чтобы вы могли повторно использовать предикат? Что, если бы я хотел, чтобы рождественские титулы сочетались с другим предикатом, как до определенной даты? Мне нужно скопировать и вставить весь мой запрос? С# полностью отличается от этого и имеет несколько способов построения и комбинирования предикатов
Ответы
Ответ 1
Это было довольно легко сделать с версией запросов F # 2.0, которая требовала явных цитат (я написал сообщение в блоге об этом). Существует способ добиться аналогичной вещи в С# (другое сообщение в блоге), и я думаю, что подобные трюки можно было бы воспроизвести с помощью F # 3.0.
Если вы не против более сильного синтаксиса, вы также можете использовать явные цитаты в F # 3.0. Когда вы пишете
query { .. }
компилятор действительно генерирует что-то вроде:
query.Run(<@ ... @>)
где код внутри <@ .. @>
указан кодом F #, то есть кодом, хранящимся в типе Expr
, который представляет исходный код и может быть переведен в выражения LINQ и, следовательно, в SQL.
Вот пример, который я тестировал с помощью поставщика SqlDataConnection
:
let db = Nwind.GetDataContext()
let predicate = <@ fun (p:Nwind.ServiceTypes.Products) ->
p.UnitPrice.Value > 50.0M @>
let test () =
<@ query.Select
( query.Where(query.Source(db.Products), %predicate),
fun p -> p.ProductName) @>
|> query.Run
|> Seq.iter (printfn "%s")
Основной трюк заключается в том, что при использовании явных котировок (используя <@ .. @>
) вы можете использовать оператор %
для резки котировок. Это означает, что цитата predicate
помещается в цитату запроса (в test
), где вы пишете %predicate
.
Код довольно уродливый по сравнению с хорошим выражением запроса, но я подозреваю, что вы могли бы сделать его лучше, написав некоторые DSL поверх этого или предварительно обработав цитату.
РЕДАКТИРОВАТЬ: С меньшими усилиями на самом деле можно снова использовать синтаксис query { .. }
. Вы можете процитировать все выражение запроса и написать <@ query { .. } @>
- это не будет работать напрямую, но затем вы можете взять цитату и извлечь фактический текст запроса и передать его в query.Run
напрямую. Вот пример, который работает для приведенного выше примера:
open System.Linq
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns
let runQuery (q:Expr<IQueryable<'T>>) =
match q with
| Application(Lambda(builder, Call(Some builder2, miRun, [Quote body])), queryObj) ->
query.Run(Expr.Cast<Microsoft.FSharp.Linq.QuerySource<'T, IQueryable>>(body))
| _ -> failwith "Wrong argument"
let test () =
<@ query { for p in db.Products do
where ((%predicate) p)
select p.ProductName } @>
|> runQuery
|> Seq.iter (printfn "%s")
Ответ 2
Наивно, в исходном примере можно попытаться процитировать предикат, а затем объединить его в:
let christmasPredicate = <@ fun (x:Catalog.ServiceTypes.Title) ->
x.Name.Contains("Christmas") @>
let testQuery = query {
for number in netflix.Titles do
where ((%christmasPredicate) number)
select number
}
(я позволил немного очистить оригинальный пример)
Примеры, подобные этому (с простыми лямбда-абстракциями первого порядка), часто работают в F # в любом случае, но в целом нет гарантии, что F # по умолчанию QueryBuilder нормализует полученные приложения лямбда-абстракций в цитируемых срок. Это может привести к появлению странных сообщений об ошибках или к запросам с низкой производительностью (например, запрос одной таблицы, а затем генерация одного запроса в другой таблице для каждой строки первой таблицы вместо того, чтобы делать одно соединение запроса).
Недавно мы разработали библиотеку с именем FSharpComposableQuery
, которая (если открыта) перегружает оператор query
для выполнения нормализации (и для выполнения других полезных действий). Он обеспечивает надежную гарантию генерации единого запроса для нетривиального подмножества выражений запроса F #. Используя FSharpComposableQuery
версию query
, работает наивный состав. Мы также много тестировали, чтобы убедиться, что FSharpComposableQuery
не нарушает существующий код запроса.
Аналогично, например, используя FSharpComposableQuery
, для примера Tomas не требуется специальная функция RunQuery
. Вместо этого можно просто сделать:
open FSharpComposableQuery
let predicate = <@ fun (p:Nwind.ServiceTypes.Product) ->
p.UnitPrice.Value > 50.0M @>
let test () =
query { for p in db.Products do
where ((%predicate) p)
select p.ProductName }
|> Seq.iter (printfn "%s")
(Предостережение: я тестировал только этот код только с версией OData Northwind, а не с провайдером типа SQL, но мы протестировали большое количество подобных и более сложных примеров. Версия OData завершилась с таинственной ошибкой OData, но это кажется ортогональным этому вопросу.)
FSharpComposableQuery
теперь доступен от NuGet здесь: https://www.nuget.org/packages/FSharpComposableQuery
и более подробная информация (включая примеры и небольшой учебник, демонстрирующие более сложные формы композиции) можно найти здесь:
http://fsprojects.github.io/FSharp.Linq.ComposableQuery/
[EDIT: изменили приведенные выше ссылки, чтобы удалить слово "Experimental", так как имя проекта изменилось.]