diff --git a/core/general.html b/core/general.html index 7f122bf8..ba37db65 100644 --- a/core/general.html +++ b/core/general.html @@ -389,7 +389,7 @@

Basic Queryin

Individuals

The SQL provider has the ability via intellisense to navigate the actual data held within a table or view. You can then bind that data as an entity to a value.

-
let BERGS = ctx.Main.Customers.Individuals.BERGS
+
let BERGS = ctx.Main.Customers.Individuals.BERGS
 

Every table and view has an Individuals property. When you press dot on this property, intellisense will display a list of the data in that table, @@ -402,7 +402,7 @@

Individuals

-
let christina = ctx.Main.Customers.Individuals.``As ContactName``.``BERGS, Christina Berglund``
+
let christina = ctx.Main.Customers.Individuals.``As ContactName``.``BERGS, Christina Berglund``
 

DataContext

You should create and use one data context as long as it has the parameters you need. @@ -423,7 +423,7 @@

DataContext

A. Once SQLProvider gets a "mental model" of your database (the schema), that is what is used for any intellisense/completion suggestions for the rest of your IDE session.

This is a fantastic feature, because it means that you're not assaulting your database with a 
-new "What are you like?" query on EVERY SINGLE KEYSTROKE. 
+new "What are you like?" query on EVERY SINGLE KEYSTROKE. 
 
 But what if the database changes? SQLProvider will NOT see your change because it's source of truth is
 that locally cached schema snapshot it took right when it started, and that snapshot will persist until
@@ -436,7 +436,7 @@ 

DataContext

2. Forced clearing of the local database schema cache. If SQLProvider is currently able to communicate with the database, you can force the local cache to clear, to be invalidated and refreshed by - by using what are called `Design Time Commands`, specifically the + by using what are called `Design Time Commands`, specifically the `ClearDatabaseSchemaCache` method. You're probably thinking: "Ok, fine, that sounds good! How do I do that though?" @@ -640,11 +640,10 @@

val ordersQuery: (string * System.DateTime * string) array
property SqlDataProvider<...>.dataContext.main.OrdersEntity.OrderDate: System.DateTime with get, set
<summary>OrderDate: datetime</summary>
property SqlDataProvider<...>.dataContext.main.OrdersEntity.ShipAddress: string with get, set
<summary>ShipAddress: nvarchar(60)</summary>
-
val BERGS: SqlDataProvider<...>.dataContext.main.CustomersEntity
+
val BERGS: obj
property SqlDataProvider<...>.dataContext.mainSchema.main.Customers.Individuals: SqlDataProvider<...>.dataContext.main.Customers.Individuals with get
<summary>Get individual items from the table. Requires single primary key.</summary>
-
property SqlDataProvider<...>.dataContext.main.Customers.Individuals.BERGS: SqlDataProvider<...>.dataContext.main.CustomersEntity with get
-
val christina: SqlDataProvider<...>.dataContext.main.CustomersEntity
-
val using: resource: 'T -> action: ('T -> 'U) -> 'U (requires 'T :> System.IDisposable)
+
val christina: obj
+
val using: resource: 'T -> action: ('T -> 'U) -> 'U (requires 'T :> System.IDisposable)
diff --git a/core/individuals.html b/core/individuals.html index 50454bf8..2fb57678 100644 --- a/core/individuals.html +++ b/core/individuals.html @@ -266,13 +266,13 @@

Individuals

let customers = ctx.Main.Customers

Get individual customer row by primary key value

-
customers.Individuals.COMMI
+
customers.Individuals.COMMI
 

Get individual customer row using address

-
customers.Individuals.``As ContactName``.``COMMI, Pedro Afonso``
+
customers.Individuals.``As ContactName``.``COMMI, Pedro Afonso``
 

Get individual customer row using address

-
customers.Individuals.``As Address``.``CONSH, Berkeley Gardens 12  Brewery``
+
customers.Individuals.``As Address``.``CONSH, Berkeley Gardens 12  Brewery``
 
Multiple items
type LiteralAttribute = @@ -336,7 +336,6 @@

Individuals

property SqlDataProvider<...>.dataContext.Main: SqlDataProvider<...>.dataContext.mainSchema with get
property SqlDataProvider<...>.dataContext.mainSchema.Customers: SqlDataProvider<...>.dataContext.mainSchema.main.Customers with get
<summary> The table Customers belonging to schema main</summary>
property SqlDataProvider<...>.dataContext.mainSchema.main.Customers.Individuals: SqlDataProvider<...>.dataContext.main.Customers.Individuals with get
<summary>Get individual items from the table. Requires single primary key.</summary>
-
property SqlDataProvider<...>.dataContext.main.Customers.Individuals.COMMI: SqlDataProvider<...>.dataContext.main.CustomersEntity with get
diff --git a/core/querying.html b/core/querying.html index b64ac04f..0ff99d80 100644 --- a/core/querying.html +++ b/core/querying.html @@ -1349,7 +1349,7 @@

Expressions

These operators perform no specific function in the code itself, rather they are placeholders replaced by their database-specific server-side operations. Their utility is in forcing the compiler to check against the correct types.

-
let bergs = ctx.Main.Customers.Individuals.BERGS
+
let bergs = ctx.Main.Customers.Individuals.BERGS
 

Operators

You can find some custom operators using FSharp.Data.Sql:

@@ -1367,38 +1367,38 @@

let result = - query { + query { for order in ctx.Main.Orders do where ( order.ShippedDate.IsSome && order.ShippedDate.Value.Year = 2015) select (order.OrderId, order.Freight) - } |> Array.executeQueryAsync + } |> Array.executeQueryAsync

Using booleans and simple variables (from outside a scope) in where-clauses

This is how you make your code easier to read when you have multiple code paths. SQLProvider will optimize the SQL-clause before sending it to the database, so it will still be simple.

Consider how clean is this source-code compared to other with similar logic:

-
open System.Linq
-let getOrders(futureOrders:bool, shipYears:int list) =
+
open System.Linq
+let getOrders(futureOrders:bool, shipYears:int list) =
 
-    let today = DateTime.UtcNow.Date
-    let pastOrders = not futureOrders
-    let noYearFilter = shipYears.IsEmpty
+    let today = DateTime.UtcNow.Date
+    let pastOrders = not futureOrders
+    let noYearFilter = shipYears.IsEmpty
 
-    let result =
-        query {
-            for order in ctx.Main.Orders do
-            where ( 
-                (noYearFilter || shipYears.Contains(order.ShippedDate.Year))
+    let result =
+        query {
+            for order in ctx.Main.Orders do
+            where ( 
+                (noYearFilter || shipYears.Contains(order.ShippedDate.Year))
                 &&
-                ((futureOrders && order.OrderDate > today) ||
-                 (pastOrders   && order.OrderDate <= today))
+                ((futureOrders && order.OrderDate > today) ||
+                 (pastOrders   && order.OrderDate <= today))
             ) 
-            select (order.OrderId, order.Freight)
-        } |> Array.executeQueryAsync
-    result
+            select (order.OrderId, order.Freight)
+        } |> Array.executeQueryAsync
+    result
 
Technical details @@ -1415,52 +1415,52 @@

// ContactName, ContactTitle, Country, // CustomerID, Fax, Phone, PostalCode, // Region FROM main.Customers -let selectFullObject = - query { - for customer in ctx.Main.Customers do - select customer - } |> Seq.tryHeadAsync +let selectFullObject = + query { + for customer in ctx.Main.Customers do + select customer + } |> Seq.tryHeadAsync // Select only two fields, basically: // SELECT TOP 1 Address, City FROM main.Customers -let selectSmallObject = - query { - for customer in ctx.Main.Customers do - select (customer.Address, customer.City) - } |> Seq.tryHeadAsync +let selectSmallObject = + query { + for customer in ctx.Main.Customers do + select (customer.Address, customer.City) + } |> Seq.tryHeadAsync

If you still want the whole objects and return those to a client as untyped records, you can use ColumnValues |> Map.ofSeq:

-
let someQuery =
-    query {
-        for customer in ctx.Main.Customers do
+
let someQuery =
+    query {
+        for customer in ctx.Main.Customers do
         //where(...)
-        select customer
-    } |> Seq.toArray
+        select customer
+    } |> Seq.toArray
 
-someQuery |> Array.map(fun c -> c.ColumnValues |> Map.ofSeq)
+someQuery |> Array.map(fun c -> c.ColumnValues |> Map.ofSeq)
 

F# Map values are accessed like this: myItem.["City"]

Using code logic in select-clause

Don't be scared to insert non-Sql syntax to select-clauses. They will be parsed business-logic side!

-
let fetchOrders customerZone =
-    let currentDate = DateTime.UtcNow.Date
-    query {
-        for order in ctx.Main.Orders do
+
let fetchOrders customerZone =
+    let currentDate = DateTime.UtcNow.Date
+    query {
+        for order in ctx.Main.Orders do
         // where(...)
-        select {
-            OrderId = order.OrderId
+        select {
+            OrderId = order.OrderId
             Timezone = 
-                parseTimezoneFunction(order.ShipRegion, order.ShippedDate, customerZone);
+                parseTimezoneFunction(order.ShipRegion, order.ShippedDate, customerZone);
             Status = 
-                if order.ShippedDate > currentDate then "Shipped"
-                elif order.OrderDate > currentDate then "Ordered"
-                elif order.RequiredDate > currentDate then "Late"
+                if order.ShippedDate > currentDate then "Shipped"
+                elif order.OrderDate > currentDate then "Ordered"
+                elif order.RequiredDate > currentDate then "Late"
                 else "Waiting"
             OrderRows = [||];
         }
-    } |> Seq.toArray
+    } |> Seq.toArray
 

You can't have a let inside a select, but you can have custom function calls like parseTimezoneFunction here. Just be careful, they are executed for each result item separately. @@ -1471,31 +1471,31 @@

In the previous example we fetched OrderRows as empty array. Now we populate those with one query in immutable way:

-
let orders = fetchOrders 123
+
let orders = fetchOrders 123
 
-let orderIds = 
-    orders 
-    |> Array.map(fun o -> o.OrderId) 
-    |> Array.distinct
+let orderIds = 
+    orders 
+    |> Array.map(fun o -> o.OrderId) 
+    |> Array.distinct
     
 // Fetch all rows with one query
-let subItems =
-    query {
-        for row in ctx.Main.OrderDetails do
-        where (orderIds.Contains(row.OrderId))
-        select (row.OrderId, row.ProductId, row.Quantity)
-    } |> Seq.toArray
+let subItems =
+    query {
+        for row in ctx.Main.OrderDetails do
+        where (orderIds.Contains(row.OrderId))
+        select (row.OrderId, row.ProductId, row.Quantity)
+    } |> Seq.toArray
     
-let ordersWithDetails =
-    orders 
-    |> Array.map(fun order ->
-        {order with 
+let ordersWithDetails =
+    orders 
+    |> Array.map(fun order ->
+        {order with 
             // Match the corresponding sub items
             // to a parent item's colleciton:
             OrderRows = 
-                subItems 
-                |> Array.filter(fun (orderId,_,_) -> 
-                    order.OrderId = orderId)
+                subItems 
+                |> Array.filter(fun (orderId,_,_) -> 
+                    order.OrderId = orderId)
                 })
 

How to deal with large IN-queries?

@@ -1504,57 +1504,57 @@

Chunk your collection:

F# has built-in chunkBySize function!

-
let chunked = orderIds |> Array.chunkBySize 100
+
let chunked = orderIds |> Array.chunkBySize 100
 
-for chunk in chunked do
-    let all =
-        query {
-            for row in ctx.Main.OrderDetails do
-            where (chunk.Contains(row.OrderId))
-            select (row)
-        } |> Seq.toArray
+for chunk in chunked do
+    let all =
+        query {
+            for row in ctx.Main.OrderDetails do
+            where (chunk.Contains(row.OrderId))
+            select (row)
+        } |> Seq.toArray
 
-    all |> Array.iter(fun row -> row.Discount <- 0.1)
-    ctx.SubmitUpdates()
+    all |> Array.iter(fun row -> row.Discount <- 0.1)
+    ctx.SubmitUpdates()
 

Creating a nested query

By leaving the last |> Seq.toArray away from your main query you create a lazy IQueryable<...>-query. Which means your IN-objects are not fetched from the database, but is actually a nested query.

-
let nestedOrders =
-    query {
-        for order in ctx.Main.Orders do
+
let nestedOrders =
+    query {
+        for order in ctx.Main.Orders do
         // where(...)
-        select (order.OrderId)
+        select (order.OrderId)
     } 
 
-let subItemsAll =
-    query {
-        for row in ctx.Main.OrderDetails do
-        where (nestedOrders.Contains(row.OrderId))
-        select (row.OrderId, row.ProductId, row.Quantity)
-    } |> Seq.toArray
+let subItemsAll =
+    query {
+        for row in ctx.Main.OrderDetails do
+        where (nestedOrders.Contains(row.OrderId))
+        select (row.OrderId, row.ProductId, row.Quantity)
+    } |> Seq.toArray
 
 // similar as previous fetchOrders
-let fetchOrders2 customerZone =
-    let currentDate = DateTime.UtcNow.Date
-    query {
-        for order in ctx.Main.Orders do
+let fetchOrders2 customerZone =
+    let currentDate = DateTime.UtcNow.Date
+    query {
+        for order in ctx.Main.Orders do
         // where(...)
-        select {
-            OrderId = order.OrderId
+        select {
+            OrderId = order.OrderId
             Timezone = 
-                parseTimezoneFunction(order.ShipRegion, order.ShippedDate, customerZone);
+                parseTimezoneFunction(order.ShipRegion, order.ShippedDate, customerZone);
             Status = 
-                if order.ShippedDate > currentDate then "Shipped"
-                elif order.OrderDate > currentDate then "Ordered"
-                elif order.RequiredDate > currentDate then "Late"
+                if order.ShippedDate > currentDate then "Shipped"
+                elif order.OrderDate > currentDate then "Ordered"
+                elif order.RequiredDate > currentDate then "Late"
                 else "Waiting"
             OrderRows = 
-                subItemsAll |> (Array.filter(fun (orderId,_,_) -> 
-                    order.OrderId = orderId));
+                subItemsAll |> (Array.filter(fun (orderId,_,_) -> 
+                    order.OrderId = orderId));
         }
-    } |> Seq.toArray
+    } |> Seq.toArray
 

That way order hit count doesn't matter as the database is taking care of it.

Group-by and more complex query scenarios

@@ -1562,23 +1562,23 @@

let freightsByCity = - query { - for o in ctx.Main.Orders do +
let freightsByCity =
+    query {
+        for o in ctx.Main.Orders do
         //where (...)
-        groupBy o.ShipCity into cites
-        select (cites.Key, cites.Sum(fun order -> order.Freight))
-    } |> Array.executeQueryAsync
+        groupBy o.ShipCity into cites
+        select (cites.Key, cites.Sum(fun order -> order.Freight))
+    } |> Array.executeQueryAsync
 

Group-by is support is limited, mostly for single tables only. F# Linq query syntax doesnt support doing select count(1), sum(UnitPrice) from Products but you can group by a constant to get that:

-
let qry = 
-    query {
-        for p in ctx.Main.Products do
-        groupBy 1 into g
-        select (g.Count(), g.Sum(fun p -> p.UnitPrice))
-    } |> Seq.head
+
let qry = 
+    query {
+        for p in ctx.Main.Products do
+        groupBy 1 into g
+        select (g.Count(), g.Sum(fun p -> p.UnitPrice))
+    } |> Seq.head
 

For more info see: