6
6
Dynamic creation of offline MDF
7
7
===============================
8
8
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:
11
16
*)
12
17
13
18
open FSharp.Data
14
19
15
20
[<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"
17
23
18
24
(**
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?
21
29
22
30
Well the following scripts can do that for your project.
23
31
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):
25
34
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
+ )
28
39
29
40
$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'
34
43
"@
35
44
36
45
$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"
39
48
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" }
42
51
43
52
$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
+ )
60
62
"@
61
63
62
64
$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"
65
67
66
- sqlcmd -S "(localdb)\v11.0 " -i schema.sql
68
+ sqlcmd -S "(LocalDB)\MSSQLLocalDB " -i "$DbScript"
67
69
68
70
$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"
71
73
72
74
Then change your connection string to look like this
73
75
*)
74
76
75
77
[<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"
77
80
78
- type Foo = SqlCommandProvider< " SELECT * FROM Foo" , connectionStringForCompileTime >
81
+ type Foo = SqlCommandProvider< " SELECT * FROM Foo" , compileConnectionString >
79
82
80
83
let myResults = ( new Foo( " Use your Runtime connectionString here" )) .Execute()
81
84
82
85
(**
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 "& { $(ProjectDir)Createdb.ps1 }"" />
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 "& { @(BuildDbPsScript) -DbName %(BuildDbSqlScripts.DbName) -DbScript %(BuildDbSqlScripts.Identity) }"" />
88
104
</Target>
89
105
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 "(dir $(MSBuildProjectFile)).LastWriteTime = Get-Date"" />
109
+ </Target>
93
110
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