3
votes

I'm want to create a method which would construct an equivalent of query { ... } computation expression (using FSharp.Data.TypeProviders with either LINQ2SQL or LINQ2Entities) from provided data structure, i.e.:

Collection("customers", [ 
    Field "customerId"; 
    Collection("orders", [ 
        Field "orderId" ]) ])

For that I need to understand how the query is translated into code on the first place. Given following query as an example:

query {
    for c in db.Customers do
    select (c.CustomerID, query {
        for o in c.Orders do
        select o.OrderID
    } |> Seq.toList)
} 

What would it look like without computation expression syntax?

2
A great introduction into computation expressions is fsharpforfunandprofit.com/series/computation-expressions.html. What every keyword corresponds to is well documented in MSDN - for example, for ... in translates into a call to the For function on the builder type. To get the exact code emitted, you can always use a decompiler. - Luaan
Thanks Luaan. Unfortunately, I've failed when trying to make a straightforward translation using query expression. I've also tried to read decompiled code. This however query's body turns out to be a mixing of typeof's and byte numbers written directly into an array and called using single method - probably part of that query body is in fact a F# quotation. Nor particulary readable for humans thou. - Bartosz Sypytkowski
There are many type providers for SQL Access. F# already provides type providers over EF and L2S since version 3. But .. - Panagiotis Kanavos
With FSharp.Data.SqlClient's SqlCommandProvider though you don't need the mappings at all. The provider exposes the tables as types. Think of it as the type-safe, LINQ enabled equivalent of Dapper - Panagiotis Kanavos
@Panagiotis Kanavos - all of your questions are already answered, if your read my question carefully: 1. I've mentioned the specific NuGet package I'm using (FSharp.Data.TypeProviders with LinqToSql in the example). 2. Also I need to return entities with nested one-to-many relationships (also presented in the example). So Dapper-like provider won't help me. Also erasing type providers will probably fail here. 3. I clearly mentioned that I need to deconstruct a query (all type providers exposing query expression work on SQL databases). - Bartosz Sypytkowski

2 Answers

4
votes

I would not recommend generating F# queries programmatically.

The main purpose of queries is to make it possible to write nicely looking F# source code. When they are executed, the code you write is first translated to LINQ-style expression tree, which is then translated to SQL query. If you managed to translate your query specification into F# query, you'd have something like:

 +------------+    +----------+    +----------------------+    +-----+
 | Your query | -> | F# query | -> | LINQ expression tree | -> | SQL |
 +------------+    +----------+    +----------------------+    +-----+

There is nothing wrong with multiple transformations, but there is a number of things that make the path via F# query more complicated:

  • In your query format, things seem to be represented as strings. So, you'd need to go from strings to .NET MethodInfo just to go back to strings.

  • With F# queries/LINQ, you get back typed objects and that's one of the main benefits, but if you build query dynamically, you'll just get back a squence of obj values - and will have to use reflection or something like that to access them.

TL;DR - I think it will be much easier to just take your query representation and generate SQL directly. The structure of the transformation is similar to generating F# query, but you won't have to mess around with quotations and reflection.

4
votes

If you want to see how something desugars, just put it into a quotation. If you do this with your example, you'll see that

<@ 
    query {
        for c in db.Customers do
        select (c.CustomerID, query {
            for o in c.Orders do
            select o.OrderID
        } |> Seq.toList)
    } 
@>

is roughly equivalent to

query.Run 
    <@ query.Select(query.For(query.Source db.Customers, 
                              fun c -> query.Yield c), 
                    fun c -> 
                        c.CustomerID, 
                        query.Run <@ 
                                    query.Select(query.For(query.Source c.Orders, 
                                                           fun o -> query.Yield o), 
                                                 fun o -> o.OrderID) @> 
                             |> Seq.toList) @>

(where I've cleaned up a few extraneous variables). I agree with Tomas that there are probably better ways to achieve what you want than trying to create such an expression directly.