SQLProvider


Composable Queries

Definition of Composable Queries

Basicly composable methods are those that you can chain together to build the desired functionality out of smaller parts. By passing functions as parameters you are able to generate higher-order query operations. Therefore composable query means that you can do logics to compose just one database-SQL-query from multiple queryables. By using composable queries you can shorten the Database transactions and keep the connection open a minimum of time.

Generate composable queries by using Linq IQueryable

With composable queries you can combine two queries in multiple ways, and one query can be used as the building block for the other query. To see how this works, let’s look at a simple query:

1: 
2: 
3: 
4: 
5: 
let query1 =
    query {
      for customers  in ctx.Main.Customers do
      where (customers.ContactTitle = "USA")
      select (customers)}

The variable that is returned from the query is sometimes called a computation. If you write evaluate (e.g. a foreach loop or |> Seq.toList) and display the address field from the customers returned by this computation, you see the following output:

1: 
2: 
3: 
4: 
5: 
GREAL|Great Lakes Food Market|Howard Snyder|Marketing Manager|2732 Baker Blvd.|Eugene|OR|97403|USA|(503) 555-7555|
HUNGC|Hungry Coyote Import Store|Yoshi Latimer|Sales Representative|City Center Plaza 516 Main St.|Elgin|OR|97827|USA|(503) 555-6874|(503) 555-2376
...
TRAIH|Trail's Head Gourmet Provisioners|Helvetius Nagy|Sales Associate|722 DaVinci Blvd.|Kirkland|WA|98034|USA|(206) 555-8257|(206) 555-2174
WHITC|White Clover Markets|Karl Jablonski|Owner|305 - 14th Ave. S. Suite 3B|Seattle|WA|98128|USA|(206) 555-4112|(206) 555-4115

You can now write a second query against the result of this query:

1: 
2: 
3: 
4: 
5: 
6: 
let query2 =
    query {
      for customers in query1 do
      where (customers.CompanyName = "The Big Cheese")
      select customers}
    |> Seq.toArray

Notice that the last word in the first line of this query is the computation returned from the previous query. This second query produces the following output:

1: 
THEBI|The Big Cheese|Liz Nixon|Marketing Manager|89 Jefferson Way Suite 2|Portland|OR|97201|USA|(503) 555-3612|

SQLProvider to Objects queries are composable because they operate on and usually return variables of type IQueryable<T>. In other words, SQLProvider queries typically follow this pattern:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
let (query:IQueryable<'T>) = 
    query {
        //for is like C# foreach
        for x in (xs:IQueryable<'T>) do
        select x
    }

// 

This is a simple mechanism to understand, but it yields powerful results. It allows you to take complex problems, break them into manageable pieces, and solve them with code that is easy to understand and easy to maintain.

Generate composable queries by using .NET LINQ functions with IQueryable.

The difference between IEnumerable and IQueryable is basically that IEnumerable is executed in the IL while IQueryable can be translated as en expression tree to some other context (like a database query). They are both lazy by nature, meaning they aren’t evaluated until you enumerate over the results.

Here an example:

First you have to add .NET LINQ:

1: 
open System.Linq

Then you can define a composable query outside the main query

1: 
2: 
3: 
4: 
5: 
6: 
7: 
let companyNameFilter inUse queryable =
    let queryable:(IQueryable<CustomersEntity> -> IQueryable<CustomersEntity>) =
        match inUse with
        |true ->
            (fun iq -> iq.Where(fun (c:CustomersEntity) -> c.CompanyName = "The Big Cheese"))
        |false -> (fun iq -> iq.Where(fun (c:CustomersEntity) -> c))
    queryable

(Let's asume that your inUse some complex data: E.g. Your sub-queries would come from other functions. Basic booleans you can just include to your where-clause)

Then you can create the main query

1: 
2: 
3: 
4: 
5: 
let query1 =
    query {
        for customers  in ctx.Main.Customers do
        where (customers.ContactTitle = "USA")
        select (customers)}

and now call you are able to call the second query like this

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
let query2 =
    companyNameFilter true query1 |> Seq.toArray

    let qry1 =
        query { for u in dbContext.Users do
                select (u.Id, u.Name, u.Email)
        }

    let qry2 =
        query { for c in dbContext.Cars do
                select (c.UserId, c.Brand, c.Year)
        }

    query { for (i,n,e) in qry1 do
            join (u,b,y) in qry2 on (i = u)
            where (y > 2015)
            select (i,n,e,u,b,y)
        } |> Seq.toArray

Generate composable queries by using FSharp.Linq.ComposableQuery

The SQLProvider also supports composable queries by integrating following library FSharpLinqComposableQuery. You can read more about that library here: FSharp.Linq.ComposableQuery

Because it is implemented in the SQLProvider you dont need to add FSharpComposableQuery in your script.

Example for using FSharpComposableQuery

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
let qry1 =
    query { for u in dbContext.Users do
            select (u.Id, u.Name, u.Email)
    }

let qry2 =
    query { for c in dbContext.Cars do
            select (c.UserId, c.Brand, c.Year)
    }

query { for (i,n,e) in qry1 do
        join (u,b,y) in qry2 on (i = u)
        where (y > 2015)
        select (i,n,e,u,b,y)
    } |> Seq.toArray

Nested Select Where Queries

You can create a query like SELECT * FROM xs WHERE xs.x IN (SELECT y FROM ys)) with either LINQ Contains or custom operators: in |=| and not-in |<>| This is done by not saying |> Seq.toArray to the first query:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
open System.Linq

let nestedQueryTest = 
    let qry1 = query {
        for emp in ctx.Hr.Employees do
        where (emp.FirstName.StartsWith("S"))
        select (emp.FirstName)
    }
    query {
        for emp in ctx.Hr.Employees do
        where (qry1.Contains(emp.FirstName))
        select (emp.FirstName, emp.LastName)
    } |> Seq.toArray
Multiple items
type LiteralAttribute =
  inherit Attribute
  new : unit -> LiteralAttribute

Full name: Microsoft.FSharp.Core.LiteralAttribute

--------------------
new : unit -> LiteralAttribute
val connectionString : string

Full name: Composable.connectionString
val resolutionPath : string

Full name: Composable.resolutionPath
Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
Multiple items
namespace FSharp.Data

--------------------
namespace Microsoft.FSharp.Data
namespace FSharp.Data.Sql
type sql = SqlDataProvider<...>

Full name: Composable.sql
type SqlDataProvider

Full name: FSharp.Data.Sql.SqlDataProvider


<summary>Typed representation of a database</summary>
                    <param name='ConnectionString'>The connection string for the SQL database</param>
                    <param name='ConnectionStringName'>The connection string name to select from a configuration file</param>
                    <param name='DatabaseVendor'> The target database vendor</param>
                    <param name='IndividualsAmount'>The amount of sample entities to project into the type system for each SQL entity type. Default 1000.</param>
                    <param name='UseOptionTypes'>If true, F# option types will be used in place of nullable database columns. If false, you will always receive the default value of the column's type even if it is null in the database.</param>
                    <param name='ResolutionPath'>The location to look for dynamically loaded assemblies containing database vendor specific connections and custom types.</param>
                    <param name='Owner'>The owner of the schema for this provider to resolve (Oracle Only)</param>
                    <param name='CaseSensitivityChange'>Should we do ToUpper or ToLower when generating table names?</param>
                    <param name='TableNames'>Comma separated table names list to limit a number of tables in big instances. The names can have '%' sign to handle it as in the 'LIKE' query (Oracle and MSSQL Only)</param>
                    <param name='OdbcQuote'>Odbc quote characters: Quote characters for the table and column names: `alias`, [alias]</param>
                    <param name='SQLiteLibrary'>Use System.Data.SQLite or Mono.Data.SQLite or select automatically (SQLite only)</param>
                    
namespace FSharp.Data.Sql.Common
type DatabaseProviderTypes =
  | MSSQLSERVER = 0
  | SQLITE = 1
  | POSTGRESQL = 2
  | MYSQL = 3
  | ORACLE = 4
  | MSACCESS = 5
  | ODBC = 6

Full name: FSharp.Data.Sql.Common.DatabaseProviderTypes
Common.DatabaseProviderTypes.SQLITE: Common.DatabaseProviderTypes = 1
type CaseSensitivityChange =
  | ORIGINAL = 0
  | TOUPPER = 1
  | TOLOWER = 2

Full name: FSharp.Data.Sql.Common.CaseSensitivityChange
Common.CaseSensitivityChange.ORIGINAL: Common.CaseSensitivityChange = 0
val ctx : SqlDataProvider<...>.dataContext

Full name: Composable.ctx
SqlDataProvider<...>.GetDataContext() : SqlDataProvider<...>.dataContext


<summary>Returns an instance of the SQL Provider using the static parameters</summary>

SqlDataProvider<...>.GetDataContext(transactionOptions: Transactions.TransactionOptions) : SqlDataProvider<...>.dataContext


<summary>Returns an instance of the SQL Provider</summary>
                              <param name='transactionOptions'>TransactionOptions for the transaction created on SubmitChanges.</param>

SqlDataProvider<...>.GetDataContext(connectionString: string) : SqlDataProvider<...>.dataContext


<summary>Returns an instance of the SQL Provider</summary>
                              <param name='connectionString'>The database connection string</param>

SqlDataProvider<...>.GetDataContext(connectionString: string, transactionOptions: Transactions.TransactionOptions) : SqlDataProvider<...>.dataContext


<summary>Returns an instance of the SQL Provider</summary>
                              <param name='connectionString'>The database connection string</param>
                              <param name='transactionOptions'>TransactionOptions for the transaction created on SubmitChanges.</param>

SqlDataProvider<...>.GetDataContext(connectionString: string, resolutionPath: string) : SqlDataProvider<...>.dataContext


<summary>Returns an instance of the SQL Provider</summary>
                              <param name='connectionString'>The database connection string</param>
                              <param name='resolutionPath'>The location to look for dynamically loaded assemblies containing database vendor specific connections and custom types</param>

SqlDataProvider<...>.GetDataContext(connectionString: string, resolutionPath: string, transactionOptions: Transactions.TransactionOptions) : SqlDataProvider<...>.dataContext


<summary>Returns an instance of the SQL Provider</summary>
                              <param name='connectionString'>The database connection string</param>
                              <param name='resolutionPath'>The location to look for dynamically loaded assemblies containing database vendor specific connections and custom types</param>
                              <param name='transactionOptions'>TransactionOptions for the transaction created on SubmitChanges.</param>
val query1 : System.Linq.IQueryable<SqlDataProvider<...>.dataContext.main.CustomersEntity>

Full name: Composable.query1
val query : Linq.QueryBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.query
val customers : SqlDataProvider<...>.dataContext.main.CustomersEntity
property SqlDataProvider<...>.dataContext.Main: SqlDataProvider<...>.dataContext.mainSchema
property SqlDataProvider<...>.dataContext.mainSchema.Customers: SqlDataProvider<...>.dataContext.mainSchema.main.Customers


<summary>The table Customers belonging to schema main</summary>
custom operation: where (bool)

Calls Linq.QueryBuilder.Where
property SqlDataProvider<...>.dataContext.main.CustomersEntity.ContactTitle: string
custom operation: select ('Result)

Calls Linq.QueryBuilder.Select
val query2 : SqlDataProvider<...>.dataContext.main.CustomersEntity []

Full name: Composable.query2
property SqlDataProvider<...>.dataContext.main.CustomersEntity.CompanyName: string
Multiple items
module Seq

from FSharp.Data.Sql

--------------------
module Seq

from Microsoft.FSharp.Collections
val toArray : source:seq<'T> -> 'T []

Full name: Microsoft.FSharp.Collections.Seq.toArray
val query : System.Linq.IQueryable<obj>

Full name: Composable.query
val x : obj
namespace System
namespace System.Linq
val companyNameFilter : inUse:bool -> queryable:'a -> ('b -> 'c)

Full name: Composable.companyNameFilter
val inUse : bool
val queryable : 'a
val queryable : ('b -> 'c)
Multiple items
type IQueryable =
  member ElementType : Type
  member Expression : Expression
  member Provider : IQueryProvider

Full name: System.Linq.IQueryable

--------------------
type IQueryable<'T> =

Full name: System.Linq.IQueryable<_>
val iq : 'b
val query1 : obj

Full name: Composable.query1
val query : IQueryable<obj>

Full name: Composable.query
val query2 : obj []

Full name: Composable.query2
val qry1 : obj
val qry2 : obj
val qry1 : obj

Full name: Composable.qry1
val qry2 : obj

Full name: Composable.qry2
val nestedQueryTest : obj []

Full name: Composable.nestedQueryTest
Fork me on GitHub