Подтвердить что ты не робот

Как вы создаете выражения запроса в 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 # позволяет такую ​​композицию, чтобы вы могли повторно использовать предикат? Что, если бы я хотел, чтобы рождественские титулы сочетались с другим предикатом, как до определенной даты? Мне нужно скопировать и вставить весь мой запрос? С# полностью отличается от этого и имеет несколько способов построения и комбинирования предикатов

4b9b3361

Ответ 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", так как имя проекта изменилось.]