Skip to content

Commit 448bd64

Browse files
committed
Add v5 recipe: 'Using SQLProvider SQL Server SSDT'
1 parent c3ee6ff commit 448bd64

File tree

2 files changed

+228
-0
lines changed

2 files changed

+228
-0
lines changed
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
# Using SQLProvider SQL Server SSDT
2+
3+
## Creating a "SafeTodo" Database with Azure Data Studio
4+
5+
### Connecting to a SQL Server Instance
6+
1) In the "Connections" tab, click the "New Connection" button
7+
8+
![image](../../img/sql-provider1.png)
9+
10+
2) Enter your connection details, leaving the "Database" dropdown set to `<Default>`.
11+
12+
![image](../../img/sql-provider2.png)
13+
14+
### Creating a new "SafeTodo" Database
15+
- Right click your server and choose "New Query"
16+
- Execute this script:
17+
18+
```sql
19+
USE master
20+
GO
21+
IF NOT EXISTS (
22+
SELECT name
23+
FROM sys.databases
24+
WHERE name = N'SafeTodo'
25+
)
26+
CREATE DATABASE [SafeTodo];
27+
GO
28+
IF SERVERPROPERTY('ProductVersion') > '12'
29+
ALTER DATABASE [SafeTodo] SET QUERY_STORE=ON;
30+
GO
31+
32+
```
33+
34+
- Right-click the "Databases" folder and choose "Refresh" to see the new database.
35+
36+
_NOTE: Alternatively, if you don't want to manually create the new database, you can install the "New Database" extension in Azure Data Studio which gives you a "New Database" option when right clicking the "Databases" folder._
37+
38+
### Create a "Todos" Table
39+
- Right-click on the **SafeTodo** database and choose "New Query"
40+
- Execute this script:
41+
``` sql
42+
CREATE TABLE [dbo].[Todos]
43+
(
44+
[Id] UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
45+
[Description] NVARCHAR(500) NOT NULL,
46+
[IsDone] BIT NOT NULL
47+
)
48+
```
49+
50+
## Creating an SSDT Project (.sqlproj)
51+
At this point, you should have a SAFE Stack solution and a minimal "SafeTodo" SQL Server database with a "Todos" table.
52+
Next, we will use Azure Data Studio with the "SQL Database Projects" extension to create a new SSDT (SQL Server Data Tools) .sqlproj that will live in our SAFE Stack .sln.
53+
54+
1) Install the "SQL Database Projects" extension.
55+
56+
2) Right click the SafeTodo database and choose "Create Project From Database" (this option is added by the "SQL Database Projects" extension)
57+
58+
![image](../../img/sql-provider3.png)
59+
60+
3) Configure a path within your SAFE Stack solution folder and a project name and then click "Create". NOTE: If you choose to create an "ssdt" subfolder as I did, you will need to manually create this subfolder first.
61+
62+
![image](../../img/sql-provider4.png)
63+
64+
4) You should now be able to view your SQL Project by clicking the "Projects" tab in Azure Data Studio.
65+
66+
![image](../../img/sql-provider5.png)
67+
68+
5) Finally, right click the SafeTodoDB project and select "Build". This will create a .dacpac file which we will use in the next step.
69+
70+
71+
## Create a TodoRepository Using the new SSDT provider in SQLProvider
72+
73+
### Installing SQLProvider from NuGet
74+
- Add the following NuGet packages to both the `paket.dependencies` in the solution root and the `paket.references` in the src/Server folder:
75+
```
76+
SQLProvider
77+
System.Data.SqlClient
78+
```
79+
### Initialize Type Provider
80+
Next, we will wire up our type provider to generate database types based on the compiled .dacpac file.
81+
82+
1) In the Server project, create a new file, `Database.fs`. (this should be above `Server.fs`).
83+
84+
```fsharp
85+
module Database
86+
open FSharp.Data.Sql
87+
88+
[<Literal>]
89+
let SsdtPath = __SOURCE_DIRECTORY__ + @"/../../ssdt/SafeTodoDB/bin/Debug/SafeTodoDB.dacpac"
90+
91+
// TO RELOAD SCHEMA: 1) uncomment the line below; 2) save; 3) recomment; 4) save again and wait.
92+
//DB.GetDataContext().``Design Time Commands``.ClearDatabaseSchemaCache
93+
94+
type DB =
95+
SqlDataProvider<
96+
Common.DatabaseProviderTypes.MSSQLSERVER_SSDT,
97+
SsdtPath = SsdtPath,
98+
UseOptionTypes = Common.NullableColumnType.OPTION
99+
>
100+
101+
let createContext (connectionString: string) =
102+
DB.GetDataContext(connectionString)
103+
```
104+
105+
2) Create `TodoRepository.fs` below `Database.fs`.
106+
107+
``` fsharp
108+
module TodoRepository
109+
open FSharp.Data.Sql
110+
open Database
111+
open Shared
112+
113+
/// Get all todos that have not been marked as "done".
114+
let getTodos (db: DB.dataContext) =
115+
query {
116+
for todo in db.Dbo.Todos do
117+
where (not todo.IsDone)
118+
select
119+
{ Shared.Todo.Id = todo.Id
120+
Shared.Todo.Description = todo.Description }
121+
}
122+
|> List.executeQueryAsync
123+
124+
let addTodo (db: DB.dataContext) (todo: Shared.Todo) =
125+
async {
126+
let t = db.Dbo.Todos.Create()
127+
t.Id <- todo.Id
128+
t.Description <- todo.Description
129+
t.IsDone <- false
130+
131+
do! db.SubmitUpdatesAsync() |> Async.AwaitTask
132+
}
133+
```
134+
135+
3) Create `TodoController.fs` below `TodoRepository.fs`.
136+
137+
``` fsharp
138+
module TodoController
139+
open Database
140+
open Shared
141+
142+
let getTodos (db: DB.dataContext) =
143+
TodoRepository.getTodos db |> Async.AwaitTask
144+
145+
let addTodo (db: DB.dataContext) (todo: Todo) =
146+
async {
147+
if Todo.isValid todo.Description then
148+
do! TodoRepository.addTodo db todo
149+
return todo
150+
else
151+
return failwith "Invalid todo"
152+
}
153+
```
154+
155+
4) Finally, replace the stubbed todosApi implementation in `Server.fs` with our type provided implementation.
156+
157+
``` fsharp
158+
module Server
159+
160+
open Fable.Remoting.Server
161+
open Fable.Remoting.Giraffe
162+
open Saturn
163+
open System
164+
open Shared
165+
open Microsoft.AspNetCore.Http
166+
167+
let todosApi =
168+
let db = Database.createContext @"Data Source=.\SQLEXPRESS;Initial Catalog=SafeTodo;Integrated Security=SSPI;"
169+
{ getTodos = fun () -> TodoController.getTodos db
170+
addTodo = TodoController.addTodo db }
171+
172+
let webApp =
173+
Remoting.createApi()
174+
|> Remoting.withRouteBuilder Route.builder
175+
|> Remoting.fromValue todosApi
176+
|> Remoting.withErrorHandler fableRemotingErrorHandler
177+
|> Remoting.buildHttpHandler
178+
179+
let app =
180+
application {
181+
use_router webApp
182+
memory_cache
183+
use_static "public"
184+
use_gzip
185+
}
186+
187+
run app
188+
```
189+
190+
## Run the App!
191+
From the VS Code terminal in the SafeTodo folder, launch the app (server and client):
192+
193+
`dotnet run`
194+
195+
You should now be able to add todos.
196+
197+
![image](https://user-images.githubusercontent.com/1030435/111055048-044f2080-8440-11eb-9efc-ae454ff071c4.png)
198+
199+
## Deployment
200+
When creating a Release build for deployment, it is important to note that SQLProvider SSDT expects that the .dacpac file will be copied to the deployed Server project bin folder.
201+
202+
Here are the steps to accomplish this:
203+
204+
1) Modify your Server.fsproj to include the .dacpac file with "CopyToOutputDirectory" to ensure that the .dacpac file will always exist in the Server project bin folder.
205+
206+
```
207+
<ItemGroup>
208+
<None Include="..\{relative path to SSDT project}\ssdt\SafeTodo\bin\$(Configuration)\SafeTodoDB.dacpac" Link="SafeTodoDB.dacpac">
209+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
210+
</None>
211+
212+
{ other files... }
213+
</ItemGroup>
214+
```
215+
216+
2) In your Server.Database.fs file, you should also modify the SsdtPath binding so that it can build the project in either Debug or Release mode:
217+
218+
```F#
219+
[<Literal>]
220+
#if DEBUG
221+
let SsdtPath = __SOURCE_DIRECTORY__ + @"/../../ssdt/SafeTodoDB/bin/Debug/SafeTodoDB.dacpac"
222+
#else
223+
let SsdtPath = __SOURCE_DIRECTORY__ + @"/../../ssdt/SafeTodoDB/bin/Release/SafeTodoDB.dacpac"
224+
#endif
225+
```
226+
227+
NOTE: This assumes that your SSDT .sqlproj will be built in Release mode (you can build it manually, or use a FAKE build script to handle this).

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ nav:
8888
- Add routing with separate models per page: "recipes/ui/add-routing-with-separate-models.md"
8989
- Storage:
9090
- Quickly add a database: "recipes/storage/use-litedb.md"
91+
- Create a data module using SQLProvider SQL Server SSDT: "recipes/storage/use-sqlprovider-ssdt.md"
9192
- JavaScript:
9293
- Import a JavaScript module: "recipes/javascript/import-js-module.md"
9394
- Add Support for a Third Party React Library: "recipes/javascript/third-party-react-package.md"

0 commit comments

Comments
 (0)