|
| 1 | +# Using SQLProvider SQL Server SSDT |
| 2 | + |
| 3 | + |
| 4 | +## Set up your database Server using Docker |
| 5 | + |
| 6 | +The easiest way to get a database running locally is using Docker. You can find the installer on their [website](https://www.docker.com/get-started/). Once docker is installed, use the following command to spin up a database server |
| 7 | + |
| 8 | +``` |
| 9 | +docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=<your password>" -p 1433:1433 -d mcr.microsoft.com/mssql/server:2022-latest |
| 10 | +``` |
| 11 | + |
| 12 | +## Creating a "SafeTodo" Database with Azure Data Studio |
| 13 | + |
| 14 | + |
| 15 | +### Connecting to a SQL Server Instance |
| 16 | +1) In the "Connections" tab, click the "New Connection" button |
| 17 | + |
| 18 | + |
| 19 | + |
| 20 | +2) Enter your connection details, leaving the "Database" dropdown set to `<Default>`. |
| 21 | + |
| 22 | + |
| 23 | + |
| 24 | +### Creating a new "SafeTodo" Database |
| 25 | +- Right click your server and choose "New Query" |
| 26 | +- Execute this script: |
| 27 | + |
| 28 | +```sql |
| 29 | +USE master |
| 30 | +GO |
| 31 | +IF NOT EXISTS ( |
| 32 | + SELECT name |
| 33 | + FROM sys.databases |
| 34 | + WHERE name = N'SafeTodo' |
| 35 | +) |
| 36 | + CREATE DATABASE [SafeTodo]; |
| 37 | +GO |
| 38 | +IF SERVERPROPERTY('ProductVersion') > '12' |
| 39 | + ALTER DATABASE [SafeTodo] SET QUERY_STORE=ON; |
| 40 | +GO |
| 41 | + |
| 42 | +``` |
| 43 | + |
| 44 | +- Right-click the "Databases" folder and choose "Refresh" to see the new database. |
| 45 | + |
| 46 | +_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._ |
| 47 | + |
| 48 | +### Create a "Todos" Table |
| 49 | +- Right-click on the **SafeTodo** database and choose "New Query" |
| 50 | +- Execute this script: |
| 51 | +``` sql |
| 52 | +CREATE TABLE [dbo].[Todos] |
| 53 | +( |
| 54 | + [Id] UNIQUEIDENTIFIER NOT NULL PRIMARY KEY, |
| 55 | + [Description] NVARCHAR(500) NOT NULL, |
| 56 | + [IsDone] BIT NOT NULL |
| 57 | +) |
| 58 | +``` |
| 59 | + |
| 60 | +## Creating an SSDT Project (.sqlproj) |
| 61 | +At this point, you should have a SAFE Stack solution and a minimal "SafeTodo" SQL Server database with a "Todos" table. |
| 62 | +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. |
| 63 | + |
| 64 | +1) Install the "SQL Database Projects" extension. |
| 65 | + |
| 66 | +2) Right click the SafeTodo database and choose "Create Project From Database" (this option is added by the "SQL Database Projects" extension) |
| 67 | + |
| 68 | + |
| 69 | + |
| 70 | +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. |
| 71 | + |
| 72 | + |
| 73 | + |
| 74 | +4) You should now be able to view your SQL Project by clicking the "Projects" tab in Azure Data Studio. |
| 75 | + |
| 76 | + |
| 77 | + |
| 78 | +5) Finally, right click the SafeTodoDB project and select "Build". This will create a .dacpac file which we will use in the next step. |
| 79 | + |
| 80 | + |
| 81 | +## Create a TodoRepository Using the new SSDT provider in SQLProvider |
| 82 | + |
| 83 | +### Installing SQLProvider from NuGet |
| 84 | + |
| 85 | +Install dependencies `SqlProvider` and `Microsoft.Data.SqlClient` |
| 86 | + |
| 87 | +``` |
| 88 | +dotnet paket add SqlProvider -p Server |
| 89 | +dotnet paket add Microsoft.Data.SqlClient -p Server |
| 90 | +``` |
| 91 | + |
| 92 | +### Initialize Type Provider |
| 93 | +Next, we will wire up our type provider to generate database types based on the compiled .dacpac file. |
| 94 | + |
| 95 | +1) In the Server project, create a new file, `Database.fs`. (this should be above `Server.fs`). |
| 96 | + |
| 97 | +```fsharp |
| 98 | +module Database |
| 99 | +open FSharp.Data.Sql |
| 100 | +
|
| 101 | +[<Literal>] |
| 102 | +let SsdtPath = __SOURCE_DIRECTORY__ + @"/../../ssdt/SafeTodoDB/bin/Debug/SafeTodoDB.dacpac" |
| 103 | +
|
| 104 | +type DB = |
| 105 | + SqlDataProvider< |
| 106 | + Common.DatabaseProviderTypes.MSSQLSERVER_SSDT, |
| 107 | + SsdtPath = SsdtPath, |
| 108 | + UseOptionTypes = Common.NullableColumnType.OPTION |
| 109 | + > |
| 110 | + |
| 111 | +//TO RELOAD SCHEMA: 1) uncomment the line below; 2) save; 3) recomment; 4) save again and wait. |
| 112 | +//DB.GetDataContext().``Design Time Commands``.ClearDatabaseSchemaCache |
| 113 | +
|
| 114 | +let createContext (connectionString: string) = |
| 115 | + DB.GetDataContext(connectionString) |
| 116 | +``` |
| 117 | + |
| 118 | +2) Create `TodoRepository.fs` below `Database.fs`. |
| 119 | + |
| 120 | +``` fsharp |
| 121 | +module TodoRepository |
| 122 | +open FSharp.Data.Sql |
| 123 | +open Database |
| 124 | +open Shared |
| 125 | +
|
| 126 | +/// Get all todos that have not been marked as "done". |
| 127 | +let getTodos (db: DB.dataContext) = |
| 128 | + query { |
| 129 | + for todo in db.Dbo.Todos do |
| 130 | + where (not todo.IsDone) |
| 131 | + select |
| 132 | + { Shared.Todo.Id = todo.Id |
| 133 | + Shared.Todo.Description = todo.Description } |
| 134 | + } |
| 135 | + |> List.executeQueryAsync |
| 136 | +
|
| 137 | +let addTodo (db: DB.dataContext) (todo: Shared.Todo) = |
| 138 | + async { |
| 139 | + let t = db.Dbo.Todos.Create() |
| 140 | + t.Id <- todo.Id |
| 141 | + t.Description <- todo.Description |
| 142 | + t.IsDone <- false |
| 143 | +
|
| 144 | + do! db.SubmitUpdatesAsync() |> Async.AwaitTask |
| 145 | + } |
| 146 | +``` |
| 147 | + |
| 148 | +3) Create `TodoController.fs` below `TodoRepository.fs`. |
| 149 | + |
| 150 | +``` fsharp |
| 151 | +module TodoController |
| 152 | +open Database |
| 153 | +open Shared |
| 154 | +
|
| 155 | +let getTodos (db: DB.dataContext) = |
| 156 | + TodoRepository.getTodos db |> Async.AwaitTask |
| 157 | +
|
| 158 | +let addTodo (db: DB.dataContext) (todo: Todo) = |
| 159 | + async { |
| 160 | + if Todo.isValid todo.Description then |
| 161 | + do! TodoRepository.addTodo db todo |
| 162 | + return todo |
| 163 | + else |
| 164 | + return failwith "Invalid todo" |
| 165 | + } |
| 166 | +``` |
| 167 | + |
| 168 | +4) Finally, replace the stubbed todosApi implementation in `Server.fs` with our type provided implementation. |
| 169 | + |
| 170 | +``` fsharp |
| 171 | +module Server |
| 172 | +
|
| 173 | +open Fable.Remoting.Server |
| 174 | +open Fable.Remoting.Giraffe |
| 175 | +open Saturn |
| 176 | +open System |
| 177 | +open Shared |
| 178 | +open Microsoft.AspNetCore.Http |
| 179 | +
|
| 180 | +let todosApi = |
| 181 | + let db = Database.createContext @"Data Source=localhost,1433;Database=SafeTodo;User ID=sa;Password=<your password>;TrustServerCertificate=True" |
| 182 | + { getTodos = fun () -> TodoController.getTodos db |
| 183 | + addTodo = TodoController.addTodo db } |
| 184 | +
|
| 185 | +let webApp = |
| 186 | + Remoting.createApi() |
| 187 | + |> Remoting.withRouteBuilder Route.builder |
| 188 | + |> Remoting.fromValue todosApi |
| 189 | + |> Remoting.withErrorHandler fableRemotingErrorHandler |
| 190 | + |> Remoting.buildHttpHandler |
| 191 | +
|
| 192 | +let app = |
| 193 | + application { |
| 194 | + use_router webApp |
| 195 | + memory_cache |
| 196 | + use_static "public" |
| 197 | + use_gzip |
| 198 | + } |
| 199 | +
|
| 200 | +run app |
| 201 | +``` |
| 202 | + |
| 203 | +## Run the App! |
| 204 | +From the VS Code terminal in the SafeTodo folder, launch the app (server and client): |
| 205 | + |
| 206 | +`dotnet run` |
| 207 | + |
| 208 | +You should now be able to add todos. |
| 209 | + |
| 210 | + |
| 211 | + |
| 212 | +## Deployment |
| 213 | +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. |
| 214 | + |
| 215 | +Here are the steps to accomplish this: |
| 216 | + |
| 217 | +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. |
| 218 | + |
| 219 | +``` |
| 220 | +<ItemGroup> |
| 221 | + <None Include="..\{relative path to SSDT project}\ssdt\SafeTodo\bin\$(Configuration)\SafeTodoDB.dacpac" Link="SafeTodoDB.dacpac"> |
| 222 | + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> |
| 223 | + </None> |
| 224 | + |
| 225 | + { other files... } |
| 226 | +</ItemGroup> |
| 227 | +``` |
| 228 | + |
| 229 | +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: |
| 230 | + |
| 231 | +```F# |
| 232 | +[<Literal>] |
| 233 | +#if DEBUG |
| 234 | +let SsdtPath = __SOURCE_DIRECTORY__ + @"/../../ssdt/SafeTodoDB/bin/Debug/SafeTodoDB.dacpac" |
| 235 | +#else |
| 236 | +let SsdtPath = __SOURCE_DIRECTORY__ + @"/../../ssdt/SafeTodoDB/bin/Release/SafeTodoDB.dacpac" |
| 237 | +#endif |
| 238 | +``` |
| 239 | + |
| 240 | +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). |
0 commit comments