Skip to content

Commit c8148eb

Browse files
Merge pull request #357 from cmeeren/improve-dynamic-db-docs
Improve dynamic DB docs
2 parents b3896d5 + ba659d7 commit c8148eb

File tree

1 file changed

+94
-48
lines changed

1 file changed

+94
-48
lines changed

docs/content/dynamic local db.fsx

Lines changed: 94 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,89 +6,135 @@
66
Dynamic creation of offline MDF
77
===============================
88
9-
Sometimes you don't want to have to be online just to compile your programs. With FSharp.Data.SqlClient you can use a local
10-
.MDF file as the compile time connection string, and then change your connection string at runtime when you deploy your application.
9+
Sometimes you don't want to have to be online just to compile your programs, or
10+
you might not have access to your production database from your CI systems. With
11+
FSharp.Data.SqlClient you can use a local .MDF file as the compile-time
12+
connection string, and then use a different connection string when you deploy
13+
your application.
14+
15+
A connection string to a local .MDF file might look like this:
1116
*)
1217

1318
open FSharp.Data
1419

1520
[<Literal>]
16-
let connectionString = @"Data Source=(LocalDB)\v12.0;AttachDbFilename=C:\git\Project1\Database1.mdf;Integrated Security=True;Connect Timeout=10"
21+
let compileConnectionString =
22+
@"Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=C:\git\Project1\Database1.mdf;Integrated Security=True"
1723

1824
(**
19-
However, binary files like this are difficult to diff/merge when working with multiple developers. For this reason wouldn't it be nice
20-
to store your schema in a plain text file, and have it dynamically create the MDF file for compile time?
25+
However, binary files like this are difficult to diff/merge when working with
26+
multiple developers, so you might not want to check them in. Wouldn't it be nice
27+
to store your schema in a plain text file, and have it dynamically create the
28+
MDF file for compile time?
2129
2230
Well the following scripts can do that for your project.
2331
24-
First create a file called `createdb.ps1`:
32+
First create a file called `createDb.ps1` and place it in an `SQL` subfolder in
33+
your project (you can place it in the project root to, if you want):
2534
26-
# this is the name that Fsharp.Data.SqlClient TypeProvider expects it to be at build time
27-
$new_db_name = "Database1"
35+
param(
36+
[Parameter(Mandatory=$true)][String]$DbName,
37+
[Parameter(Mandatory=$true)][String]$DbScript
38+
)
2839
2940
$detach_db_sql = @"
30-
use master;
31-
GO
32-
EXEC sp_detach_db @dbname = N'$new_db_name';
33-
GO
41+
IF (SELECT COUNT(*) FROM sys.databases WHERE name = '$DbName') > 0
42+
EXEC sp_detach_db @dbname = N'$DbName'
3443
"@
3544
3645
$detach_db_sql | Out-File "detachdb.sql"
37-
sqlcmd -S "(localdb)\v11.0" -i detachdb.sql
38-
Remove-Item .\detachdb.sql
46+
sqlcmd -S "(LocalDB)\MSSQLLocalDB" -i "detachdb.sql"
47+
Remove-Item "detachdb.sql"
3948
40-
Remove-Item "$new_db_name.mdf"
41-
Remove-Item "$new_db_name.ldf"
49+
if (Test-Path "$PSScriptRoot\$DbName.mdf") { Remove-Item "$PSScriptRoot\$DbName.mdf" }
50+
if (Test-Path "$PSScriptRoot\$DbName.ldf") { Remove-Item "$PSScriptRoot\$DbName.ldf" }
4251
4352
$create_db_sql = @"
44-
USE master ;
45-
GO
46-
CREATE DATABASE $new_db_name
47-
ON
48-
( NAME = Sales_dat,
49-
FILENAME = '$PSScriptRoot\$new_db_name.mdf',
50-
SIZE = 10,
51-
MAXSIZE = 50,
52-
FILEGROWTH = 5 )
53-
LOG ON
54-
( NAME = Sales_log,
55-
FILENAME = '$PSScriptRoot\$new_db_name.ldf',
56-
SIZE = 5MB,
57-
MAXSIZE = 25MB,
58-
FILEGROWTH = 5MB ) ;
59-
GO
53+
CREATE DATABASE $DbName
54+
ON (
55+
NAME = ${DbName}_dat,
56+
FILENAME = '$PSScriptRoot\$DbName.mdf'
57+
)
58+
LOG ON (
59+
NAME = ${DbName}_log,
60+
FILENAME = '$PSScriptRoot\$DbName.ldf'
61+
)
6062
"@
6163
6264
$create_db_sql | Out-File "createdb.sql"
63-
sqlcmd -S "(localdb)\v11.0" -i createdb.sql
64-
Remove-Item .\createdb.sql
65+
sqlcmd -S "(LocalDB)\MSSQLLocalDB" -i "createdb.sql"
66+
Remove-Item "createdb.sql"
6567
66-
sqlcmd -S "(localdb)\v11.0" -i schema.sql
68+
sqlcmd -S "(LocalDB)\MSSQLLocalDB" -i "$DbScript"
6769
6870
$detach_db_sql | Out-File "detachdb.sql"
69-
sqlcmd -S "(localdb)\v11.0" -i detachdb.sql
70-
Remove-Item .\detachdb.sql
71+
sqlcmd -S "(LocalDB)\MSSQLLocalDB" -i "detachdb.sql"
72+
Remove-Item "detachdb.sql"
7173
7274
Then change your connection string to look like this
7375
*)
7476

7577
[<Literal>]
76-
let connectionStringForCompileTime = @"Data Source=(LocalDB)\v12.0;AttachDbFilename=" + __SOURCE_DIRECTORY__ + @"\Database1.mdf;Integrated Security=True;Connect Timeout=10"
78+
let compileConnectionString =
79+
@"Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=" + __SOURCE_DIRECTORY__ + @"\Database1.mdf;Integrated Security=True;Connect Timeout=10"
7780

78-
type Foo = SqlCommandProvider<"SELECT * FROM Foo", connectionStringForCompileTime>
81+
type Foo = SqlCommandProvider<"SELECT * FROM Foo", compileConnectionString>
7982

8083
let myResults = (new Foo("Use your Runtime connectionString here")).Execute()
8184

8285
(**
83-
Lastly, edit your `.fsproj` file and add the following to the very end right before `</Project>`
84-
85-
<Target Name="BeforeBuild">
86-
<Message Text="Building out SQL Database: Database1.mdf" Importance="High" />
87-
<Exec Command="PowerShell -NoProfile -ExecutionPolicy Bypass -Command &quot;&amp; { $(ProjectDir)Createdb.ps1 }&quot;" />
86+
Lastly, edit your `.fsproj` file and add the following to the very end right
87+
before `</Project>`:
88+
89+
<ItemGroup>
90+
<SqlFiles Include="**\*.sql" />
91+
<BuildDbPsScript Include="SQL\createDb.ps1" />
92+
<BuildDbSqlScripts Include="SQL\create_myDb1.sql" DbName="Db1" />
93+
<BuildDbSqlScripts Include="SQL\create_myDb2.sql" DbName="Db2" />
94+
<UpToDateCheckInput Include="@(SqlFiles)" />
95+
<UpToDateCheckInput Include="@(BuildDbPsScript)" />
96+
<UpToDateCheckInput Include="@(BuildDbSqlScripts)" />
97+
<UpToDateCheckInput Include="@(BuildDbSqlScripts -> 'SQL\%(DbName).mdf')" />
98+
<UpToDateCheckInput Include="@(BuildDbSqlScripts -> 'SQL\%(DbName).ldf')" />
99+
</ItemGroup>
100+
101+
<Target Name="BuildDb" BeforeTargets="BeforeBuild" Inputs="@(BuildDbSqlScripts);@(BuildDbPsScript)" Outputs="SQL\%(BuildDbSqlScripts.DbName).mdf;SQL\%(BuildDbSqlScripts.DbName).ldf">
102+
<Message Text="DB files missing or outdated. Building out database %(BuildDbSqlScripts.DbName) using script %(BuildDbSqlScripts.Identity)" Importance="High" />
103+
<Exec Command="PowerShell -NoProfile -ExecutionPolicy Bypass -Command &quot;&amp; { @(BuildDbPsScript) -DbName %(BuildDbSqlScripts.DbName) -DbScript %(BuildDbSqlScripts.Identity) }&quot;" />
88104
</Target>
89105
90-
Now when you build, it will create a database named `Database1` and then look for a file called `schema.sql` which will be used
91-
to create the database. It will then compile against this dynamically generated MDF file so you'll get full static type checking
92-
without the hassle of having to have an internet connection, or deal with binary .MDF files!
106+
<Target Name="TouchProjectFileIfSqlOrDbChanged" BeforeTargets="BeforeBuild" Inputs="@(SqlFiles);@(BuildDbPsScript);@(BuildDbSqlScripts)" Outputs="$(MSBuildProjectFile)">
107+
<Message Text="SQL or DB files changed. Changing project file modification time to force recompilation." Importance="High" />
108+
<Exec Command="PowerShell -NoProfile -ExecutionPolicy Bypass -Command &quot;(dir $(MSBuildProjectFile)).LastWriteTime = Get-Date&quot;" />
109+
</Target>
93110
94-
*)
111+
Now when you build, it will create the databases `SQL\Db1.mdf` and `SQL\Db2.mdf`
112+
using the scripts `SQL\create_myDb1.sql` and `SQL\create_myDb2.sql`. It will
113+
then compile against this dynamically generated MDF file so you'll get full
114+
static type checking without the hassle of having to have an internet
115+
connection, or deal with binary .MDF files!
116+
117+
Furthermore, the `.fsproj` edits above give the following benefits:
118+
* The DBs are rebuilt if their corresponding SQL scripts have changed, or if the
119+
PowerShell script has changed
120+
* The project is rebuilt if the PowerShell script has changed
121+
* The project is rebuilt if any SQL file has changed (both the database creation
122+
scripts, and any other SQL scripts that SqlClient might use though the
123+
`SqlFile` type provider)
124+
* Incremental build - each database is only built if its corresponding SQL
125+
script or the PowerShell script has changed
126+
127+
When it comes to actually making the database creation scripts (such as the
128+
`create_myDb1.sql` in the example above), you can do this if you use SQL Server
129+
Management Studio (SSMS):
130+
* Connect to the database you want to copy
131+
* Right-click the database and select Tasks -> Generate scripts
132+
* Select what you need to be exported (for example, everything except Users).
133+
* If SqlClient throws errors when connecting to your local database, you might
134+
be missing important objects from your database. Make sure everything you need
135+
is enabled in SSMS under Tools -> Options -> SQL Server Object Explorer ->
136+
Scripting. For example, if you have indexed views and use the `WITH
137+
(NOEXPAND)` hint in your SQL, you need the indexes too, which are not enabled
138+
by default. In this case, enable "Script indexes" under the "Table and view
139+
options" heading.
140+
*)

0 commit comments

Comments
 (0)