para - ¿Cómo compones expresiones de consulta en F#?
formulas de access (2)
De manera ingenua, en el ejemplo original se podría tratar de citar el predicado y luego empalmarlo en:
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
}
(Me he tomado la libertad de limpiar el ejemplo original ligeramente)
Ejemplos como este (con abstracciones lambda simples de primer orden) a menudo funcionan en F #, pero en general, no hay garantía de que el QueryBuilder predeterminado de F # normalice las aplicaciones resultantes de las abstracciones lambda en el término citado. Esto puede dar como resultado mensajes de error extraños o consultas con un rendimiento deficiente (por ejemplo, consultar una tabla y luego generar una consulta en otra tabla por fila de la primera tabla, en lugar de realizar una única consulta de consulta).
Recientemente desarrollamos una biblioteca llamada FSharpComposableQuery
que (si está abierta) sobrecarga al operador de query
para realizar la normalización (y para hacer otras cosas útiles). Proporciona una garantía sólida para generar una sola consulta para un subconjunto no trivial de expresiones de consulta F #. Usando la versión de query
de FSharpComposableQuery
, la composición ingenua anterior funciona. También hemos realizado pruebas exhaustivas para intentar garantizar que FSharpComposableQuery
no rompa el código de consulta existente.
De manera similar, por ejemplo, al usar FSharpComposableQuery
, el ejemplo de Tomas no requiere la función especial RunQuery
. En su lugar, uno puede simplemente hacer:
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")
(Advertencia: solo he probado el código anterior solo con la versión OData de Northwind, no con el proveedor de tipo SQL, pero hemos probado un gran número de ejemplos similares y más complejos. La versión OData falla con un error misterioso de OData, pero esto parece ortogonal a la materia en cuestión.)
FSharpComposableQuery
ahora está disponible desde NuGet aquí: https://www.nuget.org/packages/FSharpComposableQuery
y más información (incluyendo ejemplos y un pequeño tutorial, que muestra formas de composición más complejas) se pueden encontrar aquí:
http://fsprojects.github.io/FSharp.Linq.ComposableQuery/
[EDITAR: Se modificaron los enlaces anteriores para eliminar la palabra "Experimental", ya que el nombre del proyecto ha cambiado.]
He estado buscando expresiones de consulta aquí http://msdn.microsoft.com/en-us/library/vstudio/hh225374.aspx
Y me he estado preguntando por qué lo siguiente es legítimo
let testQuery = query {
for number in netflix.Titles do
where (number.Name.Contains("Test"))
}
Pero realmente no puedes hacer algo como esto
let christmasPredicate = fun (x:Catalog.ServiceTypes.Title) -> x.Name.Contains("Christmas")
let testQuery = query {
for number in netflix.Titles do
where christmasPredicate
}
Seguramente F # permite una composición de este tipo para que puedas reutilizar un predicado. ¿Y si quisiera títulos de Navidad combinados con otro predicado como antes de una fecha específica? ¿Tengo que copiar y pegar toda mi consulta? C # es completamente diferente a esto y tiene varias formas de construir y combinar predicados
Esto fue bastante fácil de hacer con la versión F # 2.0 de consultas que requirieron citas explícitas (escribí una publicación de blog al respecto ). Hay una forma de lograr algo similar en C # (otra publicación de blog ) y creo que se pueden jugar trucos similares con F # 3.0.
Si no le importa la sintaxis más fea, también puede usar citas explícitas en F # 3.0. Cuando escribes
query { .. }
el compilador en realidad genera algo como:
query.Run(<@ ... @>)
donde el código dentro de <@ .. @>
se cita con el código F #, es decir, el código almacenado en un tipo Expr
que representa el código fuente y se puede traducir a expresiones LINQ y, por lo tanto, a SQL.
Aquí hay un ejemplo que probé con el proveedor de tipo 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")
El truco clave es que, cuando usa citas explícitas (usando <@ .. @>
), puede usar el operador %
para la división de la oferta. Esto significa que la cotización del predicate
se coloca en la cotización de la consulta (en test
) en el lugar donde se escribe %predicate
.
El código es bastante feo en comparación con la expresión de consulta agradable, pero sospecho que puedes hacerlo mejor escribiendo algo de DSL encima de esto o preprocesando la oferta.
EDITAR: Con un poco más de esfuerzo, es posible volver a utilizar la sintaxis de query { .. }
. Puede citar la expresión de consulta completa y escribir <@ query { .. } @>
; esto no funcionará directamente, pero luego puede tomar la cita y extraer el cuerpo real de la consulta y pasarla a query.Run
directamente. Aquí hay una muestra que funciona para el ejemplo anterior:
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")