Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
JordanMarr committed Apr 21, 2024
2 parents 1edf087 + 11ed0a8 commit 6b3a805
Showing 1 changed file with 75 additions and 45 deletions.
120 changes: 75 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,16 @@ module dbo =
ErrorProcedure: Option<string>
ErrorLine: Option<int> }
let ErrorLog = table<ErrorLog>
type BuildVersion =
{ SystemInformationID: byte
``Database Version``: string
VersionDate: System.DateTime
ModifiedDate: System.DateTime }
let BuildVersion = table<BuildVersion>
module SalesLT =
type Address =
{ City: string
Expand All @@ -219,6 +223,8 @@ module SalesLT =
AddressLine1: string
AddressLine2: Option<string> }
let Address = table<Address>
type Customer =
{ LastName: string
PasswordHash: string
Expand All @@ -235,6 +241,8 @@ module SalesLT =
SalesPerson: Option<string>
EmailAddress: Option<string>
Phone: Option<string> }
let Customer = table<Customer>
// etc...
```
Expand Down Expand Up @@ -307,16 +315,77 @@ select {

### Select Builder

There are three select builders:
* `selectTask` - creates a self-executing query that returns a Task<'T> of query results
* `selectAsync` - creates a self-executing query that returns an Async<'T> of query results
* `select` - creates a query (this is mostly used for creating subqueries)
There are three select builders that you can use to create select queries:
#### `select` - creates a query (or subquery).

```F#
let getErrorNumbers () =
task {
use ctx = openQueryContext()
return!
select {
for e in dbo.ErrorLog do
select e.ErrorNumber
}
|> ctx.ReadAsync HydraReader.Read
}
```

#### `selectTask` - creates a _self-executing_ query that returns a Task<'T> of query results.

```F#
let getErrorNumbers () =
selectTask HydraReader.Read (Create openQueryContext) {
for e in dbo.ErrorLog do
select e.ErrorNumber
}
```

#### `selectAsync` - creates a _self-executing_ query that returns an Async<'T> of query results.

```F#
let getErrorNumbers () =
selectAsync HydraReader.Read (Create openQueryContext) {
for e in dbo.ErrorLog do
select e.ErrorNumber
}
```

Which one should you use?
* The `select` computation expression is simple and straightforward to use, but it should _always_ be wrapped within either an `async` or `task` computation expression, which results in more lines of code.
* The `selectTask` and `selectAsync` computation expressions are self-executing and do not require being wrapped within an `async` or `task` computation expression. They also take a `QueryType` argument that lets you control whether a `QueryContext` is `Shared` or `Created`.
* The `selectTask` and `selectAsync` computation expressions builders also provide the following custom operations that are applied to the queried results (after the query data is returned):
* `toArray`
* `toList`
* `mapArray`
* `mapList`
* `tryHead`
* `head`

All three select query builders must be passed the generated `HydraReader.Read` static method (which is generated by `SqlHydra.Cli` when the ["Generate HydraReader?"](#data-readers) option is selected).
For these reasons, I prefer using the `selectTask` and `selectAsync` CEs.
However, if the `selectTask` and `selectAsync` CEs seem intimidating or too complex for your taste, then feel free to use the `select` CE.

The `selectTask` and `selectAsync` builders must also be passed a `QueryType` which is a discriminated union that allows the user to specify the scope of the `QueryContext` (which manages the `DbConnection` and executes the various types of queries). `QueryType` allows for the following options:
Note that all three select query builders require the generated `HydraReader.Read` static method (which is generated by `SqlHydra.Cli` when the ["Generate HydraReader?"](#data-readers) option is selected).

The `selectTask` and `selectAsync` builders also require a `QueryType` argument which is a discriminated union that allows the user to specify the scope of the `QueryContext` (which manages the `DbConnection` and executes the various types of queries). `QueryType` allows for the following options:
* `QueryType.Create of unit -> QueryContext` - this takes a function that returns a new `QueryContext`. This option will create its own `QueryContext` and `DbConnection` automatically, execute the query and then dispose them. This is very useful because it allows you to create a simple data function that executes a query without the need of manually instantiating the `QueryContext`, executing the query and then disposing (which also necessitates wrapping everything in a `task` or `async` block to ensure that the connection isn't prematurely disposed). The end result is a much cleaner data function that doesn't need to be wrapped in a `task` or `async` block!
* `QueryType.Shared of QueryContext` - this takes an already instantiated `QueryContext` and uses it to execute the query. In this case, the builder will ensure that the connection is open before executing the query, but it will not try to close or dispose when it is done. This is useful for when you need to call multiple queries within a `task` or `async` block with a single shared `QueryContext`.

### Creating a Custom `selectAsync` or `selectTask` Builder
If the redundancy of passing the generated `HydraReader.Read` static method into the `selectAsync` and `selectTask` builders bothers you, you can easily create your builder that has it baked-in:

```F#
let selectTask' ct = selectTask HydraReader.Read ct
// Usage:
let! distinctCustomerNames =
selectTask' (Create openContext) {
for c in SalesLT.Customer do
select (c.FirstName, c.LastName)
distinct
}
```


Selecting city and state columns only:
Expand Down Expand Up @@ -646,45 +715,6 @@ let getCities () =
}
```

### Using the `selectAsync` and `selectTask` Builders
The new `selectAsync` and `selectTask` builders should generally be prefered over the older `select` builder (with the exception of creating subqueries, which must be done using the `select` builder) because they provide several advantages:

#### They are self-executing
The new `selectAsync` and `selectTask` builders will execute the query automatically, whereas the old `select` builder creates a query that must be manually passed into a `QueryContext` execution method.

#### They offer more explicit control over the `QueryContext` and connection handling
The new `selectAsync` and `selectTask` builders must be initialized with with a `ContextType` discriminated union value that can either be `Shared` or `Create`.
Passing in a `Shared` context will run the query with an already existing `QueryContext`, whereas `Create` will create a new context and dispose it automatically after executing the query.

#### They make it possible to create a query function that is not wrapped in an `async` or `task` block.
One problem with the `select` builder is that the `QueryContext` generally had to be initialized within the `task` block to ensure that it was not disposed while the task was running asynchronously. Having the ability to initilize a `selectAsync` or `selectTask` builder with `Create` makes it a completely self-contained query which can exist by itself in a function without being wrapped in a `task` block.
**The new `selectAsync` and `selectTask` builders have the following new custom operations that are applied to the queried results:**

* `toArray`
* `toList`
* `mapArray`
* `mapList`
These new operations are designed to make the new select builders completely self-contained by removing the need to pipe the results.

#### They are Cleaner
Removing the need to pipeline the query builder into a `QueryContext` makes the code a bit more tidy.

### Creating a Custom `selectAsync` or `selectTask` Builder
If the redundancy of passing the generated `HydraReader.Read` static method into the `selectAsync` and `selectTask` builders bothers you, you can easily create your builder that has it baked-in:

```F#
let selectTask' ct = selectTask HydraReader.Read ct
// Usage:
let! distinctCustomerNames =
selectTask' (Create openContext) {
for c in SalesLT.Customer do
select (c.FirstName, c.LastName)
distinct
}
```

### Insert Builder

#### Simple Inserts
Expand Down

0 comments on commit 6b3a805

Please sign in to comment.